changeset 3786:3801435e34a1 SylvainRouquette/fix-issue169-95b752c

integration Orthanc-1.6.0->SylvainRouquette
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 19 Mar 2020 11:48:30 +0100
parents 763533d6dd67 (current diff) 64a095d133a8 (diff)
children 7c853804f3b9
files Core/DicomNetworking/DicomUserConnection.cpp Core/DicomNetworking/DicomUserConnection.h Core/DicomNetworking/RemoteModalityParameters.cpp Core/DicomNetworking/RemoteModalityParameters.h Resources/Patches/dcmtk-3.6.2-cmath.patch Resources/WebAssembly/dcdict.cc Resources/WebAssembly/dcdict.h UnitTestsSources/MultiThreadingTests.cpp
diffstat 547 files changed, 13485 insertions(+), 4013 deletions(-) [+]
line wrap: on
line diff
--- a/.hgtags	Wed Mar 18 08:59:06 2020 +0100
+++ b/.hgtags	Thu Mar 19 11:48:30 2020 +0100
@@ -1,1 +1,4 @@
 a95beca72e99f3a1110cffd252bcf3abf5a2db27 dcmtk-3.6.1
+19966d29968506773f90b733b6e34559839ca5c7 toa2020012701
+dfd9a2229c18abd5c794d8fec967ef0ed10b8e91 toa2020012702
+799a8278b151222ea9e8b8628b1d57b5b7943f41 toa2020012703
--- a/CMakeLists.txt	Wed Mar 18 08:59:06 2020 +0100
+++ b/CMakeLists.txt	Thu Mar 19 11:48:30 2020 +0100
@@ -17,7 +17,7 @@
 set(ENABLE_JPEG ON)
 set(ENABLE_LOCALE ON)
 set(ENABLE_LUA ON)
-set(ENABLE_OPENSSL_ENGINES ON)
+set(ENABLE_OPENSSL_ENGINES ON)  # OpenSSL engines are necessary for PKCS11
 set(ENABLE_PNG ON)
 set(ENABLE_PUGIXML ON)
 set(ENABLE_SQLITE ON)
@@ -25,6 +25,9 @@
 set(ENABLE_WEB_SERVER ON)
 set(ENABLE_ZLIB ON)
 
+# To test transcoding
+#set(ENABLE_DCMTK_TRANSCODING ON)
+
 set(HAS_EMBEDDED_RESOURCES ON)
 
 
@@ -37,6 +40,7 @@
 SET(BUILD_MODALITY_WORKLISTS ON CACHE BOOL "Whether to build the sample plugin to serve modality worklists")
 SET(BUILD_RECOVER_COMPRESSED_FILE ON CACHE BOOL "Whether to build the companion tool to recover files compressed using Orthanc")
 SET(BUILD_SERVE_FOLDERS ON CACHE BOOL "Whether to build the ServeFolders plugin")
+SET(BUILD_CONNECTIVITY_CHECKS ON CACHE BOOL "Whether to build the ConnectivityChecks plugin")
 SET(ENABLE_PLUGINS ON CACHE BOOL "Enable plugins")
 SET(UNIT_TESTS_WITH_HTTP_CONNEXIONS ON CACHE BOOL "Allow unit tests to make HTTP requests")
 
@@ -102,8 +106,10 @@
   OrthancServer/ServerJobs/OrthancPeerStoreJob.cpp
   OrthancServer/ServerJobs/ResourceModificationJob.cpp
   OrthancServer/ServerJobs/SplitStudyJob.cpp
+  OrthancServer/ServerJobs/StorageCommitmentScpJob.cpp
   OrthancServer/ServerToolbox.cpp
   OrthancServer/SliceOrdering.cpp
+  OrthancServer/StorageCommitmentReports.cpp
   )
 
 
@@ -461,6 +467,67 @@
 
 
 #####################################################################
+## Build the "ConnectivityChecks" plugin
+#####################################################################
+
+if (ENABLE_PLUGINS AND BUILD_CONNECTIVITY_CHECKS)
+  include(ExternalProject)
+
+  set(Flags)
+
+  if (CMAKE_TOOLCHAIN_FILE)
+    # Take absolute path to the toolchain
+    get_filename_component(TMP ${CMAKE_TOOLCHAIN_FILE} REALPATH BASE ${CMAKE_SOURCE_DIR})
+    list(APPEND Flags -DCMAKE_TOOLCHAIN_FILE=${TMP})
+  endif()
+
+  if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+    list(APPEND Flags
+      -DLSB_CC=${CMAKE_LSB_CC}
+      -DLSB_CXX=${CMAKE_LSB_CXX}
+      )
+  endif()
+
+  externalproject_add(ConnectivityChecks
+    SOURCE_DIR "${ORTHANC_ROOT}/Plugins/Samples/ConnectivityChecks"
+
+    # We explicitly provide a build directory, in order to avoid paths
+    # that are too long on our Visual Studio 2008 CIS
+    BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/ConnectivityChecks-build"
+
+    CMAKE_ARGS
+    -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
+    -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}
+    -DPLUGIN_VERSION=${ORTHANC_VERSION}
+    -DSTATIC_BUILD=${STATIC_BUILD}
+    -DALLOW_DOWNLOADS=${ALLOW_DOWNLOADS}
+    -DUSE_LEGACY_JSONCPP=${USE_LEGACY_JSONCPP}
+    ${Flags}
+    )
+
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+    if (MSVC)
+      set(Prefix "")
+    else()
+      set(Prefix "lib")  # MinGW
+    endif()
+
+    install(FILES
+      ${CMAKE_CURRENT_BINARY_DIR}/${Prefix}ConnectivityChecks.dll
+      DESTINATION "lib")
+  else()
+    list(GET CMAKE_FIND_LIBRARY_PREFIXES 0 Prefix)
+    list(GET CMAKE_FIND_LIBRARY_SUFFIXES 0 Suffix)
+    install(FILES
+      ${CMAKE_CURRENT_BINARY_DIR}/${Prefix}ConnectivityChecks${Suffix}
+      ${CMAKE_CURRENT_BINARY_DIR}/${Prefix}ConnectivityChecks${Suffix}.${ORTHANC_VERSION}
+      DESTINATION "share/orthanc/plugins")
+  endif()
+endif()
+
+
+
+#####################################################################
 ## Build the companion tool to recover files compressed using Orthanc
 #####################################################################
 
--- a/Core/Cache/ICachePageProvider.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Cache/ICachePageProvider.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -38,13 +38,16 @@
 
 namespace Orthanc
 {
-  class ICachePageProvider
+  namespace Deprecated
   {
-  public:
-    virtual ~ICachePageProvider()
+    class ICachePageProvider
     {
-    }
+    public:
+      virtual ~ICachePageProvider()
+      {
+      }
 
-    virtual IDynamicObject* Provide(const std::string& id) = 0;
-  };
+      virtual IDynamicObject* Provide(const std::string& id) = 0;
+    };
+  }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Cache/ICacheable.h	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,49 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class ICacheable : public boost::noncopyable
+  {
+  public:
+    virtual ~ICacheable()
+    {
+    }
+
+    virtual size_t GetMemoryUsage() const = 0;
+  };
+}
--- a/Core/Cache/LeastRecentlyUsedIndex.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Cache/LeastRecentlyUsedIndex.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Cache/MemoryCache.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Cache/MemoryCache.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -38,71 +38,74 @@
 
 namespace Orthanc
 {
-  MemoryCache::Page& MemoryCache::Load(const std::string& id)
+  namespace Deprecated
   {
-    // Reuse the cache entry if it already exists
-    Page* p = NULL;
-    if (index_.Contains(id, p))
+    MemoryCache::Page& MemoryCache::Load(const std::string& id)
     {
-      VLOG(1) << "Reusing a cache page";
-      assert(p != NULL);
-      index_.MakeMostRecent(id);
+      // Reuse the cache entry if it already exists
+      Page* p = NULL;
+      if (index_.Contains(id, p))
+      {
+        VLOG(1) << "Reusing a cache page";
+        assert(p != NULL);
+        index_.MakeMostRecent(id);
+        return *p;
+      }
+
+      // The id is not in the cache yet. Make some room if the cache
+      // is full.
+      if (index_.GetSize() == cacheSize_)
+      {
+        VLOG(1) << "Dropping the oldest cache page";
+        index_.RemoveOldest(p);
+        delete p;
+      }
+
+      // Create a new cache page
+      std::unique_ptr<Page> result(new Page);
+      result->id_ = id;
+      result->content_.reset(provider_.Provide(id));
+
+      // Add the newly create page to the cache
+      VLOG(1) << "Registering new data in a cache page";
+      p = result.release();
+      index_.Add(id, p);
       return *p;
     }
 
-    // The id is not in the cache yet. Make some room if the cache
-    // is full.
-    if (index_.GetSize() == cacheSize_)
+    MemoryCache::MemoryCache(ICachePageProvider& provider,
+                             size_t cacheSize) : 
+      provider_(provider),
+      cacheSize_(cacheSize)
     {
-      VLOG(1) << "Dropping the oldest cache page";
-      index_.RemoveOldest(p);
-      delete p;
     }
 
-    // Create a new cache page
-    std::auto_ptr<Page> result(new Page);
-    result->id_ = id;
-    result->content_.reset(provider_.Provide(id));
-
-    // Add the newly create page to the cache
-    VLOG(1) << "Registering new data in a cache page";
-    p = result.release();
-    index_.Add(id, p);
-    return *p;
-  }
+    void MemoryCache::Invalidate(const std::string& id)
+    {
+      Page* p = NULL;
+      if (index_.Contains(id, p))
+      {
+        VLOG(1) << "Invalidating a cache page";
+        assert(p != NULL);
+        delete p;
+        index_.Invalidate(id);
+      }
+    }
 
-  MemoryCache::MemoryCache(ICachePageProvider& provider,
-                           size_t cacheSize) : 
-    provider_(provider),
-    cacheSize_(cacheSize)
-  {
-  }
+    MemoryCache::~MemoryCache()
+    {
+      while (!index_.IsEmpty())
+      {
+        Page* element = NULL;
+        index_.RemoveOldest(element);
+        assert(element != NULL);
+        delete element;
+      }
+    }
 
-  void MemoryCache::Invalidate(const std::string& id)
-  {
-    Page* p = NULL;
-    if (index_.Contains(id, p))
+    IDynamicObject& MemoryCache::Access(const std::string& id)
     {
-      VLOG(1) << "Invalidating a cache page";
-      assert(p != NULL);
-      delete p;
-      index_.Invalidate(id);
+      return *Load(id).content_;
     }
   }
-
-  MemoryCache::~MemoryCache()
-  {
-    while (!index_.IsEmpty())
-    {
-      Page* element = NULL;
-      index_.RemoveOldest(element);
-      assert(element != NULL);
-      delete element;
-    }
-  }
-
-  IDynamicObject& MemoryCache::Access(const std::string& id)
-  {
-    return *Load(id).content_;
-  }
 }
--- a/Core/Cache/MemoryCache.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Cache/MemoryCache.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -33,38 +33,43 @@
 
 #pragma once
 
-#include <memory>
+#include "../Compatibility.h"
+#include "ICachePageProvider.h"
 #include "LeastRecentlyUsedIndex.h"
-#include "ICachePageProvider.h"
+
+#include <memory>
 
 namespace Orthanc
 {
-  /**
-   * WARNING: This class is NOT thread-safe.
-   **/
-  class MemoryCache
+  namespace Deprecated
   {
-  private:
-    struct Page
+    /**
+     * WARNING: This class is NOT thread-safe.
+     **/
+    class MemoryCache
     {
-      std::string id_;
-      std::auto_ptr<IDynamicObject> content_;
-    };
+    private:
+      struct Page
+      {
+        std::string id_;
+        std::unique_ptr<IDynamicObject> content_;
+      };
 
-    ICachePageProvider& provider_;
-    size_t cacheSize_;
-    LeastRecentlyUsedIndex<std::string, Page*>  index_;
+      ICachePageProvider& provider_;
+      size_t cacheSize_;
+      LeastRecentlyUsedIndex<std::string, Page*>  index_;
 
-    Page& Load(const std::string& id);
+      Page& Load(const std::string& id);
 
-  public:
-    MemoryCache(ICachePageProvider& provider,
-                size_t cacheSize);
+    public:
+      MemoryCache(ICachePageProvider& provider,
+                  size_t cacheSize);
 
-    ~MemoryCache();
+      ~MemoryCache();
 
-    IDynamicObject& Access(const std::string& id);
+      IDynamicObject& Access(const std::string& id);
 
-    void Invalidate(const std::string& id);
-  };
+      void Invalidate(const std::string& id);
+    };
+  }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Cache/MemoryObjectCache.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,282 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "MemoryObjectCache.h"
+
+#include "../Compatibility.h"
+
+namespace Orthanc
+{
+  class MemoryObjectCache::Item : public boost::noncopyable
+  {
+  private:
+    ICacheable*               value_;
+    boost::posix_time::ptime  time_;
+
+  public:
+    explicit Item(ICacheable* value) :   // Takes ownership
+    value_(value),
+    time_(boost::posix_time::second_clock::local_time())
+    {
+      if (value == NULL)
+      {
+        throw OrthancException(ErrorCode_NullPointer);
+      }
+    }
+
+    ~Item()
+    {
+      assert(value_ != NULL);
+      delete value_;
+    }
+
+    ICacheable& GetValue() const
+    {
+      assert(value_ != NULL);
+      return *value_;
+    }
+
+    const boost::posix_time::ptime& GetTime() const
+    {
+      return time_;
+    }
+  };
+
+
+  void MemoryObjectCache::Recycle(size_t targetSize)
+  {
+    // WARNING: "cacheMutex_" must be locked
+    while (currentSize_ > targetSize)
+    {
+      assert(!content_.IsEmpty());
+        
+      Item* item = NULL;
+      content_.RemoveOldest(item);
+
+      assert(item != NULL);
+      const size_t size = item->GetValue().GetMemoryUsage();
+      delete item;
+
+      assert(currentSize_ >= size);
+      currentSize_ -= size;
+    }
+
+    // Post-condition: "currentSize_ <= targetSize"
+  }
+    
+
+  MemoryObjectCache::MemoryObjectCache() :
+    currentSize_(0),
+    maxSize_(100 * 1024 * 1024)  // 100 MB
+  {
+  }
+
+
+  MemoryObjectCache::~MemoryObjectCache()
+  {
+    Recycle(0);
+    assert(content_.IsEmpty());
+  }
+
+
+  size_t MemoryObjectCache::GetMaximumSize()
+  {
+#if !defined(__EMSCRIPTEN__)
+    boost::mutex::scoped_lock lock(cacheMutex_);
+#endif
+
+    return maxSize_;
+  }
+
+
+  void MemoryObjectCache::SetMaximumSize(size_t size)
+  {
+    if (size == 0)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+      
+#if !defined(__EMSCRIPTEN__)
+    // Make sure no accessor is currently open (as its data may be
+    // removed if recycling is needed)
+    WriterLock contentLock(contentMutex_);
+
+    // Lock the global structure of the cache
+    boost::mutex::scoped_lock cacheLock(cacheMutex_);
+#endif
+
+    Recycle(size);
+    maxSize_ = size;
+  }
+
+
+  void MemoryObjectCache::Acquire(const std::string& key,
+                                  ICacheable* value)
+  {
+    std::unique_ptr<Item> item(new Item(value));
+
+    if (value == NULL)
+    {
+      throw OrthancException(ErrorCode_NullPointer);
+    }
+    else
+    {
+#if !defined(__EMSCRIPTEN__)
+      // Make sure no accessor is currently open (as its data may be
+      // removed if recycling is needed)
+      WriterLock contentLock(contentMutex_);
+
+      // Lock the global structure of the cache
+      boost::mutex::scoped_lock cacheLock(cacheMutex_);
+#endif
+
+      const size_t size = item->GetValue().GetMemoryUsage();
+
+      if (size > maxSize_)
+      {
+        // This object is too large to be stored in the cache, discard it
+      }
+      else if (content_.Contains(key))
+      {
+        // Value already stored, don't overwrite the old value
+        content_.MakeMostRecent(key);
+      }
+      else
+      {
+        Recycle(maxSize_ - size);   // Post-condition: currentSize_ <= maxSize_ - size
+        assert(currentSize_ + size <= maxSize_);
+
+        content_.Add(key, item.release());
+        currentSize_ += size;
+      }
+    }
+  }
+
+
+  void MemoryObjectCache::Invalidate(const std::string& key)
+  {
+#if !defined(__EMSCRIPTEN__)
+    // Make sure no accessor is currently open (as it may correspond
+    // to the key to remove)
+    WriterLock contentLock(contentMutex_);
+
+    // Lock the global structure of the cache
+    boost::mutex::scoped_lock cacheLock(cacheMutex_);
+#endif
+
+    Item* item = NULL;
+    if (content_.Contains(key, item))
+    {
+      assert(item != NULL);
+      const size_t size = item->GetValue().GetMemoryUsage();
+      delete item;
+
+      content_.Invalidate(key);
+          
+      assert(currentSize_ >= size);
+      currentSize_ -= size;
+    }
+  }
+
+
+  MemoryObjectCache::Accessor::Accessor(MemoryObjectCache& cache,
+                                        const std::string& key,
+                                        bool unique) :
+    item_(NULL)
+  {
+#if !defined(__EMSCRIPTEN__)
+    if (unique)
+    {
+      writerLock_ = WriterLock(cache.contentMutex_);
+    }
+    else
+    {
+      readerLock_ = ReaderLock(cache.contentMutex_);
+    }
+
+    // Lock the global structure of the cache, must be *after* the
+    // reader/writer lock
+    cacheLock_ = boost::mutex::scoped_lock(cache.cacheMutex_);
+#endif
+
+    if (cache.content_.Contains(key, item_))
+    {
+      cache.content_.MakeMostRecent(key);
+    }
+    
+#if !defined(__EMSCRIPTEN__)
+    cacheLock_.unlock();
+
+    if (item_ == NULL)
+    {
+      // This item does not exist in the cache, we can release the
+      // reader/writer lock
+      if (unique)
+      {
+        writerLock_.unlock();
+      }
+      else
+      {
+        readerLock_.unlock();
+      }
+    }
+#endif
+  }
+
+
+  ICacheable& MemoryObjectCache::Accessor::GetValue() const
+  {
+    if (IsValid())
+    {
+      return item_->GetValue();
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+
+  const boost::posix_time::ptime& MemoryObjectCache::Accessor::GetTime() const
+  {
+    if (IsValid())
+    {
+      return item_->GetTime();
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }        
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Cache/MemoryObjectCache.h	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,112 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "ICacheable.h"
+#include "LeastRecentlyUsedIndex.h"
+
+#if !defined(__EMSCRIPTEN__)
+// Multithreading is not supported in WebAssembly
+#  include <boost/thread/mutex.hpp>
+#  include <boost/thread/shared_mutex.hpp>
+#endif
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+
+namespace Orthanc
+{
+  class MemoryObjectCache : public boost::noncopyable
+  {
+  private:
+    class Item;
+
+#if !defined(__EMSCRIPTEN__)
+    typedef boost::unique_lock<boost::shared_mutex> WriterLock;
+    typedef boost::shared_lock<boost::shared_mutex> ReaderLock;
+
+    // This mutex protects modifications to the structure of the cache (monitor)
+    boost::mutex   cacheMutex_;
+
+    // This mutex protects modifications to the items that are stored in the cache
+    boost::shared_mutex contentMutex_;
+#endif
+
+    size_t currentSize_;
+    size_t maxSize_;
+    LeastRecentlyUsedIndex<std::string, Item*>  content_;
+
+    void Recycle(size_t targetSize);
+    
+  public:
+    MemoryObjectCache();
+
+    ~MemoryObjectCache();
+
+    size_t GetMaximumSize();
+
+    void SetMaximumSize(size_t size);
+
+    void Acquire(const std::string& key,
+                 ICacheable* value);
+
+    void Invalidate(const std::string& key);
+
+    class Accessor : public boost::noncopyable
+    {
+    private:
+#if !defined(__EMSCRIPTEN__)
+      ReaderLock                 readerLock_;
+      WriterLock                 writerLock_;
+      boost::mutex::scoped_lock  cacheLock_;
+#endif
+      
+      Item*  item_;
+
+    public:
+      Accessor(MemoryObjectCache& cache,
+               const std::string& key,
+               bool unique);
+
+      bool IsValid() const
+      {
+        return item_ != NULL;
+      }
+
+      ICacheable& GetValue() const;
+
+      const boost::posix_time::ptime& GetTime() const;
+    };
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Cache/MemoryStringCache.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,84 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "MemoryStringCache.h"
+
+namespace Orthanc
+{
+  class MemoryStringCache::StringValue : public ICacheable
+  {
+  private:
+    std::string  content_;
+
+  public:
+    StringValue(const std::string& content) :
+      content_(content)
+    {
+    }
+      
+    const std::string& GetContent() const
+    {
+      return content_;
+    }
+
+    virtual size_t GetMemoryUsage() const
+    {
+      return content_.size();
+    }      
+  };
+
+
+  void MemoryStringCache::Add(const std::string& key,
+                              const std::string& value)
+  {
+    cache_.Acquire(key, new StringValue(value));
+  }
+
+  
+  bool MemoryStringCache::Fetch(std::string& value,
+                                const std::string& key)
+  {
+    MemoryObjectCache::Accessor reader(cache_, key, false /* multiple readers are allowed */);
+
+    if (reader.IsValid())
+    {
+      value = dynamic_cast<StringValue&>(reader.GetValue()).GetContent();
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Cache/MemoryStringCache.h	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,73 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "MemoryObjectCache.h"
+
+namespace Orthanc
+{
+  /**
+   * Facade object around "MemoryObjectCache" that caches a dictionary
+   * of strings, using the "fetch/add" paradigm of memcached.
+   **/
+  class MemoryStringCache : public boost::noncopyable
+  {
+  private:
+    class StringValue;
+
+    MemoryObjectCache  cache_;
+
+  public:
+    size_t GetMaximumSize()
+    {
+      return cache_.GetMaximumSize();
+    }
+    
+    void SetMaximumSize(size_t size)
+    {
+      cache_.SetMaximumSize(size);
+    }
+
+    void Add(const std::string& key,
+             const std::string& value);
+    
+    void Invalidate(const std::string& key)
+    {
+      cache_.Invalidate(key);
+    }
+
+    bool Fetch(std::string& value,
+               const std::string& key);
+  };
+}
--- a/Core/Cache/SharedArchive.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Cache/SharedArchive.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Cache/SharedArchive.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Cache/SharedArchive.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -57,7 +57,7 @@
     size_t         maxSize_;
     boost::mutex   mutex_;
     Archive        archive_;
-    Orthanc::LeastRecentlyUsedIndex<std::string> lru_;
+    LeastRecentlyUsedIndex<std::string> lru_;
 
     void RemoveInternal(const std::string& id);
 
--- a/Core/ChunkedBuffer.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/ChunkedBuffer.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/ChunkedBuffer.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/ChunkedBuffer.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Compatibility.h	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,105 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+//#define Orthanc_Compatibility_h_STR2(x) #x
+//#define Orthanc_Compatibility_h_STR1(x) Orthanc_Compatibility_h_STR2(x)
+
+//#pragma message("__cplusplus = " Orthanc_Compatibility_h_STR1(__cplusplus))
+
+#if (defined _MSC_VER)
+//#  pragma message("_MSC_VER = " Orthanc_Compatibility_h_STR1(_MSC_VER))
+//#  pragma message("_MSVC_LANG = " Orthanc_Compatibility_h_STR1(_MSVC_LANG))
+// The __cplusplus macro cannot be used in Visual C++ < 1914 (VC++ 15.7)
+// However, even in recent versions, __cplusplus will only be correct (that is,
+// correctly defines the supported C++ version) if a special flag is passed to
+// the compiler ("/Zc:__cplusplus")
+// To make this header more robust, we use the _MSVC_LANG equivalent macro.
+
+// please note that not all C++11 features are supported when _MSC_VER == 1600
+// (or higher). This header file can be made for fine-grained, if required, 
+// based on specific _MSC_VER values
+
+#  if _MSC_VER >= 1600
+#    define ORTHANC_Cxx03_DETECTED 0
+#  else
+#    define ORTHANC_Cxx03_DETECTED 1
+#  endif
+
+#else
+// of _MSC_VER is not defined, we assume __cplusplus is correctly defined
+// if __cplusplus is not defined (very old compilers??), then the following
+// test will compare 0 < 201103L and will be true --> safe.
+#  if __cplusplus < 201103L
+#    define ORTHANC_Cxx03_DETECTED 1
+#  else
+#    define ORTHANC_Cxx03_DETECTED 0
+#  endif
+#endif
+
+#if ORTHANC_Cxx03_DETECTED == 1
+//#pragma message("C++ 11 support is not present.")
+
+/**
+ * "std::unique_ptr" was introduced in C++11, and "std::auto_ptr" was
+ * removed in C++17. We emulate "std::auto_ptr" using boost: "The
+ * smart pointer unique_ptr [is] a drop-in replacement for
+ * std::unique_ptr, usable also from C++03 compilers." This is only
+ * available if Boost >= 1.57.0 (from November 2014).
+ * https://www.boost.org/doc/libs/1_57_0/doc/html/move/reference.html#header.boost.move.unique_ptr_hpp
+ **/
+
+#include <boost/move/unique_ptr.hpp>
+
+namespace std
+{
+  template <typename T>
+  class unique_ptr : public boost::movelib::unique_ptr<T>
+  {
+  public:
+    explicit unique_ptr() :
+      boost::movelib::unique_ptr<T>()
+    {
+    }      
+
+    explicit unique_ptr(T* p) :
+      boost::movelib::unique_ptr<T>(p)
+    {
+    }      
+  };
+}
+#else
+//# pragma message("C++ 11 support is present.")
+# include <memory>
+#endif
--- a/Core/Compression/DeflateBaseCompressor.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Compression/DeflateBaseCompressor.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Compression/DeflateBaseCompressor.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Compression/DeflateBaseCompressor.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Compression/GzipCompressor.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Compression/GzipCompressor.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Compression/GzipCompressor.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Compression/GzipCompressor.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Compression/HierarchicalZipWriter.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Compression/HierarchicalZipWriter.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Compression/HierarchicalZipWriter.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Compression/HierarchicalZipWriter.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Compression/IBufferCompressor.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Compression/IBufferCompressor.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Compression/ZipWriter.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Compression/ZipWriter.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Compression/ZipWriter.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Compression/ZipWriter.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Compression/ZlibCompressor.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Compression/ZlibCompressor.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Compression/ZlibCompressor.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Compression/ZlibCompressor.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomFormat/DicomArray.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomFormat/DicomArray.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -40,10 +40,10 @@
 {
   DicomArray::DicomArray(const DicomMap& map)
   {
-    elements_.reserve(map.map_.size());
+    elements_.reserve(map.content_.size());
     
-    for (DicomMap::Map::const_iterator it = 
-           map.map_.begin(); it != map.map_.end(); ++it)
+    for (DicomMap::Content::const_iterator it = 
+           map.content_.begin(); it != map.content_.end(); ++it)
     {
       elements_.push_back(new DicomElement(it->first, *it->second));
     }
--- a/Core/DicomFormat/DicomArray.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomFormat/DicomArray.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomFormat/DicomElement.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomFormat/DicomElement.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomFormat/DicomImageInformation.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomFormat/DicomImageInformation.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -39,6 +39,7 @@
 
 #include "DicomImageInformation.h"
 
+#include "../Compatibility.h"
 #include "../OrthancException.h"
 #include "../Toolbox.h"
 #include <boost/lexical_cast.hpp>
@@ -223,7 +224,7 @@
 
   DicomImageInformation* DicomImageInformation::Clone() const
   {
-    std::auto_ptr<DicomImageInformation> target(new DicomImageInformation);
+    std::unique_ptr<DicomImageInformation> target(new DicomImageInformation);
     target->width_ = width_;
     target->height_ = height_;
     target->samplesPerPixel_ = samplesPerPixel_;
--- a/Core/DicomFormat/DicomImageInformation.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomFormat/DicomImageInformation.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomFormat/DicomInstanceHasher.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomFormat/DicomInstanceHasher.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomFormat/DicomInstanceHasher.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomFormat/DicomInstanceHasher.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomFormat/DicomIntegerPixelAccessor.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomFormat/DicomIntegerPixelAccessor.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomFormat/DicomIntegerPixelAccessor.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomFormat/DicomMap.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomFormat/DicomMap.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -37,6 +37,7 @@
 #include <stdio.h>
 #include <memory>
 
+#include "../Compatibility.h"
 #include "../Endianness.h"
 #include "../Logging.h"
 #include "../OrthancException.h"
@@ -46,72 +47,87 @@
 
 namespace Orthanc
 {
-  static DicomTag patientTags[] =
+  namespace
   {
-    //DicomTag(0x0010, 0x1010), // PatientAge
-    //DicomTag(0x0010, 0x1040)  // PatientAddress
-    DicomTag(0x0010, 0x0010),   // PatientName
-    DicomTag(0x0010, 0x0030),   // PatientBirthDate
-    DicomTag(0x0010, 0x0040),   // PatientSex
-    DicomTag(0x0010, 0x1000),   // OtherPatientIDs
-    DICOM_TAG_PATIENT_ID
-  };
+    struct MainDicomTag
+    {
+      const DicomTag tag_;
+      const char*    name_;
+    };
+  }
 
-  static DicomTag studyTags[] =
+  static const MainDicomTag PATIENT_MAIN_DICOM_TAGS[] =
   {
-    //DicomTag(0x0010, 0x1020), // PatientSize
-    //DicomTag(0x0010, 0x1030)  // PatientWeight
-    DICOM_TAG_STUDY_DATE,
-    DicomTag(0x0008, 0x0030),   // StudyTime
-    DicomTag(0x0020, 0x0010),   // StudyID
-    DICOM_TAG_STUDY_DESCRIPTION,
-    DICOM_TAG_ACCESSION_NUMBER,
-    DICOM_TAG_STUDY_INSTANCE_UID,
-    DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION,   // New in db v6
-    DICOM_TAG_INSTITUTION_NAME,                  // New in db v6
-    DICOM_TAG_REQUESTING_PHYSICIAN,              // New in db v6
-    DICOM_TAG_REFERRING_PHYSICIAN_NAME           // New in db v6
+    // { DicomTag(0x0010, 0x1010), "PatientAge" },
+    // { DicomTag(0x0010, 0x1040), "PatientAddress" },
+    { DicomTag(0x0010, 0x0010), "PatientName" },
+    { DicomTag(0x0010, 0x0030), "PatientBirthDate" },
+    { DicomTag(0x0010, 0x0040), "PatientSex" },
+    { DicomTag(0x0010, 0x1000), "OtherPatientIDs" },
+    { DICOM_TAG_PATIENT_ID, "PatientID" }
   };
+    
+  static const MainDicomTag STUDY_MAIN_DICOM_TAGS[] =
+  {
+    // { DicomTag(0x0010, 0x1020), "PatientSize" },
+    // { DicomTag(0x0010, 0x1030), "PatientWeight" },
+    { DICOM_TAG_STUDY_DATE, "StudyDate" },
+    { DicomTag(0x0008, 0x0030), "StudyTime" },
+    { DicomTag(0x0020, 0x0010), "StudyID" },
+    { DICOM_TAG_STUDY_DESCRIPTION, "StudyDescription" },
+    { DICOM_TAG_ACCESSION_NUMBER, "AccessionNumber" },
+    { DICOM_TAG_STUDY_INSTANCE_UID, "StudyInstanceUID" },
 
-  static DicomTag seriesTags[] =
+    // New in db v6
+    { DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION, "RequestedProcedureDescription" },
+    { DICOM_TAG_INSTITUTION_NAME, "InstitutionName" },
+    { DICOM_TAG_REQUESTING_PHYSICIAN, "RequestingPhysician" },
+    { DICOM_TAG_REFERRING_PHYSICIAN_NAME, "ReferringPhysicianName" }
+  };
+    
+  static const MainDicomTag SERIES_MAIN_DICOM_TAGS[] =
   {
-    //DicomTag(0x0010, 0x1080), // MilitaryRank
-    DicomTag(0x0008, 0x0021),   // SeriesDate
-    DicomTag(0x0008, 0x0031),   // SeriesTime
-    DICOM_TAG_MODALITY,
-    DicomTag(0x0008, 0x0070),   // Manufacturer
-    DicomTag(0x0008, 0x1010),   // StationName
-    DICOM_TAG_SERIES_DESCRIPTION,
-    DicomTag(0x0018, 0x0015),   // BodyPartExamined
-    DicomTag(0x0018, 0x0024),   // SequenceName
-    DicomTag(0x0018, 0x1030),   // ProtocolName
-    DicomTag(0x0020, 0x0011),   // SeriesNumber
-    DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES,
-    DICOM_TAG_IMAGES_IN_ACQUISITION,
-    DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS,
-    DICOM_TAG_NUMBER_OF_SLICES,
-    DICOM_TAG_NUMBER_OF_TIME_SLICES,
-    DICOM_TAG_SERIES_INSTANCE_UID,
-    DICOM_TAG_IMAGE_ORIENTATION_PATIENT,                  // New in db v6
-    DICOM_TAG_SERIES_TYPE,                                // New in db v6
-    DICOM_TAG_OPERATOR_NAME,                              // New in db v6
-    DICOM_TAG_PERFORMED_PROCEDURE_STEP_DESCRIPTION,       // New in db v6
-    DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION,  // New in db v6
-    DICOM_TAG_CONTRAST_BOLUS_AGENT                        // New in db v6
+    // { DicomTag(0x0010, 0x1080), "MilitaryRank" },
+    { DicomTag(0x0008, 0x0021), "SeriesDate" },
+    { DicomTag(0x0008, 0x0031), "SeriesTime" },
+    { DICOM_TAG_MODALITY, "Modality" },
+    { DicomTag(0x0008, 0x0070), "Manufacturer" },
+    { DicomTag(0x0008, 0x1010), "StationName" },
+    { DICOM_TAG_SERIES_DESCRIPTION, "SeriesDescription" },
+    { DicomTag(0x0018, 0x0015), "BodyPartExamined" },
+    { DicomTag(0x0018, 0x0024), "SequenceName" },
+    { DicomTag(0x0018, 0x1030), "ProtocolName" },
+    { DicomTag(0x0020, 0x0011), "SeriesNumber" },
+    { DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES, "CardiacNumberOfImages" },
+    { DICOM_TAG_IMAGES_IN_ACQUISITION, "ImagesInAcquisition" },
+    { DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS, "NumberOfTemporalPositions" },
+    { DICOM_TAG_NUMBER_OF_SLICES, "NumberOfSlices" },
+    { DICOM_TAG_NUMBER_OF_TIME_SLICES, "NumberOfTimeSlices" },
+    { DICOM_TAG_SERIES_INSTANCE_UID, "SeriesInstanceUID" },
+
+    // New in db v6
+    { DICOM_TAG_IMAGE_ORIENTATION_PATIENT, "ImageOrientationPatient" },
+    { DICOM_TAG_SERIES_TYPE, "SeriesType" },
+    { DICOM_TAG_OPERATOR_NAME, "OperatorsName" },
+    { DICOM_TAG_PERFORMED_PROCEDURE_STEP_DESCRIPTION, "PerformedProcedureStepDescription" },
+    { DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION, "AcquisitionDeviceProcessingDescription" },
+    { DICOM_TAG_CONTRAST_BOLUS_AGENT, "ContrastBolusAgent" }
   };
-
-  static DicomTag instanceTags[] =
+    
+  static const MainDicomTag INSTANCE_MAIN_DICOM_TAGS[] =
   {
-    DicomTag(0x0008, 0x0012),   // InstanceCreationDate
-    DicomTag(0x0008, 0x0013),   // InstanceCreationTime
-    DicomTag(0x0020, 0x0012),   // AcquisitionNumber
-    DICOM_TAG_IMAGE_INDEX,
-    DICOM_TAG_INSTANCE_NUMBER,
-    DICOM_TAG_NUMBER_OF_FRAMES,
-    DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER,
-    DICOM_TAG_SOP_INSTANCE_UID,
-    DICOM_TAG_IMAGE_POSITION_PATIENT,    // New in db v6
-    DICOM_TAG_IMAGE_COMMENTS,            // New in db v6
+    { DicomTag(0x0008, 0x0012), "InstanceCreationDate" },
+    { DicomTag(0x0008, 0x0013), "InstanceCreationTime" },
+    { DicomTag(0x0020, 0x0012), "AcquisitionNumber" },
+    { DICOM_TAG_IMAGE_INDEX, "ImageIndex" },
+    { DICOM_TAG_INSTANCE_NUMBER, "InstanceNumber" },
+    { DICOM_TAG_NUMBER_OF_FRAMES, "NumberOfFrames" },
+    { DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER, "TemporalPositionIdentifier" },
+    { DICOM_TAG_SOP_INSTANCE_UID, "SOPInstanceUID" },
+
+    // New in db v6
+    { DICOM_TAG_IMAGE_POSITION_PATIENT, "ImagePositionPatient" },
+    { DICOM_TAG_IMAGE_COMMENTS, "ImageComments" },
 
     /**
      * Main DICOM tags that are not part of any release of the
@@ -120,34 +136,34 @@
      * access these tags if the corresponding DICOM files where
      * indexed in the database by an older version of Orthanc.
      **/
-    DICOM_TAG_IMAGE_ORIENTATION_PATIENT  // New in Orthanc 1.4.2
+    { DICOM_TAG_IMAGE_ORIENTATION_PATIENT, "ImageOrientationPatient" }  // New in Orthanc 1.4.2
   };
 
 
-  void DicomMap::LoadMainDicomTags(const DicomTag*& tags,
-                                   size_t& size,
-                                   ResourceType level)
+  static void LoadMainDicomTags(const MainDicomTag*& tags,
+                                size_t& size,
+                                ResourceType level)
   {
     switch (level)
     {
       case ResourceType_Patient:
-        tags = patientTags;
-        size = sizeof(patientTags) / sizeof(DicomTag);
+        tags = PATIENT_MAIN_DICOM_TAGS;
+        size = sizeof(PATIENT_MAIN_DICOM_TAGS) / sizeof(MainDicomTag);
         break;
 
       case ResourceType_Study:
-        tags = studyTags;
-        size = sizeof(studyTags) / sizeof(DicomTag);
+        tags = STUDY_MAIN_DICOM_TAGS;
+        size = sizeof(STUDY_MAIN_DICOM_TAGS) / sizeof(MainDicomTag);
         break;
 
       case ResourceType_Series:
-        tags = seriesTags;
-        size = sizeof(seriesTags) / sizeof(DicomTag);
+        tags = SERIES_MAIN_DICOM_TAGS;
+        size = sizeof(SERIES_MAIN_DICOM_TAGS) / sizeof(MainDicomTag);
         break;
 
       case ResourceType_Instance:
-        tags = instanceTags;
-        size = sizeof(instanceTags) / sizeof(DicomTag);
+        tags = INSTANCE_MAIN_DICOM_TAGS;
+        size = sizeof(INSTANCE_MAIN_DICOM_TAGS) / sizeof(MainDicomTag);
         break;
 
       default:
@@ -156,55 +172,106 @@
   }
 
 
-  void DicomMap::SetValue(uint16_t group, 
-                          uint16_t element, 
-                          DicomValue* value)
+  static void LoadMainDicomTags(std::map<DicomTag, std::string>& target,
+                                ResourceType level)
+  {
+    const MainDicomTag* tags = NULL;
+    size_t size;
+    LoadMainDicomTags(tags, size, level);
+
+    assert(tags != NULL &&
+           size != 0);
+
+    for (size_t i = 0; i < size; i++)
+    {
+      assert(target.find(tags[i].tag_) == target.end());
+      
+      target[tags[i].tag_] = tags[i].name_;
+    }
+  }
+
+
+  namespace
+  {
+    class DicomTag2 : public DicomTag
+    {
+    public:
+      DicomTag2() :
+        DicomTag(0, 0)   // To make std::map<> happy
+      {
+      }
+
+      DicomTag2(const DicomTag& tag) :
+        DicomTag(tag)
+      {
+      }
+    };
+  }
+
+
+  static void LoadMainDicomTags(std::map<std::string, DicomTag2>& target,
+                                ResourceType level)
+  {
+    const MainDicomTag* tags = NULL;
+    size_t size;
+    LoadMainDicomTags(tags, size, level);
+
+    assert(tags != NULL &&
+           size != 0);
+
+    for (size_t i = 0; i < size; i++)
+    {
+      assert(target.find(tags[i].name_) == target.end());
+      
+      target[tags[i].name_] = tags[i].tag_;
+    }
+  }
+
+
+  void DicomMap::SetValueInternal(uint16_t group, 
+                                  uint16_t element, 
+                                  DicomValue* value)
   {
     DicomTag tag(group, element);
-    Map::iterator it = map_.find(tag);
+    Content::iterator it = content_.find(tag);
 
-    if (it != map_.end())
+    if (it != content_.end())
     {
       delete it->second;
       it->second = value;
     }
     else
     {
-      map_.insert(std::make_pair(tag, value));
+      content_.insert(std::make_pair(tag, value));
     }
   }
 
-  void DicomMap::SetValue(DicomTag tag, 
-                          DicomValue* value)
-  {
-    SetValue(tag.GetGroup(), tag.GetElement(), value);
-  }
-
 
   void DicomMap::Clear()
   {
-    for (Map::iterator it = map_.begin(); it != map_.end(); ++it)
+    for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
     {
       assert(it->second != NULL);
       delete it->second;
     }
 
-    map_.clear();
+    content_.clear();
   }
 
 
-  void DicomMap::ExtractTags(DicomMap& result,
-                             const DicomTag* tags,
-                             size_t count) const
+  static void ExtractTags(DicomMap& result,
+                          const DicomMap::Content& source,
+                          const MainDicomTag* tags,
+                          size_t count)
   {
     result.Clear();
 
     for (unsigned int i = 0; i < count; i++)
     {
-      Map::const_iterator it = map_.find(tags[i]);
-      if (it != map_.end())
+      DicomMap::Content::const_iterator it = source.find(tags[i].tag_);
+      if (it != source.end())
       {
-        result.SetValue(it->first, it->second->Clone());
+        result.SetValue(it->first, *it->second /* value will be cloned */);
       }
     }
   }
@@ -212,33 +279,33 @@
 
   void DicomMap::ExtractPatientInformation(DicomMap& result) const
   {
-    ExtractTags(result, patientTags, sizeof(patientTags) / sizeof(DicomTag));
+    ExtractTags(result, content_, PATIENT_MAIN_DICOM_TAGS, sizeof(PATIENT_MAIN_DICOM_TAGS) / sizeof(MainDicomTag));
   }
 
   void DicomMap::ExtractStudyInformation(DicomMap& result) const
   {
-    ExtractTags(result, studyTags, sizeof(studyTags) / sizeof(DicomTag));
+    ExtractTags(result, content_, STUDY_MAIN_DICOM_TAGS, sizeof(STUDY_MAIN_DICOM_TAGS) / sizeof(MainDicomTag));
   }
 
   void DicomMap::ExtractSeriesInformation(DicomMap& result) const
   {
-    ExtractTags(result, seriesTags, sizeof(seriesTags) / sizeof(DicomTag));
+    ExtractTags(result, content_, SERIES_MAIN_DICOM_TAGS, sizeof(SERIES_MAIN_DICOM_TAGS) / sizeof(MainDicomTag));
   }
 
   void DicomMap::ExtractInstanceInformation(DicomMap& result) const
   {
-    ExtractTags(result, instanceTags, sizeof(instanceTags) / sizeof(DicomTag));
+    ExtractTags(result, content_, INSTANCE_MAIN_DICOM_TAGS, sizeof(INSTANCE_MAIN_DICOM_TAGS) / sizeof(MainDicomTag));
   }
 
 
 
   DicomMap* DicomMap::Clone() const
   {
-    std::auto_ptr<DicomMap> result(new DicomMap);
+    std::unique_ptr<DicomMap> result(new DicomMap);
 
-    for (Map::const_iterator it = map_.begin(); it != map_.end(); ++it)
+    for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
     {
-      result->map_.insert(std::make_pair(it->first, it->second->Clone()));
+      result->content_.insert(std::make_pair(it->first, it->second->Clone()));
     }
 
     return result.release();
@@ -249,9 +316,9 @@
   {
     Clear();
 
-    for (Map::const_iterator it = other.map_.begin(); it != other.map_.end(); ++it)
+    for (Content::const_iterator it = other.content_.begin(); it != other.content_.end(); ++it)
     {
-      map_.insert(std::make_pair(it->first, it->second->Clone()));
+      content_.insert(std::make_pair(it->first, it->second->Clone()));
     }
   }
 
@@ -273,9 +340,9 @@
 
   const DicomValue* DicomMap::TestAndGetValue(const DicomTag& tag) const
   {
-    Map::const_iterator it = map_.find(tag);
+    Content::const_iterator it = content_.find(tag);
 
-    if (it == map_.end())
+    if (it == content_.end())
     {
       return NULL;
     }
@@ -288,35 +355,35 @@
 
   void DicomMap::Remove(const DicomTag& tag) 
   {
-    Map::iterator it = map_.find(tag);
-    if (it != map_.end())
+    Content::iterator it = content_.find(tag);
+    if (it != content_.end())
     {
       delete it->second;
-      map_.erase(it);
+      content_.erase(it);
     }
   }
 
 
   static void SetupFindTemplate(DicomMap& result,
-                                const DicomTag* tags,
+                                const MainDicomTag* tags,
                                 size_t count) 
   {
     result.Clear();
 
     for (size_t i = 0; i < count; i++)
     {
-      result.SetValue(tags[i], "", false);
+      result.SetValue(tags[i].tag_, "", false);
     }
   }
 
   void DicomMap::SetupFindPatientTemplate(DicomMap& result)
   {
-    SetupFindTemplate(result, patientTags, sizeof(patientTags) / sizeof(DicomTag));
+    SetupFindTemplate(result, PATIENT_MAIN_DICOM_TAGS, sizeof(PATIENT_MAIN_DICOM_TAGS) / sizeof(MainDicomTag));
   }
 
   void DicomMap::SetupFindStudyTemplate(DicomMap& result)
   {
-    SetupFindTemplate(result, studyTags, sizeof(studyTags) / sizeof(DicomTag));
+    SetupFindTemplate(result, STUDY_MAIN_DICOM_TAGS, sizeof(STUDY_MAIN_DICOM_TAGS) / sizeof(MainDicomTag));
     result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false);
     result.SetValue(DICOM_TAG_PATIENT_ID, "", false);
 
@@ -329,7 +396,7 @@
 
   void DicomMap::SetupFindSeriesTemplate(DicomMap& result)
   {
-    SetupFindTemplate(result, seriesTags, sizeof(seriesTags) / sizeof(DicomTag));
+    SetupFindTemplate(result, SERIES_MAIN_DICOM_TAGS, sizeof(SERIES_MAIN_DICOM_TAGS) / sizeof(MainDicomTag));
     result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false);
     result.SetValue(DICOM_TAG_PATIENT_ID, "", false);
     result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false);
@@ -351,7 +418,7 @@
 
   void DicomMap::SetupFindInstanceTemplate(DicomMap& result)
   {
-    SetupFindTemplate(result, instanceTags, sizeof(instanceTags) / sizeof(DicomTag));
+    SetupFindTemplate(result, INSTANCE_MAIN_DICOM_TAGS, sizeof(INSTANCE_MAIN_DICOM_TAGS) / sizeof(MainDicomTag));
     result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false);
     result.SetValue(DICOM_TAG_PATIENT_ID, "", false);
     result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false);
@@ -371,38 +438,13 @@
 
   bool DicomMap::IsMainDicomTag(const DicomTag& tag, ResourceType level)
   {
-    DicomTag *tags = NULL;
+    const MainDicomTag *tags = NULL;
     size_t size;
-
-    switch (level)
-    {
-      case ResourceType_Patient:
-        tags = patientTags;
-        size = sizeof(patientTags) / sizeof(DicomTag);
-        break;
-
-      case ResourceType_Study:
-        tags = studyTags;
-        size = sizeof(studyTags) / sizeof(DicomTag);
-        break;
-
-      case ResourceType_Series:
-        tags = seriesTags;
-        size = sizeof(seriesTags) / sizeof(DicomTag);
-        break;
-
-      case ResourceType_Instance:
-        tags = instanceTags;
-        size = sizeof(instanceTags) / sizeof(DicomTag);
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
+    LoadMainDicomTags(tags, size, level);
 
     for (size_t i = 0; i < size; i++)
     {
-      if (tags[i] == tag)
+      if (tags[i].tag_ == tag)
       {
         return true;
       }
@@ -422,38 +464,13 @@
 
   void DicomMap::GetMainDicomTagsInternal(std::set<DicomTag>& result, ResourceType level)
   {
-    DicomTag *tags = NULL;
+    const MainDicomTag *tags = NULL;
     size_t size;
-
-    switch (level)
-    {
-      case ResourceType_Patient:
-        tags = patientTags;
-        size = sizeof(patientTags) / sizeof(DicomTag);
-        break;
-
-      case ResourceType_Study:
-        tags = studyTags;
-        size = sizeof(studyTags) / sizeof(DicomTag);
-        break;
-
-      case ResourceType_Series:
-        tags = seriesTags;
-        size = sizeof(seriesTags) / sizeof(DicomTag);
-        break;
-
-      case ResourceType_Instance:
-        tags = instanceTags;
-        size = sizeof(instanceTags) / sizeof(DicomTag);
-        break;
-
-      default:
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
+    LoadMainDicomTags(tags, size, level);
 
     for (size_t i = 0; i < size; i++)
     {
-      result.insert(tags[i]);
+      result.insert(tags[i].tag_);
     }
   }
 
@@ -479,8 +496,8 @@
   {
     tags.clear();
 
-    for (Map::const_iterator it = map_.begin();
-         it != map_.end(); ++it)
+    for (Content::const_iterator it = content_.begin();
+         it != content_.end(); ++it)
     {
       tags.insert(it->first);
     }
@@ -728,9 +745,8 @@
   }
 
 
-  bool DicomMap::ParseDicomMetaInformation(DicomMap& result,
-                                           const char* dicom,
-                                           size_t size)
+  bool DicomMap::IsDicomFile(const char* dicom,
+                             size_t size)
   {
     /**
      * http://dicom.nema.org/medical/dicom/current/output/chtml/part10/chapter_7.html
@@ -739,11 +755,19 @@
      * account to determine whether the file is or is not a DICOM file.
      **/
 
-    if (size < 132 ||
-        dicom[128] != 'D' ||
-        dicom[129] != 'I' ||
-        dicom[130] != 'C' ||
-        dicom[131] != 'M')
+    return (size >= 132 &&
+            dicom[128] == 'D' &&
+            dicom[129] == 'I' &&
+            dicom[130] == 'C' &&
+            dicom[131] == 'M');
+  }
+    
+
+  bool DicomMap::ParseDicomMetaInformation(DicomMap& result,
+                                           const char* dicom,
+                                           size_t size)
+  {
+    if (!IsDicomFile(dicom, size))
     {
       return false;
     }
@@ -968,6 +992,21 @@
     }
   }
 
+  bool DicomMap::ParseFirstFloat(float& result,
+                                 const DicomTag& tag) const
+  {
+    const DicomValue* value = TestAndGetValue(tag);
+
+    if (value == NULL)
+    {
+      return false;
+    }
+    else
+    {
+      return value->ParseFirstFloat(result);
+    }
+  }
+
   bool DicomMap::ParseDouble(double& result,
                              const DicomTag& tag) const
   {
@@ -1030,23 +1069,23 @@
 
   void DicomMap::Merge(const DicomMap& other)
   {
-    for (Map::const_iterator it = other.map_.begin();
-         it != other.map_.end(); ++it)
+    for (Content::const_iterator it = other.content_.begin();
+         it != other.content_.end(); ++it)
     {
       assert(it->second != NULL);
 
-      if (map_.find(it->first) == map_.end())
+      if (content_.find(it->first) == content_.end())
       {
-        map_[it->first] = it->second->Clone();
+        content_[it->first] = it->second->Clone();
       }
     }
   }
 
 
-  void DicomMap::ExtractMainDicomTagsInternal(const DicomMap& other,
-                                              ResourceType level)
+  void DicomMap::MergeMainDicomTags(const DicomMap& other,
+                                    ResourceType level)
   {
-    const DicomTag* tags = NULL;
+    const MainDicomTag* tags = NULL;
     size_t size = 0;
 
     LoadMainDicomTags(tags, size, level);
@@ -1054,13 +1093,13 @@
 
     for (size_t i = 0; i < size; i++)
     {
-      Map::const_iterator found = other.map_.find(tags[i]);
+      Content::const_iterator found = other.content_.find(tags[i].tag_);
 
-      if (found != other.map_.end() &&
-          map_.find(tags[i]) == map_.end())
+      if (found != other.content_.end() &&
+          content_.find(tags[i].tag_) == content_.end())
       {
         assert(found->second != NULL);
-        map_[tags[i]] = found->second->Clone();
+        content_[tags[i].tag_] = found->second->Clone();
       }
     }
   }
@@ -1069,10 +1108,10 @@
   void DicomMap::ExtractMainDicomTags(const DicomMap& other)
   {
     Clear();
-    ExtractMainDicomTagsInternal(other, ResourceType_Patient);
-    ExtractMainDicomTagsInternal(other, ResourceType_Study);
-    ExtractMainDicomTagsInternal(other, ResourceType_Series);
-    ExtractMainDicomTagsInternal(other, ResourceType_Instance);
+    MergeMainDicomTags(other, ResourceType_Patient);
+    MergeMainDicomTags(other, ResourceType_Study);
+    MergeMainDicomTags(other, ResourceType_Series);
+    MergeMainDicomTags(other, ResourceType_Instance);
   }    
 
 
@@ -1083,7 +1122,7 @@
     std::set<DicomTag> mainDicomTags;
     GetMainDicomTags(mainDicomTags);
 
-    for (Map::const_iterator it = map_.begin(); it != map_.end(); ++it)
+    for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
     {
       if (mainDicomTags.find(it->first) == mainDicomTags.end())
       {
@@ -1099,7 +1138,7 @@
   {
     target = Json::objectValue;
 
-    for (Map::const_iterator it = map_.begin(); it != map_.end(); ++it)
+    for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
     {
       assert(it->second != NULL);
       
@@ -1129,15 +1168,15 @@
       DicomTag tag(0, 0);
       
       if (!DicomTag::ParseHexadecimal(tag, tags[i].c_str()) ||
-          map_.find(tag) != map_.end())
+          content_.find(tag) != content_.end())
       {
         throw OrthancException(ErrorCode_BadFileFormat);
       }
 
-      std::auto_ptr<DicomValue> value(new DicomValue);
+      std::unique_ptr<DicomValue> value(new DicomValue);
       value->Unserialize(source[tags[i]]);
 
-      map_[tag] = value.release();
+      content_[tag] = value.release();
     }
   }
 
@@ -1294,6 +1333,87 @@
   }
 
 
+  void DicomMap::RemoveBinaryTags()
+  {
+    Content kept;
+
+    for (Content::iterator it = content_.begin(); it != content_.end(); ++it)
+    {
+      assert(it->second != NULL);
+
+      if (!it->second->IsBinary() &&
+          !it->second->IsNull())
+      {
+        kept[it->first] = it->second;
+      }
+      else
+      {
+        delete it->second;
+      }
+    }
+
+    content_ = kept;
+  }
+
+
+  void DicomMap::DumpMainDicomTags(Json::Value& target,
+                                   ResourceType level) const
+  {
+    std::map<DicomTag, std::string> mainTags;   // TODO - Create a singleton to hold this map
+    LoadMainDicomTags(mainTags, level);
+    
+    target = Json::objectValue;
+
+    for (Content::const_iterator it = content_.begin(); it != content_.end(); ++it)
+    {
+      assert(it->second != NULL);
+      
+      if (!it->second->IsBinary() &&
+          !it->second->IsNull())
+      {
+        std::map<DicomTag, std::string>::const_iterator found = mainTags.find(it->first);
+
+        if (found != mainTags.end())
+        {
+          target[found->second] = it->second->GetContent();
+        }
+      }
+    }    
+  }
+  
+
+  void DicomMap::ParseMainDicomTags(const Json::Value& source,
+                                    ResourceType level)
+  {
+    if (source.type() != Json::objectValue)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+    
+    std::map<std::string, DicomTag2> mainTags;   // TODO - Create a singleton to hold this map
+    LoadMainDicomTags(mainTags, level);
+    
+    Json::Value::Members members = source.getMemberNames();
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      std::map<std::string, DicomTag2>::const_iterator found = mainTags.find(members[i]);
+
+      if (found != mainTags.end())
+      {
+        const Json::Value& value = source[members[i]];
+        if (value.type() != Json::stringValue)
+        {
+          throw OrthancException(ErrorCode_BadFileFormat);
+        }
+        else
+        {
+          SetValue(found->second, value.asString(), false);
+        }
+      }
+    }
+  }
+
+
   void DicomMap::Print(FILE* fp) const
   {
     DicomArray a(*this);
--- a/Core/DicomFormat/DicomMap.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomFormat/DicomMap.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -45,31 +45,23 @@
 {
   class DicomMap : public boost::noncopyable
   {
+  public:
+    typedef std::map<DicomTag, DicomValue*>  Content;
+    
   private:
     friend class DicomArray;
     friend class FromDcmtkBridge;
     friend class ParsedDicomFile;
 
-    typedef std::map<DicomTag, DicomValue*>  Map;
-
-    Map map_;
+    Content content_;
 
     // Warning: This takes the ownership of "value"
-    void SetValue(uint16_t group, 
-                  uint16_t element, 
-                  DicomValue* value);
-
-    void SetValue(DicomTag tag,
-                  DicomValue* value);
+    void SetValueInternal(uint16_t group, 
+                          uint16_t element, 
+                          DicomValue* value);
 
-    void ExtractTags(DicomMap& source,
-                     const DicomTag* tags,
-                     size_t count) const;
-   
-    static void GetMainDicomTagsInternal(std::set<DicomTag>& result, ResourceType level);
-
-    void ExtractMainDicomTagsInternal(const DicomMap& other,
-                                      ResourceType level);
+    static void GetMainDicomTagsInternal(std::set<DicomTag>& result,
+                                         ResourceType level);
 
   public:
     DicomMap()
@@ -83,7 +75,7 @@
 
     size_t GetSize() const
     {
-      return map_.size();
+      return content_.size();
     }
     
     DicomMap* Clone() const;
@@ -95,32 +87,32 @@
     void SetNullValue(uint16_t group, 
                       uint16_t element)
     {
-      SetValue(group, element, new DicomValue);
+      SetValueInternal(group, element, new DicomValue);
     }
     
     void SetNullValue(const DicomTag& tag)
     {
-      SetValue(tag, new DicomValue);
+      SetValueInternal(tag.GetGroup(), tag.GetElement(), new DicomValue);
     }
     
     void SetValue(uint16_t group, 
                   uint16_t element, 
                   const DicomValue& value)
     {
-      SetValue(group, element, value.Clone());
+      SetValueInternal(group, element, value.Clone());
     }
 
     void SetValue(const DicomTag& tag,
                   const DicomValue& value)
     {
-      SetValue(tag, value.Clone());
+      SetValueInternal(tag.GetGroup(), tag.GetElement(), value.Clone());
     }
 
     void SetValue(const DicomTag& tag,
                   const std::string& str,
                   bool isBinary)
     {
-      SetValue(tag, new DicomValue(str, isBinary));
+      SetValueInternal(tag.GetGroup(), tag.GetElement(), new DicomValue(str, isBinary));
     }
 
     void SetValue(uint16_t group, 
@@ -128,7 +120,7 @@
                   const std::string& str,
                   bool isBinary)
     {
-      SetValue(group, element, new DicomValue(str, isBinary));
+      SetValueInternal(group, element, new DicomValue(str, isBinary));
     }
 
     bool HasTag(uint16_t group, uint16_t element) const
@@ -138,7 +130,7 @@
 
     bool HasTag(const DicomTag& tag) const
     {
-      return map_.find(tag) != map_.end();
+      return content_.find(tag) != content_.end();
     }
 
     const DicomValue& GetValue(uint16_t group, uint16_t element) const
@@ -188,10 +180,9 @@
 
     void GetTags(std::set<DicomTag>& tags) const;
 
-    static void LoadMainDicomTags(const DicomTag*& tags,
-                                  size_t& size,
-                                  ResourceType level);
-
+    static bool IsDicomFile(const char* dicom,
+                            size_t size);
+    
     static bool ParseDicomMetaInformation(DicomMap& result,
                                           const char* dicom,
                                           size_t size);
@@ -217,6 +208,9 @@
     bool ParseFloat(float& result,
                     const DicomTag& tag) const;
 
+    bool ParseFirstFloat(float& result,
+                         const DicomTag& tag) const;
+
     bool ParseDouble(double& result,
                      const DicomTag& tag) const;
 
@@ -224,6 +218,9 @@
 
     void Merge(const DicomMap& other);
 
+    void MergeMainDicomTags(const DicomMap& other,
+                            ResourceType level);
+
     void ExtractMainDicomTags(const DicomMap& other);
 
     bool HasOnlyMainDicomTags() const;
@@ -238,6 +235,14 @@
                                const std::string& defaultValue,
                                bool allowBinary) const;
 
+    void RemoveBinaryTags();
+
+    void DumpMainDicomTags(Json::Value& target,
+                           ResourceType level) const;
+
+    void ParseMainDicomTags(const Json::Value& source,
+                            ResourceType level);
+
     void Print(FILE* fp) const;  // For debugging only
   };
 }
--- a/Core/DicomFormat/DicomTag.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomFormat/DicomTag.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -187,6 +187,10 @@
     if (*this == DICOM_TAG_IMAGE_ORIENTATION_PATIENT)
       return "ImageOrientationPatient";
 
+    // New in Orthanc 1.6.0, as tagged as "RETIRED_" since DCMTK 3.6.4
+    if (*this == DICOM_TAG_OTHER_PATIENT_IDS)
+      return "OtherPatientIDs";
+
     return "";
   }
 
--- a/Core/DicomFormat/DicomTag.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomFormat/DicomTag.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -149,6 +149,8 @@
   static const DicomTag DICOM_TAG_PHOTOMETRIC_INTERPRETATION(0x0028, 0x0004);
   static const DicomTag DICOM_TAG_IMAGE_ORIENTATION_PATIENT(0x0020, 0x0037);
   static const DicomTag DICOM_TAG_IMAGE_POSITION_PATIENT(0x0020, 0x0032);
+  static const DicomTag DICOM_TAG_LARGEST_IMAGE_PIXEL_VALUE(0x0028, 0x0107);
+  static const DicomTag DICOM_TAG_SMALLEST_IMAGE_PIXEL_VALUE(0x0028, 0x0106);
 
   // Tags related to date and time
   static const DicomTag DICOM_TAG_ACQUISITION_DATE(0x0008, 0x0022);
@@ -189,6 +191,7 @@
   static const DicomTag DICOM_TAG_PATIENT_COMMENTS(0x0010, 0x4000);
   static const DicomTag DICOM_TAG_PATIENT_SPECIES_DESCRIPTION(0x0010, 0x2201);
   static const DicomTag DICOM_TAG_STUDY_COMMENTS(0x0032, 0x4000);
+  static const DicomTag DICOM_TAG_OTHER_PATIENT_IDS(0x0010, 0x1000);
 
   // Tags used within the Stone of Orthanc
   static const DicomTag DICOM_TAG_FRAME_INCREMENT_POINTER(0x0028, 0x0009);
@@ -206,6 +209,7 @@
   static const DicomTag DICOM_TAG_RED_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR(0x0028, 0x1101);
   static const DicomTag DICOM_TAG_GREEN_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR(0x0028, 0x1102);
   static const DicomTag DICOM_TAG_BLUE_PALETTE_COLOR_LOOKUP_TABLE_DESCRIPTOR(0x0028, 0x1103);
+  static const DicomTag DICOM_TAG_CONTOUR_DATA(0x3006, 0x0050);
                              
   // Counting patients, studies and series
   // https://www.medicalconnections.co.uk/kb/Counting_Studies_Series_and_Instances
@@ -229,4 +233,11 @@
   static const DicomTag DICOM_TAG_REFERENCED_FRAME_OF_REFERENCE_SEQUENCE(0x3006, 0x0010);
   static const DicomTag DICOM_TAG_RT_REFERENCED_STUDY_SEQUENCE(0x3006, 0x0012);
   static const DicomTag DICOM_TAG_RT_REFERENCED_SERIES_SEQUENCE(0x3006, 0x0014);
+
+  // Tags for DICOMDIR
+  static const DicomTag DICOM_TAG_DIRECTORY_RECORD_TYPE(0x0004, 0x1430);
+  static const DicomTag DICOM_TAG_OFFSET_OF_THE_NEXT_DIRECTORY_RECORD(0x0004, 0x1400);
+  static const DicomTag DICOM_TAG_OFFSET_OF_REFERENCED_LOWER_LEVEL_DIRECTORY_ENTITY(0x0004, 0x1420);
+  static const DicomTag DICOM_TAG_REFERENCED_SOP_INSTANCE_UID_IN_FILE(0x0004, 0x1511);
+  static const DicomTag DICOM_TAG_REFERENCED_FILE_ID(0x0004, 0x1500);
 }
--- a/Core/DicomFormat/DicomValue.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomFormat/DicomValue.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -231,6 +231,11 @@
     return ParseValue<double, true>(result, *this);
   }
 
+  bool DicomValue::ParseFirstFloat(float& result) const
+  {
+    return ParseFirstValue<float, true>(result, *this);
+  }
+
   bool DicomValue::ParseFirstUnsignedInteger(unsigned int& result) const
   {
     return ParseFirstValue<unsigned int, true>(result, *this);
--- a/Core/DicomFormat/DicomValue.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomFormat/DicomValue.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -112,6 +112,8 @@
 
     bool ParseDouble(double& result) const;
 
+    bool ParseFirstFloat(float& result) const;
+
     bool ParseFirstUnsignedInteger(unsigned int& result) const;
 
     void Serialize(Json::Value& target) const;
--- a/Core/DicomNetworking/DicomFindAnswers.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/DicomFindAnswers.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -46,7 +46,7 @@
 {
   void DicomFindAnswers::AddAnswerInternal(ParsedDicomFile* answer)
   {
-    std::auto_ptr<ParsedDicomFile> protection(answer);
+    std::unique_ptr<ParsedDicomFile> protection(answer);
 
     if (isWorklist_)
     {
@@ -166,7 +166,7 @@
 
     DcmDataset& source = *GetAnswer(index).GetDcmtkObject().getDataset();
 
-    std::auto_ptr<DcmDataset> target(new DcmDataset);
+    std::unique_ptr<DcmDataset> target(new DcmDataset);
 
     for (unsigned long i = 0; i < source.card(); i++)
     {
--- a/Core/DicomNetworking/DicomFindAnswers.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/DicomFindAnswers.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomNetworking/DicomServer.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/DicomServer.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -53,7 +53,7 @@
   {
     boost::thread  thread_;
     T_ASC_Network *network_;
-    std::auto_ptr<RunnableWorkersPool>  workers_;
+    std::unique_ptr<RunnableWorkersPool>  workers_;
   };
 
 
@@ -65,7 +65,7 @@
     {
       /* receive an association and acknowledge or reject it. If the association was */
       /* acknowledged, offer corresponding services and invoke one or more if required. */
-      std::auto_ptr<Internals::CommandDispatcher> dispatcher(Internals::AcceptAssociation(*server, server->pimpl_->network_));
+      std::unique_ptr<Internals::CommandDispatcher> dispatcher(Internals::AcceptAssociation(*server, server->pimpl_->network_));
 
       try
       {
@@ -94,6 +94,7 @@
     moveRequestHandlerFactory_ = NULL;
     storeRequestHandlerFactory_ = NULL;
     worklistRequestHandlerFactory_ = NULL;
+    storageCommitmentFactory_ = NULL;
     applicationEntityFilter_ = NULL;
     checkCalledAet_ = true;
     associationTimeout_ = 30;
@@ -289,6 +290,29 @@
     }
   }
 
+  void DicomServer::SetStorageCommitmentRequestHandlerFactory(IStorageCommitmentRequestHandlerFactory& factory)
+  {
+    Stop();
+    storageCommitmentFactory_ = &factory;
+  }
+
+  bool DicomServer::HasStorageCommitmentRequestHandlerFactory() const
+  {
+    return (storageCommitmentFactory_ != NULL);
+  }
+
+  IStorageCommitmentRequestHandlerFactory& DicomServer::GetStorageCommitmentRequestHandlerFactory() const
+  {
+    if (HasStorageCommitmentRequestHandlerFactory())
+    {
+      return *storageCommitmentFactory_;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_NoStorageCommitmentHandler);
+    }
+  }
+
   void DicomServer::SetApplicationEntityFilter(IApplicationEntityFilter& factory)
   {
     Stop();
@@ -378,5 +402,4 @@
       return modalities_->IsSameAETitle(aet, GetApplicationEntityTitle());
     }
   }
-
 }
--- a/Core/DicomNetworking/DicomServer.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/DicomServer.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -41,6 +41,7 @@
 #include "IMoveRequestHandlerFactory.h"
 #include "IStoreRequestHandlerFactory.h"
 #include "IWorklistRequestHandlerFactory.h"
+#include "IStorageCommitmentRequestHandlerFactory.h"
 #include "IApplicationEntityFilter.h"
 #include "RemoteModalityParameters.h"
 
@@ -82,6 +83,7 @@
     IMoveRequestHandlerFactory* moveRequestHandlerFactory_;
     IStoreRequestHandlerFactory* storeRequestHandlerFactory_;
     IWorklistRequestHandlerFactory* worklistRequestHandlerFactory_;
+    IStorageCommitmentRequestHandlerFactory* storageCommitmentFactory_;
     IApplicationEntityFilter* applicationEntityFilter_;
 
     static void ServerThread(DicomServer* server);
@@ -122,6 +124,10 @@
     bool HasWorklistRequestHandlerFactory() const;
     IWorklistRequestHandlerFactory& GetWorklistRequestHandlerFactory() const;
 
+    void SetStorageCommitmentRequestHandlerFactory(IStorageCommitmentRequestHandlerFactory& handler);
+    bool HasStorageCommitmentRequestHandlerFactory() const;
+    IStorageCommitmentRequestHandlerFactory& GetStorageCommitmentRequestHandlerFactory() const;
+
     void SetApplicationEntityFilter(IApplicationEntityFilter& handler);
     bool HasApplicationEntityFilter() const;
     IApplicationEntityFilter& GetApplicationEntityFilter() const;
--- a/Core/DicomNetworking/DicomUserConnection.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/DicomUserConnection.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -86,6 +86,7 @@
 #  error The macro DCMTK_VERSION_NUMBER must be defined
 #endif
 
+#include "../Compatibility.h"
 #include "../DicomFormat/DicomArray.h"
 #include "../Logging.h"
 #include "../OrthancException.h"
@@ -158,7 +159,9 @@
 
     void CheckIsOpen() const;
 
-    void Store(DcmInputStream& is, 
+    void Store(std::string& sopClassUidOut  /* out */,
+               std::string& sopInstanceUidOut  /* out */,
+               DcmInputStream& is, 
                DicomUserConnection& connection,
                const std::string& moveOriginatorAET,
                uint16_t moveOriginatorID);
@@ -252,7 +255,8 @@
   }
   
     
-  void DicomUserConnection::SetupPresentationContexts(const std::string& preferredTransferSyntax)
+  void DicomUserConnection::SetupPresentationContexts(Mode mode,
+                                                      const std::string& preferredTransferSyntax)
   {
     // Flatten an array with the preferred transfer syntax
     const char* asPreferred[1] = { preferredTransferSyntax.c_str() };
@@ -274,30 +278,73 @@
     }
 
     CheckStorageSOPClassesInvariant();
-    unsigned int presentationContextId = 1;
+
+    switch (mode)
+    {
+      case Mode_Generic:
+      {
+        unsigned int presentationContextId = 1;
+
+        for (std::list<std::string>::const_iterator it = reservedStorageSOPClasses_.begin();
+             it != reservedStorageSOPClasses_.end(); ++it)
+        {
+          RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
+                                  *it, asPreferred, asFallback, remoteAet_);
+        }
 
-    for (std::list<std::string>::const_iterator it = reservedStorageSOPClasses_.begin();
-         it != reservedStorageSOPClasses_.end(); ++it)
-    {
-      RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
-                              *it, asPreferred, asFallback, remoteAet_);
-    }
+        for (std::set<std::string>::const_iterator it = storageSOPClasses_.begin();
+             it != storageSOPClasses_.end(); ++it)
+        {
+          RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
+                                  *it, asPreferred, asFallback, remoteAet_);
+        }
+
+        for (std::set<std::string>::const_iterator it = defaultStorageSOPClasses_.begin();
+             it != defaultStorageSOPClasses_.end(); ++it)
+        {
+          RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
+                                  *it, asPreferred, asFallback, remoteAet_);
+        }
+
+        break;
+      }
 
-    for (std::set<std::string>::const_iterator it = storageSOPClasses_.begin();
-         it != storageSOPClasses_.end(); ++it)
-    {
-      RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
-                              *it, asPreferred, asFallback, remoteAet_);
-    }
+      case Mode_RequestStorageCommitment:
+      case Mode_ReportStorageCommitment:
+      {
+        const char* as = UID_StorageCommitmentPushModelSOPClass;
+
+        std::vector<const char*> ts;
+        ts.push_back(UID_LittleEndianExplicitTransferSyntax);
+        ts.push_back(UID_LittleEndianImplicitTransferSyntax);
 
-    for (std::set<std::string>::const_iterator it = defaultStorageSOPClasses_.begin();
-         it != defaultStorageSOPClasses_.end(); ++it)
-    {
-      RegisterStorageSOPClass(pimpl_->params_, presentationContextId, 
-                              *it, asPreferred, asFallback, remoteAet_);
+        T_ASC_SC_ROLE role;
+        switch (mode)
+        {
+          case Mode_RequestStorageCommitment:
+            role = ASC_SC_ROLE_DEFAULT;
+            break;
+            
+          case Mode_ReportStorageCommitment:
+            role = ASC_SC_ROLE_SCP;
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_InternalError);
+        }
+        
+        Check(ASC_addPresentationContext(pimpl_->params_, 1 /*presentationContextId*/,
+                                         as, &ts[0], ts.size(), role),
+              remoteAet_, "initializing");
+              
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
     }
   }
-
+  
 
   static bool IsGenericTransferSyntax(const std::string& syntax)
   {
@@ -307,7 +354,9 @@
   }
 
 
-  void DicomUserConnection::PImpl::Store(DcmInputStream& is, 
+  void DicomUserConnection::PImpl::Store(std::string& sopClassUidOut,
+                                         std::string& sopInstanceUidOut,
+                                         DcmInputStream& is, 
                                          DicomUserConnection& connection,
                                          const std::string& moveOriginatorAET,
                                          uint16_t moveOriginatorID)
@@ -390,6 +439,9 @@
                              connection.remoteAet_);
     }
 
+    sopClassUidOut.assign(sopClass);
+    sopInstanceUidOut.assign(sopInstance);
+
     // Figure out which of the accepted presentation contexts should be used
     int presID = ASC_findAcceptedPresentationContextID(assoc_, sopClass);
     if (presID == 0)
@@ -422,18 +474,37 @@
     }
 
     // Finally conduct transmission of data
-    T_DIMSE_C_StoreRSP rsp;
+    T_DIMSE_C_StoreRSP response;
     DcmDataset* statusDetail = NULL;
     Check(DIMSE_storeUser(assoc_, presID, &request,
                           NULL, dcmff.getDataset(), /*progressCallback*/ NULL, NULL,
-                          /*opt_blockMode*/ DIMSE_BLOCKING, /*opt_dimse_timeout*/ dimseTimeout_,
-                          &rsp, &statusDetail, NULL),
+                          /*opt_blockMode*/ (dimseTimeout_ ? DIMSE_NONBLOCKING : DIMSE_BLOCKING),
+                          /*opt_dimse_timeout*/ dimseTimeout_,
+                          &response, &statusDetail, NULL),
           connection.remoteAet_, "C-STORE");
 
     if (statusDetail != NULL) 
     {
       delete statusDetail;
     }
+    
+    
+    /**
+     * New in Orthanc 1.6.0: Deal with failures during C-STORE.
+     * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_B.2.3.html#table_B.2-1
+     **/
+    
+    if (response.DimseStatus != 0x0000 &&  // Success
+        response.DimseStatus != 0xB000 &&  // Warning - Coercion of Data Elements
+        response.DimseStatus != 0xB007 &&  // Warning - Data Set does not match SOP Class
+        response.DimseStatus != 0xB006)    // Warning - Elements Discarded
+    {
+      char buf[16];
+      sprintf(buf, "%04X", response.DimseStatus);
+      throw OrthancException(ErrorCode_NetworkProtocol,
+                             "C-STORE SCU to AET \"" + connection.remoteAet_ +
+                             "\" has failed with DIMSE status 0x" + buf);
+    }
   }
 
 
@@ -565,7 +636,7 @@
       case ModalityManufacturer_GenericNoWildcardInDates:
       case ModalityManufacturer_GenericNoUniversalWildcard:
       {
-        std::auto_ptr<DicomMap> fix(fields.Clone());
+        std::unique_ptr<DicomMap> fix(fields.Clone());
 
         std::set<DicomTag> tags;
         fix->GetTags(tags);
@@ -642,7 +713,7 @@
 				      responseCount,
 #endif
                                       FindCallback, &payload,
-                                      /*opt_blockMode*/ DIMSE_BLOCKING, 
+                                      /*opt_blockMode*/ (dimseTimeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING),
                                       /*opt_dimse_timeout*/ dimseTimeout,
                                       &response, &statusDetail);
 
@@ -652,6 +723,24 @@
     }
 
     Check(cond, remoteAet, "C-FIND");
+
+    
+    /**
+     * New in Orthanc 1.6.0: Deal with failures during C-FIND.
+     * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.html#table_C.4-1
+     **/
+    
+    if (response.DimseStatus != 0x0000 &&  // Success
+        response.DimseStatus != 0xFF00 &&  // Pending - Matches are continuing 
+        response.DimseStatus != 0xFF01)    // Pending - Matches are continuing 
+    {
+      char buf[16];
+      sprintf(buf, "%04X", response.DimseStatus);
+      throw OrthancException(ErrorCode_NetworkProtocol,
+                             "C-FIND SCU to AET \"" + remoteAet +
+                             "\" has failed with DIMSE status 0x" + buf);
+    }
+
   }
 
 
@@ -662,7 +751,7 @@
   {
     CheckIsOpen();
 
-    std::auto_ptr<ParsedDicomFile> query;
+    std::unique_ptr<ParsedDicomFile> query;
 
     if (normalize)
     {
@@ -703,22 +792,8 @@
         break;
 
       case ResourceType_Instance:
-        clevel = "INSTANCE";
-        if (manufacturer_ == ModalityManufacturer_ClearCanvas ||
-            manufacturer_ == ModalityManufacturer_Dcm4Chee ||
-            manufacturer_ == ModalityManufacturer_GE)
-        {
-          // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>.
-          // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J
-          // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx
-          DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "IMAGE");
-          clevel = "IMAGE";
-        }
-        else
-        {
-          DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "INSTANCE");
-        }
-
+        clevel = "IMAGE";
+        DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "IMAGE");
         sopClass = UID_FINDStudyRootQueryRetrieveInformationModel;
         break;
 
@@ -789,7 +864,7 @@
   {
     CheckIsOpen();
 
-    std::auto_ptr<ParsedDicomFile> query(ConvertQueryFields(fields, manufacturer_));
+    std::unique_ptr<ParsedDicomFile> query(ConvertQueryFields(fields, manufacturer_));
     DcmDataset* dataset = query->GetDcmtkObject().getDataset();
 
     const char* sopClass = UID_MOVEStudyRootQueryRetrieveInformationModel;
@@ -808,19 +883,7 @@
         break;
 
       case ResourceType_Instance:
-        if (manufacturer_ == ModalityManufacturer_ClearCanvas ||
-            manufacturer_ == ModalityManufacturer_Dcm4Chee ||
-            manufacturer_ == ModalityManufacturer_GE)
-        {
-          // This is a particular case for ClearCanvas, thanks to Peter Somlo <peter.somlo@gmail.com>.
-          // https://groups.google.com/d/msg/orthanc-users/j-6C3MAVwiw/iolB9hclom8J
-          // http://www.clearcanvas.ca/Home/Community/OldForums/tabid/526/aff/11/aft/14670/afv/topic/Default.aspx
-          DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "IMAGE");
-        }
-        else
-        {
-          DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "INSTANCE");
-        }
+        DU_putStringDOElement(dataset, DCM_QueryRetrieveLevel, "IMAGE");
         break;
 
       default:
@@ -848,7 +911,7 @@
     DcmDataset* responseIdentifiers = NULL;
     OFCondition cond = DIMSE_moveUser(pimpl_->assoc_, presID, &request, dataset,
                                       NULL, NULL,
-                                      /*opt_blockMode*/ DIMSE_BLOCKING, 
+                                      /*opt_blockMode*/ (pimpl_->dimseTimeout_ ? DIMSE_NONBLOCKING : DIMSE_BLOCKING),
                                       /*opt_dimse_timeout*/ pimpl_->dimseTimeout_,
                                       pimpl_->net_, NULL, NULL,
                                       &response, &statusDetail, &responseIdentifiers);
@@ -864,6 +927,22 @@
     }
 
     Check(cond, remoteAet_, "C-MOVE");
+
+    
+    /**
+     * New in Orthanc 1.6.0: Deal with failures during C-MOVE.
+     * http://dicom.nema.org/medical/dicom/current/output/chtml/part04/sect_C.4.2.html#table_C.4-2
+     **/
+    
+    if (response.DimseStatus != 0x0000 &&  // Success
+        response.DimseStatus != 0xFF00)    // Pending - Sub-operations are continuing
+    {
+      char buf[16];
+      sprintf(buf, "%04X", response.DimseStatus);
+      throw OrthancException(ErrorCode_NetworkProtocol,
+                             "C-MOVE SCU to AET \"" + remoteAet_ +
+                             "\" has failed with DIMSE status 0x" + buf);
+    }
   }
 
 
@@ -1023,7 +1102,7 @@
     }
   }
 
-  void DicomUserConnection::Open()
+  void DicomUserConnection::OpenInternal(Mode mode)
   {
     if (IsOpen())
     {
@@ -1063,7 +1142,7 @@
     Check(ASC_setTransportLayerType(pimpl_->params_, /*opt_secureConnection*/ false),
           remoteAet_, "connecting");
 
-    SetupPresentationContexts(preferredTransferSyntax_);
+    SetupPresentationContexts(mode, preferredTransferSyntax_);
 
     // Do the association
     Check(ASC_requestAssociation(pimpl_->net_, pimpl_->params_, &pimpl_->assoc_),
@@ -1107,7 +1186,9 @@
     return pimpl_->IsOpen();
   }
 
-  void DicomUserConnection::Store(const char* buffer, 
+  void DicomUserConnection::Store(std::string& sopClassUid /* out */,
+                                  std::string& sopInstanceUid /* out */,
+                                  const char* buffer, 
                                   size_t size,
                                   const std::string& moveOriginatorAET,
                                   uint16_t moveOriginatorID)
@@ -1118,26 +1199,31 @@
       is.setBuffer(buffer, size);
     is.setEos();
       
-    pimpl_->Store(is, *this, moveOriginatorAET, moveOriginatorID);
+    pimpl_->Store(sopClassUid, sopInstanceUid, is, *this, moveOriginatorAET, moveOriginatorID);
   }
 
-  void DicomUserConnection::Store(const std::string& buffer,
+  void DicomUserConnection::Store(std::string& sopClassUid /* out */,
+                                  std::string& sopInstanceUid /* out */,
+                                  const std::string& buffer,
                                   const std::string& moveOriginatorAET,
                                   uint16_t moveOriginatorID)
   {
     if (buffer.size() > 0)
-      Store(&buffer[0], buffer.size(), moveOriginatorAET, moveOriginatorID);
+      Store(sopClassUid, sopInstanceUid, &buffer[0], buffer.size(),
+            moveOriginatorAET, moveOriginatorID);
     else
-      Store(NULL, 0, moveOriginatorAET, moveOriginatorID);
+      Store(sopClassUid, sopInstanceUid, NULL, 0, moveOriginatorAET, moveOriginatorID);
   }
 
-  void DicomUserConnection::StoreFile(const std::string& path,
+  void DicomUserConnection::StoreFile(std::string& sopClassUid /* out */,
+                                      std::string& sopInstanceUid /* out */,
+                                      const std::string& path,
                                       const std::string& moveOriginatorAET,
                                       uint16_t moveOriginatorID)
   {
     // Prepare an input stream for the file
     DcmInputFileStream is(path.c_str());
-    pimpl_->Store(is, *this, moveOriginatorAET, moveOriginatorID);
+    pimpl_->Store(sopClassUid, sopInstanceUid, is, *this, moveOriginatorAET, moveOriginatorID);
   }
 
   bool DicomUserConnection::Echo()
@@ -1145,7 +1231,7 @@
     CheckIsOpen();
     DIC_US status;
     Check(DIMSE_echoUser(pimpl_->assoc_, pimpl_->assoc_->nextMsgID++, 
-                         /*opt_blockMode*/ DIMSE_BLOCKING, 
+                         /*opt_blockMode*/ (pimpl_->dimseTimeout_ ? DIMSE_NONBLOCKING : DIMSE_BLOCKING),
                          /*opt_dimse_timeout*/ pimpl_->dimseTimeout_,
                          &status, NULL), remoteAet_, "C-ECHO");
     return status == STATUS_Success;
@@ -1265,7 +1351,7 @@
     {
       dcmConnectionTimeout.set(seconds);
       pimpl_->dimseTimeout_ = seconds;
-      pimpl_->acseTimeout_ = 10;  // Timeout used during association negociation
+      pimpl_->acseTimeout_ = seconds;  // Timeout used during association negociation and ASC_releaseAssociation()
     }
   }
 
@@ -1278,7 +1364,7 @@
      */
     dcmConnectionTimeout.set(-1);
     pimpl_->dimseTimeout_ = 0;
-    pimpl_->acseTimeout_ = 10;  // Timeout used during association negociation
+    pimpl_->acseTimeout_ = 10;  // Timeout used during association negociation and ASC_releaseAssociation()
   }
 
 
@@ -1368,4 +1454,369 @@
             remotePort_ == remote.GetPortNumber() &&
             manufacturer_ == remote.GetManufacturer());
   }
+
+
+  static void FillSopSequence(DcmDataset& dataset,
+                              const DcmTagKey& tag,
+                              const std::vector<std::string>& sopClassUids,
+                              const std::vector<std::string>& sopInstanceUids,
+                              const std::vector<StorageCommitmentFailureReason>& failureReasons,
+                              bool hasFailureReasons)
+  {
+    assert(sopClassUids.size() == sopInstanceUids.size() &&
+           (hasFailureReasons ?
+            failureReasons.size() == sopClassUids.size() :
+            failureReasons.empty()));
+
+    if (sopInstanceUids.empty())
+    {
+      // Add an empty sequence
+      if (!dataset.insertEmptyElement(tag).good())
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+    else
+    {
+      for (size_t i = 0; i < sopClassUids.size(); i++)
+      {
+        std::unique_ptr<DcmItem> item(new DcmItem);
+        if (!item->putAndInsertString(DCM_ReferencedSOPClassUID, sopClassUids[i].c_str()).good() ||
+            !item->putAndInsertString(DCM_ReferencedSOPInstanceUID, sopInstanceUids[i].c_str()).good() ||
+            (hasFailureReasons &&
+             !item->putAndInsertUint16(DCM_FailureReason, failureReasons[i]).good()) ||
+            !dataset.insertSequenceItem(tag, item.release()).good())
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+      }
+    }
+  }                              
+
+
+  
+
+  void DicomUserConnection::ReportStorageCommitment(
+    const std::string& transactionUid,
+    const std::vector<std::string>& sopClassUids,
+    const std::vector<std::string>& sopInstanceUids,
+    const std::vector<StorageCommitmentFailureReason>& failureReasons)
+  {
+    if (sopClassUids.size() != sopInstanceUids.size() ||
+        sopClassUids.size() != failureReasons.size())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    
+    if (IsOpen())
+    {
+      Close();
+    }
+
+    std::vector<std::string> successSopClassUids, successSopInstanceUids, failedSopClassUids, failedSopInstanceUids;
+    std::vector<StorageCommitmentFailureReason> failedReasons;
+
+    successSopClassUids.reserve(sopClassUids.size());
+    successSopInstanceUids.reserve(sopClassUids.size());
+    failedSopClassUids.reserve(sopClassUids.size());
+    failedSopInstanceUids.reserve(sopClassUids.size());
+    failedReasons.reserve(sopClassUids.size());
+
+    for (size_t i = 0; i < sopClassUids.size(); i++)
+    {
+      switch (failureReasons[i])
+      {
+        case StorageCommitmentFailureReason_Success:
+          successSopClassUids.push_back(sopClassUids[i]);
+          successSopInstanceUids.push_back(sopInstanceUids[i]);
+          break;
+
+        case StorageCommitmentFailureReason_ProcessingFailure:
+        case StorageCommitmentFailureReason_NoSuchObjectInstance:
+        case StorageCommitmentFailureReason_ResourceLimitation:
+        case StorageCommitmentFailureReason_ReferencedSOPClassNotSupported:
+        case StorageCommitmentFailureReason_ClassInstanceConflict:
+        case StorageCommitmentFailureReason_DuplicateTransactionUID:
+          failedSopClassUids.push_back(sopClassUids[i]);
+          failedSopInstanceUids.push_back(sopInstanceUids[i]);
+          failedReasons.push_back(failureReasons[i]);
+          break;
+
+        default:
+        {
+          char buf[16];
+          sprintf(buf, "%04xH", failureReasons[i]);
+          throw OrthancException(ErrorCode_ParameterOutOfRange,
+                                 "Unsupported failure reason for storage commitment: " + std::string(buf));
+        }
+      }
+    }
+    
+    try
+    {
+      OpenInternal(Mode_ReportStorageCommitment);
+
+      /**
+       * N-EVENT-REPORT
+       * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html
+       * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-1
+       *
+       * Status code:
+       * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.1.1.8
+       **/
+
+      /**
+       * Send the "EVENT_REPORT_RQ" request
+       **/
+
+      LOG(INFO) << "Reporting modality \"" << remoteAet_
+                << "\" about storage commitment transaction: " << transactionUid
+                << " (" << successSopClassUids.size() << " successes, " 
+                << failedSopClassUids.size() << " failures)";
+      const DIC_US messageId = pimpl_->assoc_->nextMsgID++;
+      
+      {
+        T_DIMSE_Message message;
+        memset(&message, 0, sizeof(message));
+        message.CommandField = DIMSE_N_EVENT_REPORT_RQ;
+
+        T_DIMSE_N_EventReportRQ& content = message.msg.NEventReportRQ;
+        content.MessageID = messageId;
+        strncpy(content.AffectedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN);
+        strncpy(content.AffectedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN);
+        content.DataSetType = DIMSE_DATASET_PRESENT;
+
+        DcmDataset dataset;
+        if (!dataset.putAndInsertString(DCM_TransactionUID, transactionUid.c_str()).good())
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+
+        {
+          std::vector<StorageCommitmentFailureReason> empty;
+          FillSopSequence(dataset, DCM_ReferencedSOPSequence, successSopClassUids,
+                          successSopInstanceUids, empty, false);
+        }
+
+        // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html
+        if (failedSopClassUids.empty())
+        {
+          content.EventTypeID = 1;  // "Storage Commitment Request Successful"
+        }
+        else
+        {
+          content.EventTypeID = 2;  // "Storage Commitment Request Complete - Failures Exist"
+
+          // Failure reason
+          // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part03/sect_C.14.html#sect_C.14.1.1
+          FillSopSequence(dataset, DCM_FailedSOPSequence, failedSopClassUids,
+                          failedSopInstanceUids, failedReasons, true);
+        }
+
+        int presID = ASC_findAcceptedPresentationContextID(
+          pimpl_->assoc_, UID_StorageCommitmentPushModelSOPClass);
+        if (presID == 0)
+        {
+          throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
+                                 "Unable to send N-EVENT-REPORT request to AET: " + remoteAet_);
+        }
+
+        if (!DIMSE_sendMessageUsingMemoryData(
+              pimpl_->assoc_, presID, &message, NULL /* status detail */,
+              &dataset, NULL /* callback */, NULL /* callback context */,
+              NULL /* commandSet */).good())
+        {
+          throw OrthancException(ErrorCode_NetworkProtocol);
+        }
+      }
+
+      /**
+       * Read the "EVENT_REPORT_RSP" response
+       **/
+
+      {
+        T_ASC_PresentationContextID presID = 0;
+        T_DIMSE_Message message;
+
+        const int timeout = pimpl_->dimseTimeout_;
+        if (!DIMSE_receiveCommand(pimpl_->assoc_,
+                                  (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), timeout,
+                                  &presID, &message, NULL /* no statusDetail */).good() ||
+            message.CommandField != DIMSE_N_EVENT_REPORT_RSP)
+        {
+          throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
+                                 "Unable to read N-EVENT-REPORT response from AET: " + remoteAet_);
+        }
+
+        const T_DIMSE_N_EventReportRSP& content = message.msg.NEventReportRSP;
+        if (content.MessageIDBeingRespondedTo != messageId ||
+            !(content.opts & O_NEVENTREPORT_AFFECTEDSOPCLASSUID) ||
+            !(content.opts & O_NEVENTREPORT_AFFECTEDSOPINSTANCEUID) ||
+            //(content.opts & O_NEVENTREPORT_EVENTTYPEID) ||  // Pedantic test - The "content.EventTypeID" is not used by Orthanc
+            std::string(content.AffectedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass ||
+            std::string(content.AffectedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance ||
+            content.DataSetType != DIMSE_DATASET_NULL)
+        {
+          throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
+                                 "Badly formatted N-EVENT-REPORT response from AET: " + remoteAet_);
+        }
+
+        if (content.DimseStatus != 0 /* success */)
+        {
+          throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
+                                 "The request cannot be handled by remote AET: " + remoteAet_);
+        }
+      }
+
+      Close();
+    }
+    catch (OrthancException&)
+    {
+      Close();
+      throw;
+    }
+  }
+
+
+  
+  void DicomUserConnection::RequestStorageCommitment(
+    const std::string& transactionUid,
+    const std::vector<std::string>& sopClassUids,
+    const std::vector<std::string>& sopInstanceUids)
+  {
+    if (sopClassUids.size() != sopInstanceUids.size())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    for (size_t i = 0; i < sopClassUids.size(); i++)
+    {
+      if (sopClassUids[i].empty() ||
+          sopInstanceUids[i].empty())
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange,
+                               "The SOP class/instance UIDs cannot be empty, found: \"" +
+                               sopClassUids[i] + "\" / \"" + sopInstanceUids[i] + "\"");
+      }
+    }
+
+    if (transactionUid.size() < 5 ||
+        transactionUid.substr(0, 5) != "2.25.")
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    if (IsOpen())
+    {
+      Close();
+    }
+
+    try
+    {
+      OpenInternal(Mode_RequestStorageCommitment);
+
+      /**
+       * N-ACTION
+       * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.2.html
+       * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-4
+       *
+       * Status code:
+       * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.1.1.8
+       **/
+
+      /**
+       * Send the "N_ACTION_RQ" request
+       **/
+
+      LOG(INFO) << "Request to modality \"" << remoteAet_
+                << "\" about storage commitment for " << sopClassUids.size()
+                << " instances, with transaction UID: " << transactionUid;
+      const DIC_US messageId = pimpl_->assoc_->nextMsgID++;
+      
+      {
+        T_DIMSE_Message message;
+        memset(&message, 0, sizeof(message));
+        message.CommandField = DIMSE_N_ACTION_RQ;
+
+        T_DIMSE_N_ActionRQ& content = message.msg.NActionRQ;
+        content.MessageID = messageId;
+        strncpy(content.RequestedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN);
+        strncpy(content.RequestedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN);
+        content.ActionTypeID = 1;  // "Request Storage Commitment"
+        content.DataSetType = DIMSE_DATASET_PRESENT;
+
+        DcmDataset dataset;
+        if (!dataset.putAndInsertString(DCM_TransactionUID, transactionUid.c_str()).good())
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+
+        {
+          std::vector<StorageCommitmentFailureReason> empty;
+          FillSopSequence(dataset, DCM_ReferencedSOPSequence, sopClassUids, sopInstanceUids, empty, false);
+        }
+          
+        int presID = ASC_findAcceptedPresentationContextID(
+          pimpl_->assoc_, UID_StorageCommitmentPushModelSOPClass);
+        if (presID == 0)
+        {
+          throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
+                                 "Unable to send N-ACTION request to AET: " + remoteAet_);
+        }
+
+        if (!DIMSE_sendMessageUsingMemoryData(
+              pimpl_->assoc_, presID, &message, NULL /* status detail */,
+              &dataset, NULL /* callback */, NULL /* callback context */,
+              NULL /* commandSet */).good())
+        {
+          throw OrthancException(ErrorCode_NetworkProtocol);
+        }
+      }
+
+      /**
+       * Read the "N_ACTION_RSP" response
+       **/
+
+      {
+        T_ASC_PresentationContextID presID = 0;
+        T_DIMSE_Message message;
+        
+        const int timeout = pimpl_->dimseTimeout_;
+        if (!DIMSE_receiveCommand(pimpl_->assoc_,
+                                  (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), timeout,
+                                  &presID, &message, NULL /* no statusDetail */).good() ||
+            message.CommandField != DIMSE_N_ACTION_RSP)
+        {
+          throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
+                                 "Unable to read N-ACTION response from AET: " + remoteAet_);
+        }
+
+        const T_DIMSE_N_ActionRSP& content = message.msg.NActionRSP;
+        if (content.MessageIDBeingRespondedTo != messageId ||
+            !(content.opts & O_NACTION_AFFECTEDSOPCLASSUID) ||
+            !(content.opts & O_NACTION_AFFECTEDSOPINSTANCEUID) ||
+            //(content.opts & O_NACTION_ACTIONTYPEID) ||  // Pedantic test - The "content.ActionTypeID" is not used by Orthanc
+            std::string(content.AffectedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass ||
+            std::string(content.AffectedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance ||
+            content.DataSetType != DIMSE_DATASET_NULL)
+        {
+          throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
+                                 "Badly formatted N-ACTION response from AET: " + remoteAet_);
+        }
+
+        if (content.DimseStatus != 0 /* success */)
+        {
+          throw OrthancException(ErrorCode_NetworkProtocol, "Storage commitment - "
+                                 "The request cannot be handled by remote AET: " + remoteAet_);
+        }
+      }
+
+      Close();
+    }
+    catch (OrthancException&)
+    {
+      Close();
+      throw;
+    }
+  }
 }
--- a/Core/DicomNetworking/DicomUserConnection.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/DicomUserConnection.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -54,6 +54,13 @@
     struct PImpl;
     boost::shared_ptr<PImpl> pimpl_;
 
+    enum Mode
+    {
+      Mode_Generic,
+      Mode_ReportStorageCommitment,
+      Mode_RequestStorageCommitment
+    };
+    
     // Connection parameters
     std::string preferredTransferSyntax_;
     std::string modalityPreferredTransferSyntax_;
@@ -68,7 +75,8 @@
 
     void CheckIsOpen() const;
 
-    void SetupPresentationContexts(const std::string& preferredTransferSyntax);
+    void SetupPresentationContexts(Mode mode,
+                                   const std::string& preferredTransferSyntax);
 
     void MoveInternal(const std::string& targetAet,
                       ResourceType level,
@@ -80,6 +88,8 @@
 
     void DefaultSetup();
 
+    void OpenInternal(Mode mode);
+
   public:
     DicomUserConnection();
 
@@ -138,7 +148,10 @@
 
     void AddStorageSOPClass(const char* sop);
 
-    void Open();
+    void Open()
+    {
+      OpenInternal(Mode_Generic);
+    }
 
     void Close();
 
@@ -146,33 +159,45 @@
 
     bool Echo();
 
-    void Store(const char* buffer, 
+    void Store(std::string& sopClassUid /* out */,
+               std::string& sopInstanceUid /* out */,
+               const char* buffer, 
                size_t size,
                const std::string& moveOriginatorAET,
                uint16_t moveOriginatorID);
 
-    void Store(const char* buffer, 
+    void Store(std::string& sopClassUid /* out */,
+               std::string& sopInstanceUid /* out */,
+               const char* buffer, 
                size_t size)
     {
-      Store(buffer, size, "", 0);  // Not a C-Move
+      Store(sopClassUid, sopInstanceUid, buffer, size, "", 0);  // Not a C-Move
     }
 
-    void Store(const std::string& buffer,
+    void Store(std::string& sopClassUid /* out */,
+               std::string& sopInstanceUid /* out */,
+               const std::string& buffer,
                const std::string& moveOriginatorAET,
                uint16_t moveOriginatorID);
 
-    void Store(const std::string& buffer)
+    void Store(std::string& sopClassUid /* out */,
+               std::string& sopInstanceUid /* out */,
+               const std::string& buffer)
     {
-      Store(buffer, "", 0);  // Not a C-Move
+      Store(sopClassUid, sopInstanceUid, buffer, "", 0);  // Not a C-Move
     }
 
-    void StoreFile(const std::string& path,
+    void StoreFile(std::string& sopClassUid /* out */,
+                   std::string& sopInstanceUid /* out */,
+                   const std::string& path,
                    const std::string& moveOriginatorAET,
                    uint16_t moveOriginatorID);
 
-    void StoreFile(const std::string& path)
+    void StoreFile(std::string& sopClassUid /* out */,
+                   std::string& sopInstanceUid /* out */,
+                   const std::string& path)
     {
-      StoreFile(path, "", 0);  // Not a C-Move
+      StoreFile(sopClassUid, sopInstanceUid, path, "", 0);  // Not a C-Move
     }
 
     void Find(DicomFindAnswers& result,
@@ -213,5 +238,17 @@
 
     bool IsSameAssociation(const std::string& localAet,
                            const RemoteModalityParameters& remote) const;
+
+    void ReportStorageCommitment(
+      const std::string& transactionUid,
+      const std::vector<std::string>& sopClassUids,
+      const std::vector<std::string>& sopInstanceUids,
+      const std::vector<StorageCommitmentFailureReason>& failureReasons);
+
+    // transactionUid: To be generated by Toolbox::GenerateDicomPrivateUniqueIdentifier()
+    void RequestStorageCommitment(
+      const std::string& transactionUid,
+      const std::vector<std::string>& sopClassUids,
+      const std::vector<std::string>& sopInstanceUids);
   };
 }
--- a/Core/DicomNetworking/IApplicationEntityFilter.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/IApplicationEntityFilter.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomNetworking/IDicomConnectionManager.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/IDicomConnectionManager.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomNetworking/IFindRequestHandler.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/IFindRequestHandler.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomNetworking/IFindRequestHandlerFactory.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/IFindRequestHandlerFactory.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomNetworking/IMoveRequestHandler.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/IMoveRequestHandler.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomNetworking/IMoveRequestHandlerFactory.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/IMoveRequestHandlerFactory.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/IStorageCommitmentRequestHandler.h	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,66 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <boost/noncopyable.hpp>
+#include <string>
+#include <vector>
+
+namespace Orthanc
+{
+  class IStorageCommitmentRequestHandler : public boost::noncopyable
+  {
+  public:
+    virtual ~IStorageCommitmentRequestHandler()
+    {
+    }
+
+    virtual void HandleRequest(const std::string& transactionUid,
+                               const std::vector<std::string>& sopClassUids,
+                               const std::vector<std::string>& sopInstanceUids,
+                               const std::string& remoteIp,
+                               const std::string& remoteAet,
+                               const std::string& calledAet) = 0;
+
+    virtual void HandleReport(const std::string& transactionUid,
+                              const std::vector<std::string>& successSopClassUids,
+                              const std::vector<std::string>& successSopInstanceUids,
+                              const std::vector<std::string>& failedSopClassUids,
+                              const std::vector<std::string>& failedSopInstanceUids,
+                              const std::vector<StorageCommitmentFailureReason>& failureReasons,
+                              const std::string& remoteIp,
+                              const std::string& remoteAet,
+                              const std::string& calledAet) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomNetworking/IStorageCommitmentRequestHandlerFactory.h	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,49 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "IStorageCommitmentRequestHandler.h"
+
+namespace Orthanc
+{
+  class IStorageCommitmentRequestHandlerFactory : public boost::noncopyable
+  {
+  public:
+    virtual ~IStorageCommitmentRequestHandlerFactory()
+    {
+    }
+
+    virtual IStorageCommitmentRequestHandler* ConstructStorageCommitmentRequestHandler() = 0;
+  };
+}
--- a/Core/DicomNetworking/IStoreRequestHandler.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/IStoreRequestHandler.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomNetworking/IStoreRequestHandlerFactory.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/IStoreRequestHandlerFactory.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomNetworking/IWorklistRequestHandler.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/IWorklistRequestHandler.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomNetworking/IWorklistRequestHandlerFactory.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/IWorklistRequestHandlerFactory.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomNetworking/Internals/CommandDispatcher.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/Internals/CommandDispatcher.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -90,11 +90,15 @@
 #include "FindScp.h"
 #include "StoreScp.h"
 #include "MoveScp.h"
+#include "../../Compatibility.h"
 #include "../../Toolbox.h"
 #include "../../Logging.h"
+#include "../../OrthancException.h"
 
+#include <dcmtk/dcmdata/dcdeftag.h>     /* for storage commitment */
+#include <dcmtk/dcmdata/dcsequen.h>     /* for class DcmSequenceOfItems */
+#include <dcmtk/dcmdata/dcuid.h>        /* for variable dcmAllStorageSOPClassUIDs */
 #include <dcmtk/dcmnet/dcasccfg.h>      /* for class DcmAssociationConfiguration */
-#include <dcmtk/dcmdata/dcuid.h>        /* for variable dcmAllStorageSOPClassUIDs */
 
 #include <boost/lexical_cast.hpp>
 
@@ -271,33 +275,6 @@
       OFString sprofile;
       OFString temp_str;
 
-      std::vector<const char*> knownAbstractSyntaxes;
-
-      // For C-STORE
-      if (server.HasStoreRequestHandlerFactory())
-      {
-        knownAbstractSyntaxes.push_back(UID_VerificationSOPClass);
-      }
-
-      // For C-FIND
-      if (server.HasFindRequestHandlerFactory())
-      {
-        knownAbstractSyntaxes.push_back(UID_FINDPatientRootQueryRetrieveInformationModel);
-        knownAbstractSyntaxes.push_back(UID_FINDStudyRootQueryRetrieveInformationModel);
-      }
-
-      if (server.HasWorklistRequestHandlerFactory())
-      {
-        knownAbstractSyntaxes.push_back(UID_FINDModalityWorklistInformationModel);
-      }
-
-      // For C-MOVE
-      if (server.HasMoveRequestHandlerFactory())
-      {
-        knownAbstractSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel);
-        knownAbstractSyntaxes.push_back(UID_MOVEPatientRootQueryRetrieveInformationModel);
-      }
-
       cond = ASC_receiveAssociation(net, &assoc, 
                                     /*opt_maxPDU*/ ASC_DEFAULTMAXPDU, 
                                     NULL, NULL,
@@ -361,133 +338,206 @@
                 << " on IP " << remoteIp;
 
 
-      std::vector<const char*> transferSyntaxes;
-
-      // This is the list of the transfer syntaxes that were supported up to Orthanc 0.7.1
-      transferSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);
-      transferSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax);
-      transferSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax);
-
-      // New transfer syntaxes supported since Orthanc 0.7.2
-      if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Deflated))
       {
-        transferSyntaxes.push_back(UID_DeflatedExplicitVRLittleEndianTransferSyntax); 
-      }
+        /* accept the abstract syntaxes for C-ECHO, C-FIND, C-MOVE,
+           and storage commitment, if presented */
 
-      if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg))
-      {
-        transferSyntaxes.push_back(UID_JPEGProcess1TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess2_4TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess3_5TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess6_8TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess7_9TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess10_12TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess11_13TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess14TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess15TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess16_18TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess17_19TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess20_22TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess21_23TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess24_26TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess25_27TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess28TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess29TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGProcess14SV1TransferSyntax);
-      }
+        std::vector<const char*> genericTransferSyntaxes;
+        genericTransferSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);
+        genericTransferSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax);
+        genericTransferSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax);
 
-      if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg2000))
-      {
-        transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax);
-        transferSyntaxes.push_back(UID_JPEG2000TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax);
-        transferSyntaxes.push_back(UID_JPEG2000TransferSyntax);
-        transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionLosslessOnlyTransferSyntax);
-        transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionTransferSyntax);
-      }
+        std::vector<const char*> knownAbstractSyntaxes;
 
-      if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_JpegLossless))
-      {
-        transferSyntaxes.push_back(UID_JPEGLSLosslessTransferSyntax);
-        transferSyntaxes.push_back(UID_JPEGLSLossyTransferSyntax);
-      }
+        // For C-ECHO (always enabled since Orthanc 1.6.0; in earlier
+        // versions, only enabled if C-STORE was also enabled)
+        knownAbstractSyntaxes.push_back(UID_VerificationSOPClass);
 
-      if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpip))
-      {
-        transferSyntaxes.push_back(UID_JPIPReferencedTransferSyntax);
-        transferSyntaxes.push_back(UID_JPIPReferencedDeflateTransferSyntax);
-      }
-
-      if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Mpeg2))
-      {
-        transferSyntaxes.push_back(UID_MPEG2MainProfileAtMainLevelTransferSyntax);
-        transferSyntaxes.push_back(UID_MPEG2MainProfileAtHighLevelTransferSyntax);
-      }
+        // For C-FIND
+        if (server.HasFindRequestHandlerFactory())
+        {
+          knownAbstractSyntaxes.push_back(UID_FINDPatientRootQueryRetrieveInformationModel);
+          knownAbstractSyntaxes.push_back(UID_FINDStudyRootQueryRetrieveInformationModel);
+        }
 
-      if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Rle))
-      {
-        transferSyntaxes.push_back(UID_RLELosslessTransferSyntax);
-      }
-
-      /* accept the Verification SOP Class if presented */
-      cond = ASC_acceptContextsWithPreferredTransferSyntaxes(
-        assoc->params,
-        &knownAbstractSyntaxes[0], knownAbstractSyntaxes.size(),
-        &transferSyntaxes[0], transferSyntaxes.size());
-      if (cond.bad())
-      {
-        LOG(INFO) << cond.text();
-        AssociationCleanup(assoc);
-        return NULL;
-      }
+        if (server.HasWorklistRequestHandlerFactory())
+        {
+          knownAbstractSyntaxes.push_back(UID_FINDModalityWorklistInformationModel);
+        }
 
-      /* the array of Storage SOP Class UIDs that is defined within "dcmdata/libsrc/dcuid.cc" */
-      size_t count = 0;
-      while (dcmAllStorageSOPClassUIDs[count] != NULL)
-      {
-        count++;
-      }
+        // For C-MOVE
+        if (server.HasMoveRequestHandlerFactory())
+        {
+          knownAbstractSyntaxes.push_back(UID_MOVEStudyRootQueryRetrieveInformationModel);
+          knownAbstractSyntaxes.push_back(UID_MOVEPatientRootQueryRetrieveInformationModel);
+        }
 
-#if DCMTK_VERSION_NUMBER >= 362
-      // The global variable "numberOfDcmAllStorageSOPClassUIDs" is
-      // only published if DCMTK >= 3.6.2:
-      // https://bitbucket.org/sjodogne/orthanc/issues/137
-      assert(static_cast<int>(count) == numberOfDcmAllStorageSOPClassUIDs);
-#endif
-      
-      cond = ASC_acceptContextsWithPreferredTransferSyntaxes(
-        assoc->params,
-        dcmAllStorageSOPClassUIDs, count,
-        &transferSyntaxes[0], transferSyntaxes.size());
-      if (cond.bad())
-      {
-        LOG(INFO) << cond.text();
-        AssociationCleanup(assoc);
-        return NULL;
-      }
-
-      if (!server.HasApplicationEntityFilter() ||
-          server.GetApplicationEntityFilter().IsUnknownSopClassAccepted(remoteIp, remoteAet, calledAet))
-      {
-        /*
-         * Promiscous mode is enabled: Accept everything not known not
-         * to be a storage SOP class.
-         **/
-        cond = acceptUnknownContextsWithPreferredTransferSyntaxes(
-          assoc->params, &transferSyntaxes[0], transferSyntaxes.size(), ASC_SC_ROLE_DEFAULT);
+        cond = ASC_acceptContextsWithPreferredTransferSyntaxes(
+          assoc->params,
+          &knownAbstractSyntaxes[0], knownAbstractSyntaxes.size(),
+          &genericTransferSyntaxes[0], genericTransferSyntaxes.size());
         if (cond.bad())
         {
           LOG(INFO) << cond.text();
           AssociationCleanup(assoc);
           return NULL;
         }
+
+      
+        /* storage commitment support, new in Orthanc 1.6.0 */
+        if (server.HasStorageCommitmentRequestHandlerFactory())
+        {
+          /**
+           * "ASC_SC_ROLE_SCUSCP": The "SCU" role is needed to accept
+           * remote storage commitment requests, and the "SCP" role is
+           * needed to receive storage commitments answers.
+           **/        
+          const char* as[1] = { UID_StorageCommitmentPushModelSOPClass }; 
+          cond = ASC_acceptContextsWithPreferredTransferSyntaxes(
+            assoc->params, as, 1,
+            &genericTransferSyntaxes[0], genericTransferSyntaxes.size(), ASC_SC_ROLE_SCUSCP);
+          if (cond.bad())
+          {
+            LOG(INFO) << cond.text();
+            AssociationCleanup(assoc);
+            return NULL;
+          }
+        }
+      }
+      
+
+      {
+        /* accept the abstract syntaxes for C-STORE, if presented */
+
+        std::vector<const char*> storageTransferSyntaxes;
+
+        // This is the list of the transfer syntaxes that were supported up to Orthanc 0.7.1
+        storageTransferSyntaxes.push_back(UID_LittleEndianExplicitTransferSyntax);
+        storageTransferSyntaxes.push_back(UID_BigEndianExplicitTransferSyntax);
+        storageTransferSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax);
+
+        // New transfer syntaxes supported since Orthanc 0.7.2
+        if (!server.HasApplicationEntityFilter() ||
+            server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Deflated))
+        {
+          storageTransferSyntaxes.push_back(UID_DeflatedExplicitVRLittleEndianTransferSyntax); 
+        }
+
+        if (!server.HasApplicationEntityFilter() ||
+            server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg))
+        {
+          storageTransferSyntaxes.push_back(UID_JPEGProcess1TransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEGProcess2_4TransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEGProcess3_5TransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEGProcess6_8TransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEGProcess7_9TransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEGProcess10_12TransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEGProcess11_13TransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEGProcess14TransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEGProcess15TransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEGProcess16_18TransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEGProcess17_19TransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEGProcess20_22TransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEGProcess21_23TransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEGProcess24_26TransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEGProcess25_27TransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEGProcess28TransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEGProcess29TransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEGProcess14SV1TransferSyntax);
+        }
+
+        if (!server.HasApplicationEntityFilter() ||
+            server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpeg2000))
+        {
+          storageTransferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEG2000TransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEG2000TransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionLosslessOnlyTransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionTransferSyntax);
+        }
+
+        if (!server.HasApplicationEntityFilter() ||
+            server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_JpegLossless))
+        {
+          storageTransferSyntaxes.push_back(UID_JPEGLSLosslessTransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPEGLSLossyTransferSyntax);
+        }
+
+        if (!server.HasApplicationEntityFilter() ||
+            server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Jpip))
+        {
+          storageTransferSyntaxes.push_back(UID_JPIPReferencedTransferSyntax);
+          storageTransferSyntaxes.push_back(UID_JPIPReferencedDeflateTransferSyntax);
+        }
+
+        if (!server.HasApplicationEntityFilter() ||
+            server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Mpeg2))
+        {
+          storageTransferSyntaxes.push_back(UID_MPEG2MainProfileAtMainLevelTransferSyntax);
+          storageTransferSyntaxes.push_back(UID_MPEG2MainProfileAtHighLevelTransferSyntax);
+        }
+
+#if DCMTK_VERSION_NUMBER >= 361
+        // New in Orthanc 1.6.0
+        if (!server.HasApplicationEntityFilter() ||
+            server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Mpeg4))
+        {
+          storageTransferSyntaxes.push_back(UID_MPEG4BDcompatibleHighProfileLevel4_1TransferSyntax);
+          storageTransferSyntaxes.push_back(UID_MPEG4HighProfileLevel4_1TransferSyntax);
+          storageTransferSyntaxes.push_back(UID_MPEG4HighProfileLevel4_2_For2DVideoTransferSyntax);
+          storageTransferSyntaxes.push_back(UID_MPEG4HighProfileLevel4_2_For3DVideoTransferSyntax);
+          storageTransferSyntaxes.push_back(UID_MPEG4StereoHighProfileLevel4_2TransferSyntax);
+        }
+#endif
+
+        if (!server.HasApplicationEntityFilter() ||
+            server.GetApplicationEntityFilter().IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, TransferSyntax_Rle))
+        {
+          storageTransferSyntaxes.push_back(UID_RLELosslessTransferSyntax);
+        }
+
+        /* the array of Storage SOP Class UIDs that is defined within "dcmdata/libsrc/dcuid.cc" */
+        size_t count = 0;
+        while (dcmAllStorageSOPClassUIDs[count] != NULL)
+        {
+          count++;
+        }
+        
+#if DCMTK_VERSION_NUMBER >= 362
+        // The global variable "numberOfDcmAllStorageSOPClassUIDs" is
+        // only published if DCMTK >= 3.6.2:
+        // https://bitbucket.org/sjodogne/orthanc/issues/137
+        assert(static_cast<int>(count) == numberOfDcmAllStorageSOPClassUIDs);
+#endif
+      
+        cond = ASC_acceptContextsWithPreferredTransferSyntaxes(
+          assoc->params,
+          dcmAllStorageSOPClassUIDs, count,
+          &storageTransferSyntaxes[0], storageTransferSyntaxes.size());
+        if (cond.bad())
+        {
+          LOG(INFO) << cond.text();
+          AssociationCleanup(assoc);
+          return NULL;
+        }
+
+        if (!server.HasApplicationEntityFilter() ||
+            server.GetApplicationEntityFilter().IsUnknownSopClassAccepted(remoteIp, remoteAet, calledAet))
+        {
+          /*
+           * Promiscous mode is enabled: Accept everything not known not
+           * to be a storage SOP class.
+           **/
+          cond = acceptUnknownContextsWithPreferredTransferSyntaxes(
+            assoc->params, &storageTransferSyntaxes[0], storageTransferSyntaxes.size(), ASC_SC_ROLE_DEFAULT);
+          if (cond.bad())
+          {
+            LOG(INFO) << cond.text();
+            AssociationCleanup(assoc);
+            return NULL;
+          }
+        }
       }
 
       /* set our app title */
@@ -689,6 +739,16 @@
             supported = true;
             break;
 
+          case DIMSE_N_ACTION_RQ:
+            request = DicomRequestType_NAction;
+            supported = true;
+            break;
+
+          case DIMSE_N_EVENT_REPORT_RQ:
+            request = DicomRequestType_NEventReport;
+            supported = true;
+            break;
+
           default:
             // we cannot handle this kind of message
             cond = DIMSE_BADCOMMANDTYPE;
@@ -725,12 +785,12 @@
             case DicomRequestType_Store:
               if (server_.HasStoreRequestHandlerFactory()) // Should always be true
               {
-                std::auto_ptr<IStoreRequestHandler> handler
+                std::unique_ptr<IStoreRequestHandler> handler
                   (server_.GetStoreRequestHandlerFactory().ConstructStoreRequestHandler());
 
                 if (handler.get() != NULL)
                 {
-                  cond = Internals::storeScp(assoc_, &msg, presID, *handler, remoteIp_);
+                  cond = Internals::storeScp(assoc_, &msg, presID, *handler, remoteIp_, associationTimeout_);
                 }
               }
               break;
@@ -738,12 +798,12 @@
             case DicomRequestType_Move:
               if (server_.HasMoveRequestHandlerFactory()) // Should always be true
               {
-                std::auto_ptr<IMoveRequestHandler> handler
+                std::unique_ptr<IMoveRequestHandler> handler
                   (server_.GetMoveRequestHandlerFactory().ConstructMoveRequestHandler());
 
                 if (handler.get() != NULL)
                 {
-                  cond = Internals::moveScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_, calledAet_);
+                  cond = Internals::moveScp(assoc_, &msg, presID, *handler, remoteIp_, remoteAet_, calledAet_, associationTimeout_);
                 }
               }
               break;
@@ -752,13 +812,13 @@
               if (server_.HasFindRequestHandlerFactory() || // Should always be true
                   server_.HasWorklistRequestHandlerFactory())
               {
-                std::auto_ptr<IFindRequestHandler> findHandler;
+                std::unique_ptr<IFindRequestHandler> findHandler;
                 if (server_.HasFindRequestHandlerFactory())
                 {
                   findHandler.reset(server_.GetFindRequestHandlerFactory().ConstructFindRequestHandler());
                 }
 
-                std::auto_ptr<IWorklistRequestHandler> worklistHandler;
+                std::unique_ptr<IWorklistRequestHandler> worklistHandler;
                 if (server_.HasWorklistRequestHandlerFactory())
                 {
                   worklistHandler.reset(server_.GetWorklistRequestHandlerFactory().ConstructWorklistRequestHandler());
@@ -766,10 +826,18 @@
 
                 cond = Internals::findScp(assoc_, &msg, presID, server_.GetRemoteModalities(),
                                           findHandler.get(), worklistHandler.get(),
-                                          remoteIp_, remoteAet_, calledAet_);
+                                          remoteIp_, remoteAet_, calledAet_, associationTimeout_);
               }
               break;
 
+            case DicomRequestType_NAction:
+              cond = NActionScp(&msg, presID);
+              break;              
+
+            case DicomRequestType_NEventReport:
+              cond = NEventReportScp(&msg, presID);
+              break;              
+
             default:
               // Should never happen
               break;
@@ -823,5 +891,379 @@
       }
       return cond;
     }
+
+
+    static DcmDataset* ReadDataset(T_ASC_Association* assoc,
+                                   const char* errorMessage,
+                                   int timeout)
+    {
+      DcmDataset *tmp = NULL;
+      T_ASC_PresentationContextID presIdData;
+    
+      OFCondition cond = DIMSE_receiveDataSetInMemory(
+        assoc, (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING), timeout,
+        &presIdData, &tmp, NULL, NULL);
+      if (!cond.good() ||
+          tmp == NULL)
+      {
+        throw OrthancException(ErrorCode_NetworkProtocol, errorMessage);
+      }
+
+      return tmp;
+    }
+
+
+    static std::string ReadString(DcmDataset& dataset,
+                                  const DcmTagKey& tag)
+    {
+      const char* s = NULL;
+      if (!dataset.findAndGetString(tag, s).good() ||
+          s == NULL)
+      {
+        char buf[64];
+        sprintf(buf, "Missing mandatory tag in dataset: (%04X,%04X)",
+                tag.getGroup(), tag.getElement());
+        throw OrthancException(ErrorCode_NetworkProtocol, buf);
+      }
+
+      return std::string(s);
+    }
+
+
+    static void ReadSopSequence(
+      std::vector<std::string>& sopClassUids,
+      std::vector<std::string>& sopInstanceUids,
+      std::vector<StorageCommitmentFailureReason>* failureReasons, // Can be NULL
+      DcmDataset& dataset,
+      const DcmTagKey& tag,
+      bool mandatory)
+    {
+      sopClassUids.clear();
+      sopInstanceUids.clear();
+
+      if (failureReasons)
+      {
+        failureReasons->clear();
+      }
+
+      DcmSequenceOfItems* sequence = NULL;
+      if (!dataset.findAndGetSequence(tag, sequence).good() ||
+          sequence == NULL)
+      {
+        if (mandatory)
+        {        
+          char buf[64];
+          sprintf(buf, "Missing mandatory sequence in dataset: (%04X,%04X)",
+                  tag.getGroup(), tag.getElement());
+          throw OrthancException(ErrorCode_NetworkProtocol, buf);
+        }
+        else
+        {
+          return;
+        }
+      }
+
+      sopClassUids.reserve(sequence->card());
+      sopInstanceUids.reserve(sequence->card());
+
+      if (failureReasons)
+      {
+        failureReasons->reserve(sequence->card());
+      }
+
+      for (unsigned long i = 0; i < sequence->card(); i++)
+      {
+        const char* a = NULL;
+        const char* b = NULL;
+        if (!sequence->getItem(i)->findAndGetString(DCM_ReferencedSOPClassUID, a).good() ||
+            !sequence->getItem(i)->findAndGetString(DCM_ReferencedSOPInstanceUID, b).good() ||
+            a == NULL ||
+            b == NULL)
+        {
+          throw OrthancException(ErrorCode_NetworkProtocol,
+                                 "Missing Referenced SOP Class/Instance UID "
+                                 "in storage commitment dataset");
+        }
+
+        sopClassUids.push_back(a);
+        sopInstanceUids.push_back(b);
+
+        if (failureReasons != NULL)
+        {
+          Uint16 reason;
+          if (!sequence->getItem(i)->findAndGetUint16(DCM_FailureReason, reason).good())
+          {
+            throw OrthancException(ErrorCode_NetworkProtocol,
+                                   "Missing Failure Reason (0008,1197) "
+                                   "in storage commitment dataset");
+          }
+
+          failureReasons->push_back(static_cast<StorageCommitmentFailureReason>(reason));
+        }
+      }
+    }
+
+    
+    OFCondition CommandDispatcher::NActionScp(T_DIMSE_Message* msg,
+                                              T_ASC_PresentationContextID presID)
+    {
+      /**
+       * Starting with Orthanc 1.6.0, only storage commitment is
+       * supported with DICOM N-ACTION. This corresponds to the case
+       * where "Action Type ID" equals "1".
+       * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.2.html
+       * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-4
+       **/
+      
+      if (msg->CommandField != DIMSE_N_ACTION_RQ /* value == 304 == 0x0130 */ ||
+          !server_.HasStorageCommitmentRequestHandlerFactory())
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+
+      /**
+       * Check that the storage commitment request is correctly formatted.
+       **/
+      
+      const T_DIMSE_N_ActionRQ& request = msg->msg.NActionRQ;
+
+      if (request.ActionTypeID != 1)
+      {
+        throw OrthancException(ErrorCode_NotImplemented,
+                               "Only storage commitment is implemented for DICOM N-ACTION SCP");
+      }
+
+      if (std::string(request.RequestedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass ||
+          std::string(request.RequestedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance)
+      {
+        throw OrthancException(ErrorCode_NetworkProtocol,
+                               "Unexpected incoming SOP class or instance UID for storage commitment");
+      }
+
+      if (request.DataSetType != DIMSE_DATASET_PRESENT)
+      {
+        throw OrthancException(ErrorCode_NetworkProtocol,
+                               "Incoming storage commitment request without a dataset");
+      }
+
+
+      /**
+       * Extract the DICOM dataset that is associated with the DIMSE
+       * message. The content of this dataset is documented in "Table
+       * J.3-1. Storage Commitment Request - Action Information":
+       * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.2.html#table_J.3-1
+       **/
+      
+      std::unique_ptr<DcmDataset> dataset(
+        ReadDataset(assoc_, "Cannot read the dataset in N-ACTION SCP", associationTimeout_));
+
+      std::string transactionUid = ReadString(*dataset, DCM_TransactionUID);
+
+      std::vector<std::string> sopClassUid, sopInstanceUid;
+      ReadSopSequence(sopClassUid, sopInstanceUid, NULL,
+                      *dataset, DCM_ReferencedSOPSequence, true /* mandatory */);
+
+      LOG(INFO) << "Incoming storage commitment request, with transaction UID: " << transactionUid;
+
+      for (size_t i = 0; i < sopClassUid.size(); i++)
+      {
+        LOG(INFO) << "  (" << (i + 1) << "/" << sopClassUid.size()
+                  << ") queried SOP Class/Instance UID: "
+                  << sopClassUid[i] << " / " << sopInstanceUid[i];
+      }
+
+
+      /**
+       * Call the Orthanc handler. The list of available DIMSE status
+       * codes can be found at:
+       * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.4.1.10
+       **/
+
+      DIC_US dimseStatus;
+  
+      try
+      {
+        std::unique_ptr<IStorageCommitmentRequestHandler> handler
+          (server_.GetStorageCommitmentRequestHandlerFactory().
+           ConstructStorageCommitmentRequestHandler());
+
+        handler->HandleRequest(transactionUid, sopClassUid, sopInstanceUid,
+                               remoteIp_, remoteAet_, calledAet_);
+        
+        dimseStatus = 0;  // Success
+      }
+      catch (OrthancException& e)
+      {
+        LOG(ERROR) << "Error while processing an incoming storage commitment request: " << e.What();
+
+        // Code 0x0110 - "General failure in processing the operation was encountered"
+        dimseStatus = STATUS_N_ProcessingFailure;
+      }
+
+
+      /**
+       * Send the DIMSE status back to the SCU.
+       **/
+
+      {
+        T_DIMSE_Message response;
+        memset(&response, 0, sizeof(response));
+        response.CommandField = DIMSE_N_ACTION_RSP;
+
+        T_DIMSE_N_ActionRSP& content = response.msg.NActionRSP;
+        content.MessageIDBeingRespondedTo = request.MessageID;
+        strncpy(content.AffectedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN);
+        content.DimseStatus = dimseStatus;
+        strncpy(content.AffectedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN);
+        content.ActionTypeID = 0; // Not present, as "O_NACTION_ACTIONTYPEID" not set in "opts"
+        content.DataSetType = DIMSE_DATASET_NULL;  // Dataset is absent in storage commitment response
+        content.opts = O_NACTION_AFFECTEDSOPCLASSUID | O_NACTION_AFFECTEDSOPINSTANCEUID;
+
+        return DIMSE_sendMessageUsingMemoryData(
+          assoc_, presID, &response, NULL /* no dataset */, NULL /* dataObject */,
+          NULL /* callback */, NULL /* callback context */, NULL /* commandSet */);
+      }
+    }
+
+
+    OFCondition CommandDispatcher::NEventReportScp(T_DIMSE_Message* msg,
+                                                   T_ASC_PresentationContextID presID)
+    {
+      /**
+       * Starting with Orthanc 1.6.0, handling N-EVENT-REPORT for
+       * storage commitment.
+       * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html
+       * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#table_10.1-1
+       **/
+
+      if (msg->CommandField != DIMSE_N_EVENT_REPORT_RQ /* value == 256 == 0x0100 */ ||
+          !server_.HasStorageCommitmentRequestHandlerFactory())
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+
+      /**
+       * Check that the storage commitment report is correctly formatted.
+       **/
+      
+      const T_DIMSE_N_EventReportRQ& report = msg->msg.NEventReportRQ;
+
+      if (report.EventTypeID != 1 /* successful */ &&
+          report.EventTypeID != 2 /* failures exist */)
+      {
+        throw OrthancException(ErrorCode_NotImplemented,
+                               "Unknown event for DICOM N-EVENT-REPORT SCP");
+      }
+
+      if (std::string(report.AffectedSOPClassUID) != UID_StorageCommitmentPushModelSOPClass ||
+          std::string(report.AffectedSOPInstanceUID) != UID_StorageCommitmentPushModelSOPInstance)
+      {
+        throw OrthancException(ErrorCode_NetworkProtocol,
+                               "Unexpected incoming SOP class or instance UID for storage commitment");
+      }
+
+      if (report.DataSetType != DIMSE_DATASET_PRESENT)
+      {
+        throw OrthancException(ErrorCode_NetworkProtocol,
+                               "Incoming storage commitment report without a dataset");
+      }
+
+
+      /**
+       * Extract the DICOM dataset that is associated with the DIMSE
+       * message. The content of this dataset is documented in "Table
+       * J.3-2. Storage Commitment Result - Event Information":
+       * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html#table_J.3-2
+       **/
+      
+      std::unique_ptr<DcmDataset> dataset(
+        ReadDataset(assoc_, "Cannot read the dataset in N-EVENT-REPORT SCP", associationTimeout_));
+
+      std::string transactionUid = ReadString(*dataset, DCM_TransactionUID);
+
+      std::vector<std::string> successSopClassUid, successSopInstanceUid;
+      ReadSopSequence(successSopClassUid, successSopInstanceUid, NULL,
+                      *dataset, DCM_ReferencedSOPSequence,
+                      (report.EventTypeID == 1) /* mandatory in the case of success */);
+
+      std::vector<std::string> failedSopClassUid, failedSopInstanceUid;
+      std::vector<StorageCommitmentFailureReason> failureReasons;
+
+      if (report.EventTypeID == 2 /* failures exist */)
+      {
+        ReadSopSequence(failedSopClassUid, failedSopInstanceUid, &failureReasons,
+                        *dataset, DCM_FailedSOPSequence, true);
+      }
+
+      LOG(INFO) << "Incoming storage commitment report, with transaction UID: " << transactionUid;
+
+      for (size_t i = 0; i < successSopClassUid.size(); i++)
+      {
+        LOG(INFO) << "  (success " << (i + 1) << "/" << successSopClassUid.size()
+                  << ") SOP Class/Instance UID: "
+                  << successSopClassUid[i] << " / " << successSopInstanceUid[i];
+      }
+
+      for (size_t i = 0; i < failedSopClassUid.size(); i++)
+      {
+        LOG(INFO) << "  (failure " << (i + 1) << "/" << failedSopClassUid.size()
+                  << ") SOP Class/Instance UID: "
+                  << failedSopClassUid[i] << " / " << failedSopInstanceUid[i];
+      }
+
+      /**
+       * Call the Orthanc handler. The list of available DIMSE status
+       * codes can be found at:
+       * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part07/chapter_10.html#sect_10.1.4.1.10
+       **/
+
+      DIC_US dimseStatus;
+
+      try
+      {
+        std::unique_ptr<IStorageCommitmentRequestHandler> handler
+          (server_.GetStorageCommitmentRequestHandlerFactory().
+           ConstructStorageCommitmentRequestHandler());
+
+        handler->HandleReport(transactionUid, successSopClassUid, successSopInstanceUid,
+                              failedSopClassUid, failedSopInstanceUid, failureReasons,
+                              remoteIp_, remoteAet_, calledAet_);
+        
+        dimseStatus = 0;  // Success
+      }
+      catch (OrthancException& e)
+      {
+        LOG(ERROR) << "Error while processing an incoming storage commitment report: " << e.What();
+
+        // Code 0x0110 - "General failure in processing the operation was encountered"
+        dimseStatus = STATUS_N_ProcessingFailure;
+      }
+
+      
+      /**
+       * Send the DIMSE status back to the SCU.
+       **/
+
+      {
+        T_DIMSE_Message response;
+        memset(&response, 0, sizeof(response));
+        response.CommandField = DIMSE_N_EVENT_REPORT_RSP;
+
+        T_DIMSE_N_EventReportRSP& content = response.msg.NEventReportRSP;
+        content.MessageIDBeingRespondedTo = report.MessageID;
+        strncpy(content.AffectedSOPClassUID, UID_StorageCommitmentPushModelSOPClass, DIC_UI_LEN);
+        content.DimseStatus = dimseStatus;
+        strncpy(content.AffectedSOPInstanceUID, UID_StorageCommitmentPushModelSOPInstance, DIC_UI_LEN);
+        content.EventTypeID = 0; // Not present, as "O_NEVENTREPORT_EVENTTYPEID" not set in "opts"
+        content.DataSetType = DIMSE_DATASET_NULL;  // Dataset is absent in storage commitment response
+        content.opts = O_NEVENTREPORT_AFFECTEDSOPCLASSUID | O_NEVENTREPORT_AFFECTEDSOPINSTANCEUID;
+
+        return DIMSE_sendMessageUsingMemoryData(
+          assoc_, presID, &response, NULL /* no dataset */, NULL /* dataObject */,
+          NULL /* callback */, NULL /* callback context */, NULL /* commandSet */);
+      }
+    }
   }
 }
--- a/Core/DicomNetworking/Internals/CommandDispatcher.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/Internals/CommandDispatcher.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -56,6 +56,12 @@
       std::string calledAet_;
       IApplicationEntityFilter* filter_;
 
+      OFCondition NActionScp(T_DIMSE_Message* msg, 
+                             T_ASC_PresentationContextID presID);
+
+      OFCondition NEventReportScp(T_DIMSE_Message* msg, 
+                                  T_ASC_PresentationContextID presID);
+      
     public:
       CommandDispatcher(const DicomServer& server,
                         T_ASC_Association* assoc,
@@ -69,11 +75,11 @@
       virtual bool Step();
     };
 
-    OFCondition EchoScp(T_ASC_Association * assoc, 
-                        T_DIMSE_Message * msg, 
-                        T_ASC_PresentationContextID presID);
-
     CommandDispatcher* AcceptAssociation(const DicomServer& server, 
                                          T_ASC_Network *net);
+
+    OFCondition EchoScp(T_ASC_Association* assoc, 
+                        T_DIMSE_Message* msg, 
+                        T_ASC_PresentationContextID presID);
   }
 }
--- a/Core/DicomNetworking/Internals/FindScp.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/Internals/FindScp.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -346,7 +346,8 @@
                                  IWorklistRequestHandler* worklistHandler,
                                  const std::string& remoteIp,
                                  const std::string& remoteAet,
-                                 const std::string& calledAet)
+                                 const std::string& calledAet,
+                                 int timeout)
   {
     FindScpData data;
     data.modalities_ = &modalities;
@@ -359,8 +360,8 @@
 
     OFCondition cond = DIMSE_findProvider(assoc, presID, &msg->msg.CFindRQ, 
                                           FindScpCallback, &data,
-                                          /*opt_blockMode*/ DIMSE_BLOCKING, 
-                                          /*opt_dimse_timeout*/ 0);
+                                          /*opt_blockMode*/ (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING),
+                                          /*opt_dimse_timeout*/ timeout);
 
     // if some error occured, dump corresponding information and remove the outfile if necessary
     if (cond.bad())
--- a/Core/DicomNetworking/Internals/FindScp.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/Internals/FindScp.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -49,6 +49,7 @@
                         IWorklistRequestHandler* worklistHandler,   // can be NULL
                         const std::string& remoteIp,
                         const std::string& remoteAet,
-                        const std::string& calledAet);
+                        const std::string& calledAet,
+                        int timeout);
   }
 }
--- a/Core/DicomNetworking/Internals/MoveScp.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/Internals/MoveScp.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -114,7 +114,7 @@
       unsigned int subOperationCount_;
       unsigned int failureCount_;
       unsigned int warningCount_;
-      std::auto_ptr<IMoveRequestIterator> iterator_;
+      std::unique_ptr<IMoveRequestIterator> iterator_;
       const std::string* remoteIp_;
       const std::string* remoteAet_;
       const std::string* calledAet_;
@@ -272,7 +272,8 @@
                                  IMoveRequestHandler& handler,
                                  const std::string& remoteIp,
                                  const std::string& remoteAet,
-                                 const std::string& calledAet)
+                                 const std::string& calledAet,
+                                 int timeout)
   {
     MoveScpData data;
     data.target_ = std::string(msg->msg.CMoveRQ.MoveDestination);
@@ -284,8 +285,8 @@
 
     OFCondition cond = DIMSE_moveProvider(assoc, presID, &msg->msg.CMoveRQ, 
                                           MoveScpCallback, &data,
-                                          /*opt_blockMode*/ DIMSE_BLOCKING, 
-                                          /*opt_dimse_timeout*/ 0);
+                                          /*opt_blockMode*/ (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING),
+                                          /*opt_dimse_timeout*/ timeout);
 
     // if some error occured, dump corresponding information and remove the outfile if necessary
     if (cond.bad())
--- a/Core/DicomNetworking/Internals/MoveScp.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/Internals/MoveScp.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -47,6 +47,7 @@
                         IMoveRequestHandler& handler,
                         const std::string& remoteIp,
                         const std::string& remoteAet,
-                        const std::string& calledAet);
+                        const std::string& calledAet,
+                        int timeout);
   }
 }
--- a/Core/DicomNetworking/Internals/StoreScp.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/Internals/StoreScp.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -251,7 +251,8 @@
                                   T_DIMSE_Message * msg, 
                                   T_ASC_PresentationContextID presID,
                                   IStoreRequestHandler& handler,
-                                  const std::string& remoteIp)
+                                  const std::string& remoteIp,
+                                  int timeout)
   {
     OFCondition cond = EC_Normal;
     T_DIMSE_C_StoreRQ *req;
@@ -294,8 +295,8 @@
 
     cond = DIMSE_storeProvider(assoc, presID, req, NULL, /*opt_useMetaheader*/OFFalse, &dset,
                                storeScpCallback, &data, 
-                               /*opt_blockMode*/ DIMSE_BLOCKING, 
-                               /*opt_dimse_timeout*/ 0);
+                               /*opt_blockMode*/ (timeout ? DIMSE_NONBLOCKING : DIMSE_BLOCKING),
+                               /*opt_dimse_timeout*/ timeout);
 
     // if some error occured, dump corresponding information and remove the outfile if necessary
     if (cond.bad())
--- a/Core/DicomNetworking/Internals/StoreScp.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/Internals/StoreScp.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -45,6 +45,7 @@
                          T_DIMSE_Message * msg, 
                          T_ASC_PresentationContextID presID,
                          IStoreRequestHandler& handler,
-                         const std::string& remoteIp);
+                         const std::string& remoteIp,
+                         int timeout);
   }
 }
--- a/Core/DicomNetworking/RemoteModalityParameters.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/RemoteModalityParameters.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -50,6 +50,9 @@
 static const char* KEY_ALLOW_GET = "AllowGet";
 static const char* KEY_ALLOW_MOVE = "AllowMove";
 static const char* KEY_ALLOW_STORE = "AllowStore";
+static const char* KEY_ALLOW_N_ACTION = "AllowNAction";
+static const char* KEY_ALLOW_N_EVENT_REPORT = "AllowEventReport";
+static const char* KEY_ALLOW_STORAGE_COMMITMENT = "AllowStorageCommitment";
 static const char* KEY_HOST = "Host";
 static const char* KEY_PREFERRED_TRANSFER_SYNTAX = "PreferredTransferSyntax";
 static const char* KEY_MANUFACTURER = "Manufacturer";
@@ -70,6 +73,8 @@
     allowFind_ = true;
     allowMove_ = true;
     allowGet_ = true;
+    allowNAction_ = true;  // For storage commitment
+    allowNEventReport_ = true;  // For storage commitment
   }
 
 
@@ -171,7 +176,11 @@
 
     aet_ = SerializationToolbox::ReadString(serialized, KEY_AET);
     host_ = SerializationToolbox::ReadString(serialized, KEY_HOST);
-    preferredTransferSyntax_ = SerializationToolbox::ReadString(serialized, KEY_PREFERRED_TRANSFER_SYNTAX);
+
+    if (serialized.isMember(KEY_PREFERRED_TRANSFER_SYNTAX))
+    {
+      preferredTransferSyntax_ = SerializationToolbox::ReadString(serialized, KEY_PREFERRED_TRANSFER_SYNTAX);
+    }
 
     if (serialized.isMember(KEY_PORT))
     {
@@ -216,6 +225,23 @@
     {
       allowMove_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_MOVE);
     }
+
+    if (serialized.isMember(KEY_ALLOW_N_ACTION))
+    {
+      allowNAction_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_N_ACTION);
+    }
+
+    if (serialized.isMember(KEY_ALLOW_N_EVENT_REPORT))
+    {
+      allowNEventReport_ = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_N_EVENT_REPORT);
+    }
+
+    if (serialized.isMember(KEY_ALLOW_STORAGE_COMMITMENT))
+    {
+      bool allow = SerializationToolbox::ReadBoolean(serialized, KEY_ALLOW_STORAGE_COMMITMENT);
+      allowNAction_ = allow;
+      allowNEventReport_ = allow;
+    }
   }
 
 
@@ -238,6 +264,12 @@
       case DicomRequestType_Store:
         return allowStore_;
 
+      case DicomRequestType_NAction:
+        return allowNAction_;
+
+      case DicomRequestType_NEventReport:
+        return allowNEventReport_;
+
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
@@ -269,6 +301,14 @@
         allowStore_ = allowed;
         break;
 
+      case DicomRequestType_NAction:
+        allowNAction_ = allowed;
+        break;
+
+      case DicomRequestType_NEventReport:
+        allowNEventReport_ = allowed;
+        break;
+
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
@@ -281,7 +321,9 @@
             !allowStore_ ||
             !allowFind_ ||
             !allowGet_ ||
-            !allowMove_);
+            !allowMove_ ||
+            !allowNAction_ ||
+            !allowNEventReport_);
   }
 
   
@@ -302,6 +344,8 @@
       target[KEY_ALLOW_FIND] = allowFind_;
       target[KEY_ALLOW_GET] = allowGet_;
       target[KEY_ALLOW_MOVE] = allowMove_;
+      target[KEY_ALLOW_N_ACTION] = allowNAction_;
+      target[KEY_ALLOW_N_EVENT_REPORT] = allowNEventReport_;
     }
     else
     {
--- a/Core/DicomNetworking/RemoteModalityParameters.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/RemoteModalityParameters.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -54,6 +54,9 @@
     bool                  allowFind_;
     bool                  allowMove_;
     bool                  allowGet_;
+    bool                  allowNAction_;
+    bool                  allowNEventReport_;
+
     void Clear();
 
     void UnserializeArray(const Json::Value& serialized);
--- a/Core/DicomNetworking/TimeoutDicomConnectionManager.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/TimeoutDicomConnectionManager.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomNetworking/TimeoutDicomConnectionManager.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomNetworking/TimeoutDicomConnectionManager.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -63,6 +63,8 @@
 
 #else
 
+#include "../Compatibility.h"
+
 #include <boost/date_time/posix_time/posix_time.hpp>
 
 namespace Orthanc
@@ -72,9 +74,9 @@
   private:
     class Resource;
 
-    std::auto_ptr<DicomUserConnection>   connection_;
-    boost::posix_time::ptime             lastUse_;
-    boost::posix_time::time_duration     timeout_;
+    std::unique_ptr<DicomUserConnection>  connection_;
+    boost::posix_time::ptime              lastUse_;
+    boost::posix_time::time_duration      timeout_;
 
     void Touch();
 
--- a/Core/DicomParsing/DicomDirWriter.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomParsing/DicomDirWriter.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -104,6 +104,7 @@
 #include "FromDcmtkBridge.h"
 #include "ToDcmtkBridge.h"
 
+#include "../Compatibility.h"
 #include "../Logging.h"
 #include "../OrthancException.h"
 #include "../TemporaryFile.h"
@@ -132,7 +133,7 @@
     std::string                fileSetId_;
     bool                       extendedSopClass_;
     TemporaryFile              file_;
-    std::auto_ptr<DcmDicomDir> dir_;
+    std::unique_ptr<DcmDicomDir> dir_;
 
     typedef std::pair<ResourceType, std::string>  IndexKey;
     typedef std::map<IndexKey, DcmDirectoryRecord* >  Index;
@@ -466,7 +467,7 @@
         return false; // Already existing
       }
 
-      std::auto_ptr<DcmDirectoryRecord> record(new DcmDirectoryRecord(type, NULL, filename));
+      std::unique_ptr<DcmDirectoryRecord> record(new DcmDirectoryRecord(type, NULL, filename));
 
       switch (level)
       {
--- a/Core/DicomParsing/DicomDirWriter.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomParsing/DicomDirWriter.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomParsing/DicomModification.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomParsing/DicomModification.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -34,13 +34,14 @@
 #include "../PrecompiledHeaders.h"
 #include "DicomModification.h"
 
+#include "../Compatibility.h"
 #include "../Logging.h"
 #include "../OrthancException.h"
 #include "../SerializationToolbox.h"
 #include "FromDcmtkBridge.h"
 #include "ITagVisitor.h"
 
-#include <memory>   // For std::auto_ptr
+#include <memory>   // For std::unique_ptr
 
 
 static const std::string ORTHANC_DEIDENTIFICATION_METHOD_2008 =
@@ -317,7 +318,7 @@
   void DicomModification::MapDicomTags(ParsedDicomFile& dicom,
                                        ResourceType level)
   {
-    std::auto_ptr<DicomTag> tag;
+    std::unique_ptr<DicomTag> tag;
 
     switch (level)
     {
@@ -347,7 +348,7 @@
 
     dicom.Replace(*tag, mapped, 
                   false /* don't try and decode data URI scheme for UIDs */, 
-                  DicomReplaceMode_InsertIfAbsent);
+                  DicomReplaceMode_InsertIfAbsent, privateCreator_);
   }
 
   
@@ -359,6 +360,7 @@
     keepSeriesInstanceUid_(false),
     updateReferencedRelationships_(true),
     isAnonymization_(false),
+    //privateCreator_("PrivateCreator"),
     identifierGenerator_(NULL)
   {
   }
@@ -1067,7 +1069,8 @@
     for (Replacements::const_iterator it = replacements_.begin(); 
          it != replacements_.end(); ++it)
     {
-      toModify.Replace(it->first, *it->second, true /* decode data URI scheme */, DicomReplaceMode_InsertIfAbsent);
+      toModify.Replace(it->first, *it->second, true /* decode data URI scheme */,
+                       DicomReplaceMode_InsertIfAbsent, privateCreator_);
     }
 
     // (6) Update the DICOM identifiers
@@ -1262,6 +1265,12 @@
     {
       ParseListOfTags(*this, request["Keep"], TagOperation_Keep, force);
     }
+
+    // New in Orthanc 1.6.0
+    if (request.isMember("PrivateCreator"))
+    {
+      privateCreator_ = SerializationToolbox::ReadString(request, "PrivateCreator");
+    }
   }
 
 
@@ -1316,6 +1325,12 @@
 
     patientNameReplaced = (IsReplaced(DICOM_TAG_PATIENT_NAME) &&
                            GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName);
+
+    // New in Orthanc 1.6.0
+    if (request.isMember("PrivateCreator"))
+    {
+      privateCreator_ = SerializationToolbox::ReadString(request, "PrivateCreator");
+    }
   }
 
 
@@ -1336,6 +1351,7 @@
   static const char* MAP_STUDIES = "MapStudies";
   static const char* MAP_SERIES = "MapSeries";
   static const char* MAP_INSTANCES = "MapInstances";
+  static const char* PRIVATE_CREATOR = "PrivateCreator";  // New in Orthanc 1.6.0
   
   void DicomModification::Serialize(Json::Value& value) const
   {
@@ -1353,6 +1369,7 @@
     value[KEEP_SERIES_INSTANCE_UID] = keepSeriesInstanceUid_;
     value[UPDATE_REFERENCED_RELATIONSHIPS] = updateReferencedRelationships_;
     value[IS_ANONYMIZATION] = isAnonymization_;
+    value[PRIVATE_CREATOR] = privateCreator_;
 
     SerializationToolbox::WriteSetOfTags(value, removals_, REMOVALS);
     SerializationToolbox::WriteSetOfTags(value, clearings_, CLEARINGS);
@@ -1451,6 +1468,11 @@
       (serialized, UPDATE_REFERENCED_RELATIONSHIPS);
     isAnonymization_ = SerializationToolbox::ReadBoolean(serialized, IS_ANONYMIZATION);
 
+    if (serialized.isMember(PRIVATE_CREATOR))
+    {
+      privateCreator_ = SerializationToolbox::ReadString(serialized, PRIVATE_CREATOR);
+    }
+
     SerializationToolbox::ReadSetOfTags(removals_, serialized, REMOVALS);
     SerializationToolbox::ReadSetOfTags(clearings_, serialized, CLEARINGS);
     SerializationToolbox::ReadSetOfTags(privateTagsToKeep_, serialized, PRIVATE_TAGS_TO_KEEP);
--- a/Core/DicomParsing/DicomModification.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomParsing/DicomModification.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -86,6 +86,7 @@
     bool updateReferencedRelationships_;
     bool isAnonymization_;
     DicomMap currentSource_;
+    std::string privateCreator_;
 
     IDicomIdentifierGenerator* identifierGenerator_;
 
@@ -185,5 +186,15 @@
     }
 
     void Serialize(Json::Value& value) const;
+
+    void SetPrivateCreator(const std::string& privateCreator)
+    {
+      privateCreator_ = privateCreator;
+    }
+
+    const std::string& GetPrivateCreator()
+    {
+      return privateCreator_;
+    }
   };
 }
--- a/Core/DicomParsing/DicomWebJsonVisitor.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomParsing/DicomWebJsonVisitor.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomParsing/DicomWebJsonVisitor.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomParsing/DicomWebJsonVisitor.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomParsing/FromDcmtkBridge.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomParsing/FromDcmtkBridge.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -47,6 +47,7 @@
 
 #include "FromDcmtkBridge.h"
 #include "ToDcmtkBridge.h"
+#include "../Compatibility.h"
 #include "../Logging.h"
 #include "../Toolbox.h"
 #include "../OrthancException.h"
@@ -67,10 +68,11 @@
 #include <dcmtk/dcmdata/dcdicent.h>
 #include <dcmtk/dcmdata/dcdict.h>
 #include <dcmtk/dcmdata/dcfilefo.h>
+#include <dcmtk/dcmdata/dcistrmb.h>
 #include <dcmtk/dcmdata/dcostrmb.h>
 #include <dcmtk/dcmdata/dcpixel.h>
 #include <dcmtk/dcmdata/dcuid.h>
-#include <dcmtk/dcmdata/dcistrmb.h>
+#include <dcmtk/dcmdata/dcxfer.h>
 
 #include <dcmtk/dcmdata/dcvrae.h>
 #include <dcmtk/dcmdata/dcvras.h>
@@ -106,15 +108,31 @@
 
 #if ORTHANC_ENABLE_DCMTK_JPEG == 1
 #  include <dcmtk/dcmjpeg/djdecode.h>
+#  if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+#    include <dcmtk/dcmjpeg/djencode.h>
+#  endif
 #endif
 
 #if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
 #  include <dcmtk/dcmjpls/djdecode.h>
+#  if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+#    include <dcmtk/dcmjpls/djencode.h>
+#  endif
 #endif
 
 
 namespace Orthanc
 {
+  static bool IsBinaryTag(const DcmTag& key)
+  {
+    return (key.isUnknownVR() ||
+            key.getEVR() == EVR_OB ||
+            key.getEVR() == EVR_OW ||
+            key.getEVR() == EVR_UN ||
+            key.getEVR() == EVR_ox);
+  }
+
+
 #if DCMTK_USE_EMBEDDED_DICTIONARIES == 1
   static void LoadEmbeddedDictionary(DcmDataDictionary& dictionary,
                                      EmbeddedResources::FileResourceId resource)
@@ -177,18 +195,18 @@
     };
 
     
-#define DCMTK_TO_CTYPE_CONVERTER(converter, cType, dcmtkType, getter) \
- \
-    struct converter \
-    { \
-      typedef cType CType; \
- \
-      static bool Apply(CType& result, \
-                        DcmElement& element, \
-                        size_t i) \
-      { \
+#define DCMTK_TO_CTYPE_CONVERTER(converter, cType, dcmtkType, getter)   \
+                                                                        \
+    struct converter                                                    \
+    {                                                                   \
+      typedef cType CType;                                              \
+                                                                        \
+      static bool Apply(CType& result,                                  \
+                        DcmElement& element,                            \
+                        size_t i)                                       \
+      {                                                                 \
         return dynamic_cast<dcmtkType&>(element).getter(result, i).good(); \
-      } \
+      }                                                                 \
     };
 
 DCMTK_TO_CTYPE_CONVERTER(DcmtkToSint32Converter, Sint32, DcmSignedLong, getSint32)
@@ -341,7 +359,7 @@
               << name << " (multiplicity: " << minMultiplicity << "-" 
               << (arbitrary ? "n" : boost::lexical_cast<std::string>(maxMultiplicity)) << ")";
 
-    std::auto_ptr<DcmDictEntry>  entry;
+    std::unique_ptr<DcmDictEntry>  entry;
     if (privateCreator.empty())
     {
       if (tag.GetGroup() % 2 == 1)
@@ -456,10 +474,9 @@
   void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target, 
                                             DcmItem& dataset,
                                             unsigned int maxStringLength,
-                                            Encoding defaultEncoding)
+                                            Encoding defaultEncoding,
+                                            const std::set<DicomTag>& ignoreTagLength)
   {
-    std::set<DicomTag> ignoreTagLength;
-
     bool hasCodeExtensions;
     Encoding encoding = DetectEncoding(hasCodeExtensions, dataset, defaultEncoding);
 
@@ -469,10 +486,10 @@
       DcmElement* element = dataset.getElement(i);
       if (element && element->isLeaf())
       {
-        target.SetValue(element->getTag().getGTag(),
-                        element->getTag().getETag(),
-                        ConvertLeafElement(*element, DicomToJsonFlags_Default,
-                                           maxStringLength, encoding, hasCodeExtensions, ignoreTagLength));
+        target.SetValueInternal(element->getTag().getGTag(),
+                                element->getTag().getETag(),
+                                ConvertLeafElement(*element, DicomToJsonFlags_Default,
+                                                   maxStringLength, encoding, hasCodeExtensions, ignoreTagLength));
       }
     }
   }
@@ -876,7 +893,7 @@
     if (element.isLeaf())
     {
       // The "0" below lets "LeafValueToJson()" take care of "TooLong" values
-      std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement
+      std::unique_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement
                                   (element, flags, 0, encoding, hasCodeExtensions, ignoreTagLength));
 
       if (ignoreTagLength.find(GetTag(element)) == ignoreTagLength.end())
@@ -939,18 +956,13 @@
       if (!(flags & DicomToJsonFlags_IncludeUnknownTags))
       {
         DictionaryLocker locker;
-        if (locker->findEntry(element->getTag(), NULL) == NULL)
+        if (locker->findEntry(element->getTag(), element->getTag().getPrivateCreator()) == NULL)
         {
           continue;
         }
       }
 
-      DcmEVR evr = element->getTag().getEVR();
-      if (evr == EVR_OB ||
-          evr == EVR_OF ||
-          evr == EVR_OW ||
-          evr == EVR_UN ||
-          evr == EVR_ox)
+      if (IsBinaryTag(element->getTag()))
       {
         // This is a binary tag
         if ((tag == DICOM_TAG_PIXEL_DATA && !(flags & DicomToJsonFlags_IncludePixelData)) ||
@@ -1118,8 +1130,8 @@
 
     result.clear();
 
-    for (DicomMap::Map::const_iterator 
-           it = values.map_.begin(); it != values.map_.end(); ++it)
+    for (DicomMap::Content::const_iterator 
+           it = values.content_.begin(); it != values.content_.end(); ++it)
     {
       // TODO Inject PrivateCreator if some is available in the DicomMap?
       const std::string tagName = GetTagName(it->first, "");
@@ -1372,189 +1384,49 @@
   }
 
 
-  static bool IsBinaryTag(const DcmTag& key)
+  DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag,
+                                                   const std::string& privateCreator)
   {
-    return (key.isUnknownVR() ||
-#if DCMTK_VERSION_NUMBER >= 361
-            key.getEVR() == EVR_OD ||
-#endif
-            
+    if (tag.IsPrivate() &&
+        privateCreator.empty())
+    {
+      // This solves issue 140 (Modifying private tags with REST API
+      // changes VR from LO to UN)
+      // https://bitbucket.org/sjodogne/orthanc/issues/140
+      LOG(WARNING) << "Private creator should not be empty while creating a private tag: " << tag.Format();
+    }
+    
 #if DCMTK_VERSION_NUMBER >= 362
-            key.getEVR() == EVR_OL ||
-#endif            
-            key.getEVR() == EVR_OB ||
-            key.getEVR() == EVR_OF ||
-            key.getEVR() == EVR_OW ||
-            key.getEVR() == EVR_UN ||
-            key.getEVR() == EVR_ox);
-  }
-
-
-  DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag)
-  {
     DcmTag key(tag.GetGroup(), tag.GetElement());
-
-    if (tag.IsPrivate() ||
-        IsBinaryTag(key))
+    if (tag.IsPrivate())
+    {
+      return DcmItem::newDicomElement(key, privateCreator.c_str());
+    }
+    else
     {
+      return DcmItem::newDicomElement(key, NULL);
+    }
+    
+#else
+    DcmTag key(tag.GetGroup(), tag.GetElement());
+    if (tag.IsPrivate())
+    {
+      // https://forum.dcmtk.org/viewtopic.php?t=4527
+      LOG(WARNING) << "You are using DCMTK <= 3.6.1: All the private tags "
+        "are considered as having a binary value representation";
+      key.setPrivateCreator(privateCreator.c_str());
       return new DcmOtherByteOtherWord(key);
     }
-
-    switch (key.getEVR())
+    else
     {
-      // http://support.dcmtk.org/docs/dcvr_8h-source.html
-
-      /**
-       * Binary types, handled above
-       **/
-    
-#if DCMTK_VERSION_NUMBER >= 361
-      case EVR_OD:
-#endif            
-
-#if DCMTK_VERSION_NUMBER >= 362
-      case EVR_OL:
-#endif            
-
-      case EVR_OB:  // other byte
-      case EVR_OF:  // other float
-      case EVR_OW:  // other word
-      case EVR_UN:  // unknown value representation
-      case EVR_ox:  // OB or OW depending on context
-        throw OrthancException(ErrorCode_InternalError);
-
-
-      /**
-       * String types.
-       * http://support.dcmtk.org/docs/classDcmByteString.html
-       **/
-      
-      case EVR_AS:  // age string
-        return new DcmAgeString(key);
-
-      case EVR_AE:  // application entity title
-        return new DcmApplicationEntity(key);
-
-      case EVR_CS:  // code string
-        return new DcmCodeString(key);        
-
-      case EVR_DA:  // date string
-        return new DcmDate(key);
-        
-      case EVR_DT:  // date time string
-        return new DcmDateTime(key);
-
-      case EVR_DS:  // decimal string
-        return new DcmDecimalString(key);
-
-      case EVR_IS:  // integer string
-        return new DcmIntegerString(key);
-
-      case EVR_TM:  // time string
-        return new DcmTime(key);
-
-      case EVR_UI:  // unique identifier
-        return new DcmUniqueIdentifier(key);
-
-      case EVR_ST:  // short text
-        return new DcmShortText(key);
-
-      case EVR_LO:  // long string
-        return new DcmLongString(key);
-
-      case EVR_LT:  // long text
-        return new DcmLongText(key);
-
-      case EVR_UT:  // unlimited text
-        return new DcmUnlimitedText(key);
-
-      case EVR_SH:  // short string
-        return new DcmShortString(key);
-
-      case EVR_PN:  // person name
-        return new DcmPersonName(key);
-
-#if DCMTK_VERSION_NUMBER >= 361
-      case EVR_UC:  // unlimited characters
-        return new DcmUnlimitedCharacters(key);
-#endif
-
-#if DCMTK_VERSION_NUMBER >= 361
-      case EVR_UR:  // URI/URL
-        return new DcmUniversalResourceIdentifierOrLocator(key);
-#endif
-          
-        
-      /**
-       * Numerical types
-       **/ 
-      
-      case EVR_SL:  // signed long
-        return new DcmSignedLong(key);
-
-      case EVR_SS:  // signed short
-        return new DcmSignedShort(key);
-
-      case EVR_UL:  // unsigned long
-        return new DcmUnsignedLong(key);
-
-      case EVR_US:  // unsigned short
-        return new DcmUnsignedShort(key);
-
-      case EVR_FL:  // float single-precision
-        return new DcmFloatingPointSingle(key);
-
-      case EVR_FD:  // float double-precision
-        return new DcmFloatingPointDouble(key);
-
-
-      /**
-       * Sequence types, should never occur at this point.
-       **/
-
-      case EVR_SQ:  // sequence of items
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-
-
-      /**
-       * TODO
-       **/
-
-      case EVR_AT:  // attribute tag
-        throw OrthancException(ErrorCode_NotImplemented);
-
-
-      /**
-       * Internal to DCMTK.
-       **/ 
-
-      case EVR_xs:  // SS or US depending on context
-      case EVR_lt:  // US, SS or OW depending on context, used for LUT Data (thus the name)
-      case EVR_na:  // na="not applicable", for data which has no VR
-      case EVR_up:  // up="unsigned pointer", used internally for DICOMDIR suppor
-      case EVR_item:  // used internally for items
-      case EVR_metainfo:  // used internally for meta info datasets
-      case EVR_dataset:  // used internally for datasets
-      case EVR_fileFormat:  // used internally for DICOM files
-      case EVR_dicomDir:  // used internally for DICOMDIR objects
-      case EVR_dirRecord:  // used internally for DICOMDIR records
-      case EVR_pixelSQ:  // used internally for pixel sequences in a compressed image
-      case EVR_pixelItem:  // used internally for pixel items in a compressed image
-      case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
-      case EVR_PixelData:  // used internally for uncompressed pixeld data
-      case EVR_OverlayData:  // used internally for overlay data
-      case EVR_UNKNOWN2B:  // used internally for elements with unknown VR with 2-byte length field in explicit VR
-      default:
-        break;
+      return newDicomElement(key);
     }
-
-    throw OrthancException(ErrorCode_InternalError);          
+#endif      
   }
 
 
 
   void FromDcmtkBridge::FillElementWithString(DcmElement& element,
-                                              const DicomTag& tag,
                                               const std::string& utf8Value,
                                               bool decodeDataUriScheme,
                                               Encoding dicomEncoding)
@@ -1579,14 +1451,11 @@
       decoded = &binary;
     }
 
-    DcmTag key(tag.GetGroup(), tag.GetElement());
-
-    if (tag.IsPrivate() ||
-        IsBinaryTag(key))
+    if (IsBinaryTag(element.getTag()))
     {
       bool ok;
 
-      switch (key.getEVR())
+      switch (element.getTag().getEVR())
       {
         case EVR_OW:
           if (decoded->size() % sizeof(Uint16) != 0)
@@ -1620,7 +1489,7 @@
     
     try
     {
-      switch (key.getEVR())
+      switch (element.getTag().getEVR())
       {
         // http://support.dcmtk.org/docs/dcvr_8h-source.html
 
@@ -1629,7 +1498,6 @@
          **/
 
         case EVR_OB:  // other byte
-        case EVR_OF:  // other float
         case EVR_OW:  // other word
         case EVR_AT:  // attribute tag
           throw OrthancException(ErrorCode_NotImplemented);
@@ -1684,6 +1552,9 @@
         }
 
         case EVR_UL:  // unsigned long
+#if DCMTK_VERSION_NUMBER >= 362
+        case EVR_OL:  // other long (requires byte-swapping)
+#endif
         {
           ok = element.putUint32(boost::lexical_cast<Uint32>(*decoded)).good();
           break;
@@ -1696,12 +1567,16 @@
         }
 
         case EVR_FL:  // float single-precision
+        case EVR_OF:  // other float (requires byte swapping)
         {
           ok = element.putFloat32(boost::lexical_cast<float>(*decoded)).good();
           break;
         }
 
         case EVR_FD:  // float double-precision
+#if DCMTK_VERSION_NUMBER >= 361
+        case EVR_OD:  // other double (requires byte-swapping)
+#endif
         {
           ok = element.putFloat64(boost::lexical_cast<double>(*decoded)).good();
           break;
@@ -1751,6 +1626,7 @@
 
     if (!ok)
     {
+      DicomTag tag(element.getTag().getGroup(), element.getTag().getElement());
       throw OrthancException(ErrorCode_BadFileFormat,
                              "While creating a DICOM instance, tag (" + tag.Format() +
                              ") has out-of-range value: \"" + (*decoded) + "\"");
@@ -1761,20 +1637,21 @@
   DcmElement* FromDcmtkBridge::FromJson(const DicomTag& tag,
                                         const Json::Value& value,
                                         bool decodeDataUriScheme,
-                                        Encoding dicomEncoding)
+                                        Encoding dicomEncoding,
+                                        const std::string& privateCreator)
   {
-    std::auto_ptr<DcmElement> element;
+    std::unique_ptr<DcmElement> element;
 
     switch (value.type())
     {
       case Json::stringValue:
-        element.reset(CreateElementForTag(tag));
-        FillElementWithString(*element, tag, value.asString(), decodeDataUriScheme, dicomEncoding);
+        element.reset(CreateElementForTag(tag, privateCreator));
+        FillElementWithString(*element, value.asString(), decodeDataUriScheme, dicomEncoding);
         break;
 
       case Json::nullValue:
-        element.reset(CreateElementForTag(tag));
-        FillElementWithString(*element, tag, "", decodeDataUriScheme, dicomEncoding);
+        element.reset(CreateElementForTag(tag, privateCreator));
+        FillElementWithString(*element, "", decodeDataUriScheme, dicomEncoding);
         break;
 
       case Json::arrayValue:
@@ -1790,7 +1667,7 @@
         
         for (Json::Value::ArrayIndex i = 0; i < value.size(); i++)
         {
-          std::auto_ptr<DcmItem> item(new DcmItem);
+          std::unique_ptr<DcmItem> item(new DcmItem);
 
           switch (value[i].type())
           {
@@ -1799,7 +1676,7 @@
               Json::Value::Members members = value[i].getMemberNames();
               for (Json::Value::ArrayIndex j = 0; j < members.size(); j++)
               {
-                item->insert(FromJson(ParseTag(members[j]), value[i][members[j]], decodeDataUriScheme, dicomEncoding));
+                item->insert(FromJson(ParseTag(members[j]), value[i][members[j]], decodeDataUriScheme, dicomEncoding, privateCreator));
               }
               break;
             }
@@ -1908,9 +1785,10 @@
   DcmDataset* FromDcmtkBridge::FromJson(const Json::Value& json,  // Encoded using UTF-8
                                         bool generateIdentifiers,
                                         bool decodeDataUriScheme,
-                                        Encoding defaultEncoding)
+                                        Encoding defaultEncoding,
+                                        const std::string& privateCreator)
   {
-    std::auto_ptr<DcmDataset> result(new DcmDataset);
+    std::unique_ptr<DcmDataset> result(new DcmDataset);
     Encoding encoding = ExtractEncoding(json, defaultEncoding);
 
     SetString(*result, DCM_SpecificCharacterSet, GetDicomSpecificCharacterSet(encoding));
@@ -1946,7 +1824,7 @@
 
       if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET)
       {
-        std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding));
+        std::unique_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding, privateCreator));
         const DcmTagKey& tag = element->getTag();
 
         result->findAndDeleteElement(tag);
@@ -1998,10 +1876,18 @@
     }
     is.setEos();
 
-    std::auto_ptr<DcmFileFormat> result(new DcmFileFormat);
+    std::unique_ptr<DcmFileFormat> result(new DcmFileFormat);
 
     result->transferInit();
-    if (!result->read(is).good())
+
+    /**
+     * New in Orthanc 1.6.0: The "size" is given as an argument to the
+     * "read()" method. This can avoid huge memory consumption if
+     * parsing an invalid DICOM file, which can notably been observed
+     * by executing the integration test "test_upload_compressed" on
+     * valgrind running Orthanc.
+     **/
+    if (!result->read(is, EXS_Unknown, EGL_noChange, size).good())
     {
       throw OrthancException(ErrorCode_BadFileFormat,
                              "Cannot parse an invalid DICOM file (size: " +
@@ -2148,11 +2034,12 @@
 
 
   void FromDcmtkBridge::ExtractDicomSummary(DicomMap& target, 
-                                            DcmItem& dataset)
+                                            DcmItem& dataset,
+                                            const std::set<DicomTag>& ignoreTagLength)
   {
     ExtractDicomSummary(target, dataset,
                         ORTHANC_MAXIMUM_TAG_LENGTH,
-                        GetDefaultDicomEncoding());
+                        GetDefaultDicomEncoding(), ignoreTagLength);
   }
 
   
@@ -2173,12 +2060,18 @@
   {
 #if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
     LOG(INFO) << "Registering JPEG Lossless codecs in DCMTK";
-    DJLSDecoderRegistration::registerCodecs();    
+    DJLSDecoderRegistration::registerCodecs();
+# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+    DJLSEncoderRegistration::registerCodecs();
+# endif
 #endif
 
 #if ORTHANC_ENABLE_DCMTK_JPEG == 1
     LOG(INFO) << "Registering JPEG codecs in DCMTK";
     DJDecoderRegistration::registerCodecs(); 
+# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+    DJEncoderRegistration::registerCodecs();
+# endif
 #endif
   }
 
@@ -2188,11 +2081,17 @@
 #if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
     // Unregister JPEG-LS codecs
     DJLSDecoderRegistration::cleanup();
+# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+    DJLSEncoderRegistration::cleanup();
+# endif
 #endif
 
 #if ORTHANC_ENABLE_DCMTK_JPEG == 1
     // Unregister JPEG codecs
     DJDecoderRegistration::cleanup();
+# if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+    DJEncoderRegistration::cleanup();
+# endif
 #endif
   }
 
@@ -2268,13 +2167,6 @@
      **/
 
     if (evr == EVR_OB ||  // other byte
-        evr == EVR_OF ||  // other float
-#if DCMTK_VERSION_NUMBER >= 361
-        evr == EVR_OD ||  // other double
-#endif
-#if DCMTK_VERSION_NUMBER >= 362
-        evr == EVR_OL ||  // other long
-#endif
         evr == EVR_OW ||  // other word
         evr == EVR_UN)    // unknown value representation
     {
@@ -2464,6 +2356,9 @@
         }
 
         case EVR_UL:  // unsigned long
+#if DCMTK_VERSION_NUMBER >= 362
+        case EVR_OL:
+#endif
         {
           DcmUnsignedLong& content = dynamic_cast<DcmUnsignedLong&>(element);
 
@@ -2504,6 +2399,7 @@
         }
 
         case EVR_FL:  // float single-precision
+        case EVR_OF:
         {
           DcmFloatingPointSingle& content = dynamic_cast<DcmFloatingPointSingle&>(element);
 
@@ -2524,6 +2420,9 @@
         }
 
         case EVR_FD:  // float double-precision
+#if DCMTK_VERSION_NUMBER >= 361
+        case EVR_OD:
+#endif
         {
           DcmFloatingPointDouble& content = dynamic_cast<DcmFloatingPointDouble&>(element);
 
@@ -2680,3 +2579,6 @@
     ApplyVisitorToDataset(dataset, visitor, parentTags, parentIndexes, encoding, hasCodeExtensions);
   }
 }
+
+
+#include "./FromDcmtkBridge_TransferSyntaxes.impl.h"
--- a/Core/DicomParsing/FromDcmtkBridge.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomParsing/FromDcmtkBridge.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -84,7 +84,8 @@
     static void ExtractDicomSummary(DicomMap& target, 
                                     DcmItem& dataset,
                                     unsigned int maxStringLength,
-                                    Encoding defaultEncoding);
+                                    Encoding defaultEncoding,
+                                    const std::set<DicomTag>& ignoreTagLength);
 
     static void DatasetToJson(Json::Value& parent,
                               DcmItem& item,
@@ -191,7 +192,8 @@
                          const std::string& tagName,
                          DicomValue* value)
     {
-      target.SetValue(ParseTag(tagName), value);
+      const DicomTag tag = ParseTag(tagName);
+      target.SetValueInternal(tag.GetGroup(), tag.GetElement(), value);
     }
 
     static void ToJson(Json::Value& result,
@@ -207,10 +209,10 @@
 
     static ValueRepresentation LookupValueRepresentation(const DicomTag& tag);
 
-    static DcmElement* CreateElementForTag(const DicomTag& tag);
+    static DcmElement* CreateElementForTag(const DicomTag& tag,
+                                           const std::string& privateCreator);
     
     static void FillElementWithString(DcmElement& element,
-                                      const DicomTag& tag,
                                       const std::string& utf8alue,  // Encoded using UTF-8
                                       bool decodeDataUriScheme,
                                       Encoding dicomEncoding);
@@ -218,7 +220,8 @@
     static DcmElement* FromJson(const DicomTag& tag,
                                 const Json::Value& element,  // Encoded using UTF-8
                                 bool decodeDataUriScheme,
-                                Encoding dicomEncoding);
+                                Encoding dicomEncoding,
+                                const std::string& privateCreator);
 
     static DcmPixelSequence* GetPixelSequence(DcmDataset& dataset);
 
@@ -228,7 +231,8 @@
     static DcmDataset* FromJson(const Json::Value& json,  // Encoded using UTF-8
                                 bool generateIdentifiers,
                                 bool decodeDataUriScheme,
-                                Encoding defaultEncoding);
+                                Encoding defaultEncoding,
+                                const std::string& privateCreator);
 
     static DcmFileFormat* LoadFromMemoryBuffer(const void* buffer,
                                                size_t size);
@@ -245,7 +249,15 @@
 #endif
 
     static void ExtractDicomSummary(DicomMap& target, 
-                                    DcmItem& dataset);
+                                    DcmItem& dataset,
+                                    const std::set<DicomTag>& ignoreTagLength);
+
+    static void ExtractDicomSummary(DicomMap& target, 
+                                    DcmItem& dataset)
+    {
+      std::set<DicomTag> none;
+      ExtractDicomSummary(target, dataset, none);
+    }
 
     static void ExtractDicomAsJson(Json::Value& target, 
                                    DcmDataset& dataset,
@@ -258,5 +270,11 @@
     static void Apply(DcmItem& dataset,
                       ITagVisitor& visitor,
                       Encoding defaultEncoding);
+
+    static bool LookupDcmtkTransferSyntax(E_TransferSyntax& target,
+                                          DicomTransferSyntax source);
+
+    static bool LookupOrthancTransferSyntax(DicomTransferSyntax& target,
+                                            E_TransferSyntax source);
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/FromDcmtkBridge_TransferSyntaxes.impl.h	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,549 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+// This file is autogenerated by "../Resources/GenerateTransferSyntaxes.py"
+
+namespace Orthanc
+{
+  bool FromDcmtkBridge::LookupDcmtkTransferSyntax(E_TransferSyntax& target,
+                                                  DicomTransferSyntax source)
+  {
+    switch (source)
+    {
+      case DicomTransferSyntax_LittleEndianImplicit:
+        target = EXS_LittleEndianImplicit;
+        return true;
+
+      case DicomTransferSyntax_LittleEndianExplicit:
+        target = EXS_LittleEndianExplicit;
+        return true;
+
+      case DicomTransferSyntax_DeflatedLittleEndianExplicit:
+        target = EXS_DeflatedLittleEndianExplicit;
+        return true;
+
+      case DicomTransferSyntax_BigEndianExplicit:
+        target = EXS_BigEndianExplicit;
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess1:
+#  if DCMTK_VERSION_NUMBER <= 360
+        target = EXS_JPEGProcess1TransferSyntax;
+#  else
+        target = EXS_JPEGProcess1;
+#  endif
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess2_4:
+#  if DCMTK_VERSION_NUMBER <= 360
+        target = EXS_JPEGProcess2_4TransferSyntax;
+#  else
+        target = EXS_JPEGProcess2_4;
+#  endif
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess3_5:
+#  if DCMTK_VERSION_NUMBER <= 360
+        target = EXS_JPEGProcess3_5TransferSyntax;
+#  else
+        target = EXS_JPEGProcess3_5;
+#  endif
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess6_8:
+#  if DCMTK_VERSION_NUMBER <= 360
+        target = EXS_JPEGProcess6_8TransferSyntax;
+#  else
+        target = EXS_JPEGProcess6_8;
+#  endif
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess7_9:
+#  if DCMTK_VERSION_NUMBER <= 360
+        target = EXS_JPEGProcess7_9TransferSyntax;
+#  else
+        target = EXS_JPEGProcess7_9;
+#  endif
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess10_12:
+#  if DCMTK_VERSION_NUMBER <= 360
+        target = EXS_JPEGProcess10_12TransferSyntax;
+#  else
+        target = EXS_JPEGProcess10_12;
+#  endif
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess11_13:
+#  if DCMTK_VERSION_NUMBER <= 360
+        target = EXS_JPEGProcess11_13TransferSyntax;
+#  else
+        target = EXS_JPEGProcess11_13;
+#  endif
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess14:
+#  if DCMTK_VERSION_NUMBER <= 360
+        target = EXS_JPEGProcess14TransferSyntax;
+#  else
+        target = EXS_JPEGProcess14;
+#  endif
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess15:
+#  if DCMTK_VERSION_NUMBER <= 360
+        target = EXS_JPEGProcess15TransferSyntax;
+#  else
+        target = EXS_JPEGProcess15;
+#  endif
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess16_18:
+#  if DCMTK_VERSION_NUMBER <= 360
+        target = EXS_JPEGProcess16_18TransferSyntax;
+#  else
+        target = EXS_JPEGProcess16_18;
+#  endif
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess17_19:
+#  if DCMTK_VERSION_NUMBER <= 360
+        target = EXS_JPEGProcess17_19TransferSyntax;
+#  else
+        target = EXS_JPEGProcess17_19;
+#  endif
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess20_22:
+#  if DCMTK_VERSION_NUMBER <= 360
+        target = EXS_JPEGProcess20_22TransferSyntax;
+#  else
+        target = EXS_JPEGProcess20_22;
+#  endif
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess21_23:
+#  if DCMTK_VERSION_NUMBER <= 360
+        target = EXS_JPEGProcess21_23TransferSyntax;
+#  else
+        target = EXS_JPEGProcess21_23;
+#  endif
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess24_26:
+#  if DCMTK_VERSION_NUMBER <= 360
+        target = EXS_JPEGProcess24_26TransferSyntax;
+#  else
+        target = EXS_JPEGProcess24_26;
+#  endif
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess25_27:
+#  if DCMTK_VERSION_NUMBER <= 360
+        target = EXS_JPEGProcess25_27TransferSyntax;
+#  else
+        target = EXS_JPEGProcess25_27;
+#  endif
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess28:
+#  if DCMTK_VERSION_NUMBER <= 360
+        target = EXS_JPEGProcess28TransferSyntax;
+#  else
+        target = EXS_JPEGProcess28;
+#  endif
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess29:
+#  if DCMTK_VERSION_NUMBER <= 360
+        target = EXS_JPEGProcess29TransferSyntax;
+#  else
+        target = EXS_JPEGProcess29;
+#  endif
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess14SV1:
+#  if DCMTK_VERSION_NUMBER <= 360
+        target = EXS_JPEGProcess14SV1TransferSyntax;
+#  else
+        target = EXS_JPEGProcess14SV1;
+#  endif
+        return true;
+
+      case DicomTransferSyntax_JPEGLSLossless:
+        target = EXS_JPEGLSLossless;
+        return true;
+
+      case DicomTransferSyntax_JPEGLSLossy:
+        target = EXS_JPEGLSLossy;
+        return true;
+
+      case DicomTransferSyntax_JPEG2000LosslessOnly:
+        target = EXS_JPEG2000LosslessOnly;
+        return true;
+
+      case DicomTransferSyntax_JPEG2000:
+        target = EXS_JPEG2000;
+        return true;
+
+      case DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly:
+        target = EXS_JPEG2000MulticomponentLosslessOnly;
+        return true;
+
+      case DicomTransferSyntax_JPEG2000Multicomponent:
+        target = EXS_JPEG2000Multicomponent;
+        return true;
+
+      case DicomTransferSyntax_JPIPReferenced:
+        target = EXS_JPIPReferenced;
+        return true;
+
+      case DicomTransferSyntax_JPIPReferencedDeflate:
+        target = EXS_JPIPReferencedDeflate;
+        return true;
+
+      case DicomTransferSyntax_MPEG2MainProfileAtMainLevel:
+        target = EXS_MPEG2MainProfileAtMainLevel;
+        return true;
+
+      case DicomTransferSyntax_MPEG2MainProfileAtHighLevel:
+        target = EXS_MPEG2MainProfileAtHighLevel;
+        return true;
+
+#if DCMTK_VERSION_NUMBER >= 361
+      case DicomTransferSyntax_MPEG4HighProfileLevel4_1:
+        target = EXS_MPEG4HighProfileLevel4_1;
+        return true;
+#endif
+
+#if DCMTK_VERSION_NUMBER >= 361
+      case DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1:
+        target = EXS_MPEG4BDcompatibleHighProfileLevel4_1;
+        return true;
+#endif
+
+#if DCMTK_VERSION_NUMBER >= 361
+      case DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo:
+        target = EXS_MPEG4HighProfileLevel4_2_For2DVideo;
+        return true;
+#endif
+
+#if DCMTK_VERSION_NUMBER >= 361
+      case DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo:
+        target = EXS_MPEG4HighProfileLevel4_2_For3DVideo;
+        return true;
+#endif
+
+#if DCMTK_VERSION_NUMBER >= 361
+      case DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2:
+        target = EXS_MPEG4StereoHighProfileLevel4_2;
+        return true;
+#endif
+
+#if DCMTK_VERSION_NUMBER >= 362
+      case DicomTransferSyntax_HEVCMainProfileLevel5_1:
+        target = EXS_HEVCMainProfileLevel5_1;
+        return true;
+#endif
+
+#if DCMTK_VERSION_NUMBER >= 362
+      case DicomTransferSyntax_HEVCMain10ProfileLevel5_1:
+        target = EXS_HEVCMain10ProfileLevel5_1;
+        return true;
+#endif
+
+      case DicomTransferSyntax_RLELossless:
+        target = EXS_RLELossless;
+        return true;
+
+      default:
+        return false;
+    }
+  }
+  
+
+  bool FromDcmtkBridge::LookupOrthancTransferSyntax(DicomTransferSyntax& target,
+                                                    E_TransferSyntax source)
+  {
+    switch (source)
+    {
+      case EXS_LittleEndianImplicit:
+        target = DicomTransferSyntax_LittleEndianImplicit;
+        return true;
+
+      case EXS_LittleEndianExplicit:
+        target = DicomTransferSyntax_LittleEndianExplicit;
+        return true;
+
+      case EXS_DeflatedLittleEndianExplicit:
+        target = DicomTransferSyntax_DeflatedLittleEndianExplicit;
+        return true;
+
+      case EXS_BigEndianExplicit:
+        target = DicomTransferSyntax_BigEndianExplicit;
+        return true;
+
+#  if DCMTK_VERSION_NUMBER <= 360
+      case EXS_JPEGProcess1TransferSyntax:
+#  else
+      case EXS_JPEGProcess1:
+#  endif
+        target = DicomTransferSyntax_JPEGProcess1;
+        return true;
+
+#  if DCMTK_VERSION_NUMBER <= 360
+      case EXS_JPEGProcess2_4TransferSyntax:
+#  else
+      case EXS_JPEGProcess2_4:
+#  endif
+        target = DicomTransferSyntax_JPEGProcess2_4;
+        return true;
+
+#  if DCMTK_VERSION_NUMBER <= 360
+      case EXS_JPEGProcess3_5TransferSyntax:
+#  else
+      case EXS_JPEGProcess3_5:
+#  endif
+        target = DicomTransferSyntax_JPEGProcess3_5;
+        return true;
+
+#  if DCMTK_VERSION_NUMBER <= 360
+      case EXS_JPEGProcess6_8TransferSyntax:
+#  else
+      case EXS_JPEGProcess6_8:
+#  endif
+        target = DicomTransferSyntax_JPEGProcess6_8;
+        return true;
+
+#  if DCMTK_VERSION_NUMBER <= 360
+      case EXS_JPEGProcess7_9TransferSyntax:
+#  else
+      case EXS_JPEGProcess7_9:
+#  endif
+        target = DicomTransferSyntax_JPEGProcess7_9;
+        return true;
+
+#  if DCMTK_VERSION_NUMBER <= 360
+      case EXS_JPEGProcess10_12TransferSyntax:
+#  else
+      case EXS_JPEGProcess10_12:
+#  endif
+        target = DicomTransferSyntax_JPEGProcess10_12;
+        return true;
+
+#  if DCMTK_VERSION_NUMBER <= 360
+      case EXS_JPEGProcess11_13TransferSyntax:
+#  else
+      case EXS_JPEGProcess11_13:
+#  endif
+        target = DicomTransferSyntax_JPEGProcess11_13;
+        return true;
+
+#  if DCMTK_VERSION_NUMBER <= 360
+      case EXS_JPEGProcess14TransferSyntax:
+#  else
+      case EXS_JPEGProcess14:
+#  endif
+        target = DicomTransferSyntax_JPEGProcess14;
+        return true;
+
+#  if DCMTK_VERSION_NUMBER <= 360
+      case EXS_JPEGProcess15TransferSyntax:
+#  else
+      case EXS_JPEGProcess15:
+#  endif
+        target = DicomTransferSyntax_JPEGProcess15;
+        return true;
+
+#  if DCMTK_VERSION_NUMBER <= 360
+      case EXS_JPEGProcess16_18TransferSyntax:
+#  else
+      case EXS_JPEGProcess16_18:
+#  endif
+        target = DicomTransferSyntax_JPEGProcess16_18;
+        return true;
+
+#  if DCMTK_VERSION_NUMBER <= 360
+      case EXS_JPEGProcess17_19TransferSyntax:
+#  else
+      case EXS_JPEGProcess17_19:
+#  endif
+        target = DicomTransferSyntax_JPEGProcess17_19;
+        return true;
+
+#  if DCMTK_VERSION_NUMBER <= 360
+      case EXS_JPEGProcess20_22TransferSyntax:
+#  else
+      case EXS_JPEGProcess20_22:
+#  endif
+        target = DicomTransferSyntax_JPEGProcess20_22;
+        return true;
+
+#  if DCMTK_VERSION_NUMBER <= 360
+      case EXS_JPEGProcess21_23TransferSyntax:
+#  else
+      case EXS_JPEGProcess21_23:
+#  endif
+        target = DicomTransferSyntax_JPEGProcess21_23;
+        return true;
+
+#  if DCMTK_VERSION_NUMBER <= 360
+      case EXS_JPEGProcess24_26TransferSyntax:
+#  else
+      case EXS_JPEGProcess24_26:
+#  endif
+        target = DicomTransferSyntax_JPEGProcess24_26;
+        return true;
+
+#  if DCMTK_VERSION_NUMBER <= 360
+      case EXS_JPEGProcess25_27TransferSyntax:
+#  else
+      case EXS_JPEGProcess25_27:
+#  endif
+        target = DicomTransferSyntax_JPEGProcess25_27;
+        return true;
+
+#  if DCMTK_VERSION_NUMBER <= 360
+      case EXS_JPEGProcess28TransferSyntax:
+#  else
+      case EXS_JPEGProcess28:
+#  endif
+        target = DicomTransferSyntax_JPEGProcess28;
+        return true;
+
+#  if DCMTK_VERSION_NUMBER <= 360
+      case EXS_JPEGProcess29TransferSyntax:
+#  else
+      case EXS_JPEGProcess29:
+#  endif
+        target = DicomTransferSyntax_JPEGProcess29;
+        return true;
+
+#  if DCMTK_VERSION_NUMBER <= 360
+      case EXS_JPEGProcess14SV1TransferSyntax:
+#  else
+      case EXS_JPEGProcess14SV1:
+#  endif
+        target = DicomTransferSyntax_JPEGProcess14SV1;
+        return true;
+
+      case EXS_JPEGLSLossless:
+        target = DicomTransferSyntax_JPEGLSLossless;
+        return true;
+
+      case EXS_JPEGLSLossy:
+        target = DicomTransferSyntax_JPEGLSLossy;
+        return true;
+
+      case EXS_JPEG2000LosslessOnly:
+        target = DicomTransferSyntax_JPEG2000LosslessOnly;
+        return true;
+
+      case EXS_JPEG2000:
+        target = DicomTransferSyntax_JPEG2000;
+        return true;
+
+      case EXS_JPEG2000MulticomponentLosslessOnly:
+        target = DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly;
+        return true;
+
+      case EXS_JPEG2000Multicomponent:
+        target = DicomTransferSyntax_JPEG2000Multicomponent;
+        return true;
+
+      case EXS_JPIPReferenced:
+        target = DicomTransferSyntax_JPIPReferenced;
+        return true;
+
+      case EXS_JPIPReferencedDeflate:
+        target = DicomTransferSyntax_JPIPReferencedDeflate;
+        return true;
+
+      case EXS_MPEG2MainProfileAtMainLevel:
+        target = DicomTransferSyntax_MPEG2MainProfileAtMainLevel;
+        return true;
+
+      case EXS_MPEG2MainProfileAtHighLevel:
+        target = DicomTransferSyntax_MPEG2MainProfileAtHighLevel;
+        return true;
+
+#if DCMTK_VERSION_NUMBER >= 361
+      case EXS_MPEG4HighProfileLevel4_1:
+        target = DicomTransferSyntax_MPEG4HighProfileLevel4_1;
+        return true;
+#endif
+
+#if DCMTK_VERSION_NUMBER >= 361
+      case EXS_MPEG4BDcompatibleHighProfileLevel4_1:
+        target = DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1;
+        return true;
+#endif
+
+#if DCMTK_VERSION_NUMBER >= 361
+      case EXS_MPEG4HighProfileLevel4_2_For2DVideo:
+        target = DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo;
+        return true;
+#endif
+
+#if DCMTK_VERSION_NUMBER >= 361
+      case EXS_MPEG4HighProfileLevel4_2_For3DVideo:
+        target = DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo;
+        return true;
+#endif
+
+#if DCMTK_VERSION_NUMBER >= 361
+      case EXS_MPEG4StereoHighProfileLevel4_2:
+        target = DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2;
+        return true;
+#endif
+
+#if DCMTK_VERSION_NUMBER >= 362
+      case EXS_HEVCMainProfileLevel5_1:
+        target = DicomTransferSyntax_HEVCMainProfileLevel5_1;
+        return true;
+#endif
+
+#if DCMTK_VERSION_NUMBER >= 362
+      case EXS_HEVCMain10ProfileLevel5_1:
+        target = DicomTransferSyntax_HEVCMain10ProfileLevel5_1;
+        return true;
+#endif
+
+      case EXS_RLELossless:
+        target = DicomTransferSyntax_RLELossless;
+        return true;
+
+      default:
+        return false;
+    }
+  }
+}
--- a/Core/DicomParsing/ITagVisitor.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomParsing/ITagVisitor.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -71,7 +71,7 @@
                                ValueRepresentation vr,
                                const std::vector<int64_t>& values) = 0;
 
-    // FL, FD
+    // FL, FD, OD, OF
     virtual void VisitDoubles(const std::vector<DicomTag>& parentTags,
                               const std::vector<size_t>& parentIndexes,
                               const DicomTag& tag,
@@ -84,7 +84,7 @@
                                  const DicomTag& tag,
                                  const std::vector<DicomTag>& values) = 0;
 
-    // OB, OD, OF, OL, OW, UN
+    // OB, OL, OW, UN
     virtual void VisitBinary(const std::vector<DicomTag>& parentTags,
                              const std::vector<size_t>& parentIndexes,
                              const DicomTag& tag,
--- a/Core/DicomParsing/Internals/DicomFrameIndex.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomParsing/Internals/DicomFrameIndex.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -68,7 +68,10 @@
       uint32_t length = item->getLength();
       if (length == 0)
       {
-        table.clear();
+        // Degenerate case: Empty offset table means only one frame
+        // that overlaps all the fragments
+        table.resize(1);
+        table[0] = 0;
         return;
       }
 
@@ -146,7 +149,6 @@
         throw OrthancException(ErrorCode_BadFileFormat);
       }
 
-
       // Loop over the fragments (ignoring the offset table). This is
       // an alternative, faster implementation to DCMTK's
       // "DcmCodec::determineStartFragment()".
@@ -318,46 +320,10 @@
   };
 
 
-
-  bool DicomFrameIndex::IsVideo(DcmFileFormat& dicom)
+  unsigned int DicomFrameIndex::GetFramesCount(DcmDataset& dicom)
   {
-    // Retrieve the transfer syntax from the DICOM header
-    const char* value = NULL;
-    if (!dicom.getMetaInfo()->findAndGetString(DCM_TransferSyntaxUID, value).good() ||
-        value == NULL)
-    {
-      return false;
-    }
-
-    const std::string transferSyntax(value);
-
-    // Video standards supported in DICOM 2016a
-    // http://dicom.nema.org/medical/dicom/2016a/output/html/part05.html
-    if (transferSyntax == "1.2.840.10008.1.2.4.100" ||  // MPEG2 MP@ML option of ISO/IEC MPEG2
-        transferSyntax == "1.2.840.10008.1.2.4.101" ||  // MPEG2 MP@HL option of ISO/IEC MPEG2
-        transferSyntax == "1.2.840.10008.1.2.4.102" ||  // MPEG-4 AVC/H.264 High Profile / Level 4.1 of ITU-T H.264
-        transferSyntax == "1.2.840.10008.1.2.4.103" ||  // MPEG-4 AVC/H.264 BD-compat High Profile / Level 4.1 of ITU-T H.264
-        transferSyntax == "1.2.840.10008.1.2.4.104" ||  // MPEG-4 AVC/H.264 High Profile / Level 4.2 of ITU-T H.264
-        transferSyntax == "1.2.840.10008.1.2.4.105" ||  // MPEG-4 AVC/H.264 High Profile / Level 4.2 of ITU-T H.264
-        transferSyntax == "1.2.840.10008.1.2.4.106")    // MPEG-4 AVC/H.264 Stereo High Profile / Level 4.2 of the ITU-T H.264
-    {
-      return true;
-    }
-
-    return false;
-  }
-
-
-  unsigned int DicomFrameIndex::GetFramesCount(DcmFileFormat& dicom)
-  {
-    // Assume 1 frame for video transfer syntaxes
-    if (IsVideo(dicom))
-    {
-      return 1;
-    }        
-
     const char* tmp = NULL;
-    if (!dicom.getDataset()->findAndGetString(DCM_NumberOfFrames, tmp).good() ||
+    if (!dicom.findAndGetString(DCM_NumberOfFrames, tmp).good() ||
         tmp == NULL)
     {
       return 1;
@@ -378,12 +344,12 @@
     }
     else
     {
-      return count;
+      return static_cast<unsigned int>(count);
     }
   }
 
 
-  DicomFrameIndex::DicomFrameIndex(DcmFileFormat& dicom)
+  DicomFrameIndex::DicomFrameIndex(DcmDataset& dicom)
   {
     countFrames_ = GetFramesCount(dicom);
     if (countFrames_ == 0)
@@ -392,10 +358,8 @@
       return;
     }
 
-    DcmDataset& dataset = *dicom.getDataset();
-
     // Test whether this image is composed of a sequence of fragments
-    DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset);
+    DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dicom);
     if (pixelSequence != NULL)
     {
       index_.reset(new FragmentIndex(pixelSequence, countFrames_));
@@ -404,18 +368,18 @@
 
     // Extract information about the image structure
     DicomMap tags;
-    FromDcmtkBridge::ExtractDicomSummary(tags, dataset);
+    FromDcmtkBridge::ExtractDicomSummary(tags, dicom);
 
     DicomImageInformation information(tags);
 
     // Access to the raw pixel data
-    if (DicomImageDecoder::IsPsmctRle1(dataset))
+    if (DicomImageDecoder::IsPsmctRle1(dicom))
     {
-      index_.reset(new PsmctRle1Index(dataset, countFrames_, information.GetFrameSize()));
+      index_.reset(new PsmctRle1Index(dicom, countFrames_, information.GetFrameSize()));
     }
     else
     {
-      index_.reset(new UncompressedIndex(dataset, countFrames_, information.GetFrameSize()));
+      index_.reset(new UncompressedIndex(dicom, countFrames_, information.GetFrameSize()));
     }
   }
 
--- a/Core/DicomParsing/Internals/DicomFrameIndex.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomParsing/Internals/DicomFrameIndex.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -33,6 +33,7 @@
 
 #pragma once
 
+#include "../../Compatibility.h"
 #include "../../Enumerations.h"
 
 #include <dcmtk/dcmdata/dcdatset.h>
@@ -62,11 +63,11 @@
     class UncompressedIndex;
     class PsmctRle1Index;
 
-    std::auto_ptr<IIndex>  index_;
-    unsigned int           countFrames_;
+    std::unique_ptr<IIndex>  index_;
+    unsigned int             countFrames_;
 
   public:
-    DicomFrameIndex(DcmFileFormat& dicom);
+    DicomFrameIndex(DcmDataset& dicom);
 
     unsigned int GetFramesCount() const
     {
@@ -76,8 +77,6 @@
     void GetRawFrame(std::string& frame,
                      unsigned int index) const;
 
-    static bool IsVideo(DcmFileFormat& dicom);
-
-    static unsigned int GetFramesCount(DcmFileFormat& dicom);
+    static unsigned int GetFramesCount(DcmDataset& dicom);
   };
 }
--- a/Core/DicomParsing/Internals/DicomImageDecoder.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomParsing/Internals/DicomImageDecoder.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -34,6 +34,8 @@
 #include "../../PrecompiledHeaders.h"
 #include "DicomImageDecoder.h"
 
+#include "../ParsedDicomFile.h"
+
 
 /*=========================================================================
 
@@ -84,7 +86,6 @@
 #include "../../DicomFormat/DicomIntegerPixelAccessor.h"
 #include "../ToDcmtkBridge.h"
 #include "../FromDcmtkBridge.h"
-#include "../ParsedDicomFile.h"
 
 #if ORTHANC_ENABLE_PNG == 1
 #  include "../../Images/PngWriter.h"
@@ -98,7 +99,6 @@
 #include <boost/lexical_cast.hpp>
 
 #include <dcmtk/dcmdata/dcdeftag.h>
-#include <dcmtk/dcmdata/dcfilefo.h>
 #include <dcmtk/dcmdata/dcrleccd.h>
 #include <dcmtk/dcmdata/dcrlecp.h>
 #include <dcmtk/dcmdata/dcrlerp.h>
@@ -249,7 +249,7 @@
   {
   private:
     std::string psmct_;
-    std::auto_ptr<DicomIntegerPixelAccessor> slowAccessor_;
+    std::unique_ptr<DicomIntegerPixelAccessor> slowAccessor_;
 
   public:
     void Setup(DcmDataset& dataset,
@@ -388,7 +388,7 @@
   }
 
 
-  static ImageAccessor* DecodeLookupTable(std::auto_ptr<ImageAccessor>& target,
+  static ImageAccessor* DecodeLookupTable(std::unique_ptr<ImageAccessor>& target,
                                           const DicomImageInformation& info,
                                           DcmDataset& dataset,
                                           const uint8_t* pixelData,
@@ -508,7 +508,7 @@
      * Create the target image.
      **/
 
-    std::auto_ptr<ImageAccessor> target(CreateImage(dataset, false));
+    std::unique_ptr<ImageAccessor> target(CreateImage(dataset, false));
 
     ImageSource source;
     source.Setup(dataset, frame);
@@ -616,7 +616,7 @@
     FromDcmtkBridge::ExtractDicomSummary(m, dataset);
     DicomImageInformation info(m);
 
-    std::auto_ptr<ImageAccessor> target(CreateImage(dataset, true));
+    std::unique_ptr<ImageAccessor> target(CreateImage(dataset, true));
 
     Uint32 startFragment = 0;  // Default 
     OFString decompressedColorModel;  // Out
@@ -662,7 +662,20 @@
   ImageAccessor* DicomImageDecoder::Decode(ParsedDicomFile& dicom,
                                            unsigned int frame)
   {
-    DcmDataset& dataset = *dicom.GetDcmtkObject().getDataset();
+    if (dicom.GetDcmtkObject().getDataset() == NULL)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    else
+    {
+      return Decode(*dicom.GetDcmtkObject().getDataset(), frame);
+    }
+  }
+
+
+  ImageAccessor* DicomImageDecoder::Decode(DcmDataset& dataset,
+                                           unsigned int frame)
+  {
     E_TransferSyntax syntax = dataset.getOriginalXfer();
 
     /**
@@ -692,7 +705,7 @@
       DJLSRepresentationParameter representationParameter(2, OFTrue);
 
       DJLSCodecParameter parameters;
-      std::auto_ptr<DJLSDecoderBase> decoder;
+      std::unique_ptr<DJLSDecoderBase> decoder;
 
       switch (syntax)
       {
@@ -734,7 +747,7 @@
         EUC_default,     // Mode for UID creation, unused for decompression
         EPC_default);    // Automatically determine whether color-by-plane is required from the SOP Class UID and decompressed photometric interpretation
       DJ_RPLossy representationParameter;
-      std::auto_ptr<DJCodecDecoder> decoder;
+      std::unique_ptr<DJCodecDecoder> decoder;
 
       switch (syntax)
       {
@@ -799,7 +812,7 @@
     {
       LOG(INFO) << "Decoding a compressed image by converting its transfer syntax to Little Endian";
 
-      std::auto_ptr<DcmDataset> converted(dynamic_cast<DcmDataset*>(dataset.clone()));
+      std::unique_ptr<DcmDataset> converted(dynamic_cast<DcmDataset*>(dataset.clone()));
       converted->chooseRepresentation(EXS_LittleEndianExplicit, NULL);
 
       if (converted->canWriteXfer(EXS_LittleEndianExplicit))
@@ -820,7 +833,7 @@
   }
 
 
-  bool DicomImageDecoder::TruncateDecodedImage(std::auto_ptr<ImageAccessor>& image,
+  bool DicomImageDecoder::TruncateDecodedImage(std::unique_ptr<ImageAccessor>& image,
                                                PixelFormat format,
                                                bool allowColorConversion)
   {
@@ -840,17 +853,22 @@
     if (image->GetFormat() != format)
     {
       // A conversion is required
-      std::auto_ptr<ImageAccessor> target
+      std::unique_ptr<ImageAccessor> target
         (new Image(format, image->GetWidth(), image->GetHeight(), false));
       ImageProcessing::Convert(*target, *image);
-      image = target;
+
+#if __cplusplus < 201103L
+      image.reset(target.release());
+#else
+      image = std::move(target);
+#endif
     }
 
     return true;
   }
 
 
-  bool DicomImageDecoder::PreviewDecodedImage(std::auto_ptr<ImageAccessor>& image)
+  bool DicomImageDecoder::PreviewDecodedImage(std::unique_ptr<ImageAccessor>& image)
   {
     switch (image->GetFormat())
     {
@@ -862,10 +880,16 @@
 
       case PixelFormat_RGB48:
       {
-        std::auto_ptr<ImageAccessor> target
+        std::unique_ptr<ImageAccessor> target
           (new Image(PixelFormat_RGB24, image->GetWidth(), image->GetHeight(), false));
         ImageProcessing::Convert(*target, *image);
-        image = target;
+
+#if __cplusplus < 201103L
+        image.reset(target.release());
+#else
+        image = std::move(target);
+#endif
+
         return true;
       }
 
@@ -891,10 +915,15 @@
         // If the source image is not grayscale 8bpp, convert it
         if (image->GetFormat() != PixelFormat_Grayscale8)
         {
-          std::auto_ptr<ImageAccessor> target
+          std::unique_ptr<ImageAccessor> target
             (new Image(PixelFormat_Grayscale8, image->GetWidth(), image->GetHeight(), false));
           ImageProcessing::Convert(*target, *image);
-          image = target;
+
+#if __cplusplus < 201103L
+          image.reset(target.release());
+#else
+          image = std::move(target);
+#endif
         }
 
         return true;
@@ -906,7 +935,7 @@
   }
 
 
-  void DicomImageDecoder::ApplyExtractionMode(std::auto_ptr<ImageAccessor>& image,
+  void DicomImageDecoder::ApplyExtractionMode(std::unique_ptr<ImageAccessor>& image,
                                               ImageExtractionMode mode,
                                               bool invert)
   {
@@ -956,7 +985,7 @@
 
 
   void DicomImageDecoder::ExtractPamImage(std::string& result,
-                                          std::auto_ptr<ImageAccessor>& image,
+                                          std::unique_ptr<ImageAccessor>& image,
                                           ImageExtractionMode mode,
                                           bool invert)
   {
@@ -968,7 +997,7 @@
 
 #if ORTHANC_ENABLE_PNG == 1
   void DicomImageDecoder::ExtractPngImage(std::string& result,
-                                          std::auto_ptr<ImageAccessor>& image,
+                                          std::unique_ptr<ImageAccessor>& image,
                                           ImageExtractionMode mode,
                                           bool invert)
   {
@@ -982,7 +1011,7 @@
 
 #if ORTHANC_ENABLE_JPEG == 1
   void DicomImageDecoder::ExtractJpegImage(std::string& result,
-                                           std::auto_ptr<ImageAccessor>& image,
+                                           std::unique_ptr<ImageAccessor>& image,
                                            ImageExtractionMode mode,
                                            bool invert,
                                            uint8_t quality)
--- a/Core/DicomParsing/Internals/DicomImageDecoder.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomParsing/Internals/DicomImageDecoder.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -33,7 +33,8 @@
 
 #pragma once
 
-#include "../ParsedDicomFile.h"
+#include "../../Compatibility.h"
+#include "../../Images/ImageAccessor.h"
 
 #include <memory>
 
@@ -61,6 +62,8 @@
 
 namespace Orthanc
 {
+  class ParsedDicomFile;
+  
   class DicomImageDecoder : public boost::noncopyable
   {
   private:
@@ -82,13 +85,13 @@
                                      DcmDataset& dataset,
                                      unsigned int frame);
 
-    static bool TruncateDecodedImage(std::auto_ptr<ImageAccessor>& image,
+    static bool TruncateDecodedImage(std::unique_ptr<ImageAccessor>& image,
                                      PixelFormat format,
                                      bool allowColorConversion);
 
-    static bool PreviewDecodedImage(std::auto_ptr<ImageAccessor>& image);
+    static bool PreviewDecodedImage(std::unique_ptr<ImageAccessor>& image);
 
-    static void ApplyExtractionMode(std::auto_ptr<ImageAccessor>& image,
+    static void ApplyExtractionMode(std::unique_ptr<ImageAccessor>& image,
                                     ImageExtractionMode mode,
                                     bool invert);
 
@@ -101,21 +104,24 @@
     static ImageAccessor *Decode(ParsedDicomFile& dicom,
                                  unsigned int frame);
 
+    static ImageAccessor *Decode(DcmDataset& dataset,
+                                 unsigned int frame);
+
     static void ExtractPamImage(std::string& result,
-                                std::auto_ptr<ImageAccessor>& image,
+                                std::unique_ptr<ImageAccessor>& image,
                                 ImageExtractionMode mode,
                                 bool invert);
 
 #if ORTHANC_ENABLE_PNG == 1
     static void ExtractPngImage(std::string& result,
-                                std::auto_ptr<ImageAccessor>& image,
+                                std::unique_ptr<ImageAccessor>& image,
                                 ImageExtractionMode mode,
                                 bool invert);
 #endif
 
 #if ORTHANC_ENABLE_JPEG == 1
     static void ExtractJpegImage(std::string& result,
-                                 std::auto_ptr<ImageAccessor>& image,
+                                 std::unique_ptr<ImageAccessor>& image,
                                  ImageExtractionMode mode,
                                  bool invert,
                                  uint8_t quality);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/ParsedDicomDir.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,195 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeaders.h"
+#include "ParsedDicomDir.h"
+
+#include "../Compatibility.h"
+#include "../OrthancException.h"
+#include "ParsedDicomFile.h"
+#include "FromDcmtkBridge.h"
+
+#include <dcmtk/dcmdata/dcdeftag.h>
+
+
+namespace Orthanc
+{
+  void ParsedDicomDir::Clear()
+  {
+    for (size_t i = 0; i < content_.size(); i++)
+    {
+      assert(content_[i] != NULL);
+      delete content_[i];
+    }
+  }
+
+  
+  bool ParsedDicomDir::LookupIndexOfOffset(size_t& target,
+                                           unsigned int offset) const
+  {
+    if (offset == 0)
+    {
+      return false;
+    }
+
+    OffsetToIndex::const_iterator found = offsetToIndex_.find(offset);
+    if (found == offsetToIndex_.end())
+    {
+      // Error in the algorithm that computes the offsets
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    else
+    {
+      target = found->second;
+      return true;
+    }
+  }
+
+
+  ParsedDicomDir::ParsedDicomDir(const std::string content)
+  {
+    ParsedDicomFile dicom(content);
+
+    DcmSequenceOfItems* sequence = NULL;
+    if (dicom.GetDcmtkObject().getDataset() == NULL ||
+        !dicom.GetDcmtkObject().getDataset()->findAndGetSequence(DCM_DirectoryRecordSequence, sequence).good() ||
+        sequence == NULL)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat, "Not a DICOMDIR");
+    }
+
+    content_.resize(sequence->card());
+    nextOffsets_.resize(content_.size());
+    lowerOffsets_.resize(content_.size());
+
+    // Manually reconstruct the list of all the available offsets of
+    // "DcmItem", as "fStartPosition" is a protected member in DCMTK
+    // API
+    std::set<uint32_t> availableOffsets;
+    availableOffsets.insert(0);
+
+
+    for (unsigned long i = 0; i < sequence->card(); i++)
+    {
+      DcmItem* item = sequence->getItem(i);
+      if (item == NULL)
+      {
+        Clear();
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      Uint32 next, lower;
+      if (!item->findAndGetUint32(DCM_OffsetOfTheNextDirectoryRecord, next).good() ||
+          !item->findAndGetUint32(DCM_OffsetOfReferencedLowerLevelDirectoryEntity, lower).good())
+      {
+        item->writeXML(std::cout);
+        throw OrthancException(ErrorCode_BadFileFormat,
+                               "Missing offsets in DICOMDIR");
+      }          
+
+      nextOffsets_[i] = next;
+      lowerOffsets_[i] = lower;
+
+      std::unique_ptr<DicomMap> entry(new DicomMap);
+      FromDcmtkBridge::ExtractDicomSummary(*entry, *item);
+
+      if (next != 0)
+      {
+        availableOffsets.insert(next);
+      }
+
+      if (lower != 0)
+      {
+        availableOffsets.insert(lower);
+      }
+
+      content_[i] = entry.release();
+    }
+
+    if (content_.size() != availableOffsets.size())
+    {
+      throw OrthancException(ErrorCode_BadFileFormat,
+                             "Inconsistent offsets in DICOMDIR");
+    }
+
+    unsigned int index = 0;
+    for (std::set<uint32_t>::const_iterator it = availableOffsets.begin();
+         it != availableOffsets.end(); ++it)
+    {
+      offsetToIndex_[*it] = index;
+      index ++;
+    }    
+  }
+
+
+  const DicomMap& ParsedDicomDir::GetItem(size_t i) const
+  {
+    if (i >= content_.size())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      assert(content_[i] != NULL);
+      return *content_[i];
+    }
+  }
+
+
+  bool ParsedDicomDir::LookupNext(size_t& target,
+                                  size_t index) const
+  {
+    if (index >= nextOffsets_.size())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return LookupIndexOfOffset(target, nextOffsets_[index]);
+    }
+  }
+
+
+  bool ParsedDicomDir::LookupLower(size_t& target,
+                                   size_t index) const
+  {
+    if (index >= lowerOffsets_.size())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return LookupIndexOfOffset(target, lowerOffsets_[index]);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/DicomParsing/ParsedDicomDir.h	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,80 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#if ORTHANC_ENABLE_DCMTK != 1
+#  error The macro ORTHANC_ENABLE_DCMTK must be set to 1 to use this file
+#endif
+
+#include "../DicomFormat/DicomMap.h"
+
+namespace Orthanc
+{
+  class ParsedDicomDir : public boost::noncopyable
+  {
+  private:
+    typedef std::map<uint32_t, size_t>  OffsetToIndex;
+
+    std::vector<DicomMap*>  content_;
+    std::vector<size_t>     nextOffsets_;
+    std::vector<size_t>     lowerOffsets_;
+    OffsetToIndex           offsetToIndex_;
+
+    void Clear();
+
+    bool LookupIndexOfOffset(size_t& target,
+                             unsigned int offset) const;
+
+  public:
+    ParsedDicomDir(const std::string content);
+
+    ~ParsedDicomDir()
+    {
+      Clear();
+    }
+
+    size_t GetSize() const
+    {
+      return content_.size();
+    }
+
+    const DicomMap& GetItem(size_t i) const;
+
+    bool LookupNext(size_t& target,
+                    size_t index) const;
+
+    bool LookupLower(size_t& target,
+                     size_t index) const;
+  };
+}
--- a/Core/DicomParsing/ParsedDicomFile.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomParsing/ParsedDicomFile.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -156,8 +156,8 @@
 {
   struct ParsedDicomFile::PImpl
   {
-    std::auto_ptr<DcmFileFormat> file_;
-    std::auto_ptr<DicomFrameIndex>  frameIndex_;
+    std::unique_ptr<DcmFileFormat> file_;
+    std::unique_ptr<DicomFrameIndex>  frameIndex_;
   };
 
 
@@ -619,7 +619,8 @@
 
   void ParsedDicomFile::Insert(const DicomTag& tag,
                                const Json::Value& value,
-                               bool decodeDataUriScheme)
+                               bool decodeDataUriScheme,
+                               const std::string& privateCreator)
   {
     if (tag.GetElement() == 0x0000)
     {
@@ -648,11 +649,38 @@
 
     bool hasCodeExtensions;
     Encoding encoding = DetectEncoding(hasCodeExtensions);
-    std::auto_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding));
+    std::unique_ptr<DcmElement> element(FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding, privateCreator));
     InsertInternal(*pimpl_->file_->getDataset(), element.release());
   }
 
 
+  void ParsedDicomFile::ReplacePlainString(const DicomTag& tag,
+                                           const std::string& utf8Value)
+  {
+    if (tag.IsPrivate())
+    {
+      throw OrthancException(ErrorCode_InternalError,
+                             "Cannot apply this function to private tags: " + tag.Format());
+    }
+    else
+    {
+      Replace(tag, utf8Value, false, DicomReplaceMode_InsertIfAbsent,
+              "" /* not a private tag, so no private creator */);
+    }
+  }
+
+
+  void ParsedDicomFile::SetIfAbsent(const DicomTag& tag,
+                                    const std::string& utf8Value)
+  {
+    std::string currentValue;
+    if (!GetTagValue(currentValue, tag))
+    {
+      ReplacePlainString(tag, utf8Value);
+    }
+  }
+
+
   static bool CanReplaceProceed(DcmDataset& dicom,
                                 const DcmTagKey& tag,
                                 DicomReplaceMode mode)
@@ -742,7 +770,8 @@
   void ParsedDicomFile::Replace(const DicomTag& tag,
                                 const std::string& utf8Value,
                                 bool decodeDataUriScheme,
-                                DicomReplaceMode mode)
+                                DicomReplaceMode mode,
+                                const std::string& privateCreator)
   {
     if (tag.GetElement() == 0x0000)
     {
@@ -769,13 +798,13 @@
         }
       }
 
-      std::auto_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag));
+      std::unique_ptr<DcmElement> element(FromDcmtkBridge::CreateElementForTag(tag, privateCreator));
 
       if (!utf8Value.empty())
       {
         bool hasCodeExtensions;
         Encoding encoding = DetectEncoding(hasCodeExtensions);
-        FromDcmtkBridge::FillElementWithString(*element, tag, utf8Value, decodeDataUriScheme, encoding);
+        FromDcmtkBridge::FillElementWithString(*element, utf8Value, decodeDataUriScheme, encoding);
       }
 
       InsertInternal(dicom, element.release());
@@ -787,7 +816,8 @@
   void ParsedDicomFile::Replace(const DicomTag& tag,
                                 const Json::Value& value,
                                 bool decodeDataUriScheme,
-                                DicomReplaceMode mode)
+                                DicomReplaceMode mode,
+                                const std::string& privateCreator)
   {
     if (tag.GetElement() == 0x0000)
     {
@@ -817,7 +847,7 @@
 
       bool hasCodeExtensions;
       Encoding encoding = DetectEncoding(hasCodeExtensions);
-      InsertInternal(dicom, FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding));
+      InsertInternal(dicom, FromDcmtkBridge::FromJson(tag, value, decodeDataUriScheme, encoding, privateCreator));
 
       if (tag == DICOM_TAG_SOP_CLASS_UID ||
           tag == DICOM_TAG_SOP_INSTANCE_UID)
@@ -891,9 +921,9 @@
       Encoding encoding = DetectEncoding(hasCodeExtensions);
       
       std::set<DicomTag> tmp;
-      std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement
-                                  (*element, DicomToJsonFlags_Default, 
-                                   0, encoding, hasCodeExtensions, tmp));
+      std::unique_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement
+                                    (*element, DicomToJsonFlags_Default, 
+                                     0, encoding, hasCodeExtensions, tmp));
       
       if (v.get() == NULL ||
           v->IsNull())
@@ -1007,8 +1037,8 @@
       }
     }
 
-    for (DicomMap::Map::const_iterator 
-           it = source.map_.begin(); it != source.map_.end(); ++it)
+    for (DicomMap::Content::const_iterator 
+           it = source.content_.begin(); it != source.content_.end(); ++it)
     {
       if (it->first != DICOM_TAG_SPECIFIC_CHARACTER_SET &&
           !it->second->IsNull())
@@ -1279,7 +1309,7 @@
     DcmTag key(DICOM_TAG_PIXEL_DATA.GetGroup(), 
                DICOM_TAG_PIXEL_DATA.GetElement());
 
-    std::auto_ptr<DcmPixelData> pixels(new DcmPixelData(key));
+    std::unique_ptr<DcmPixelData> pixels(new DcmPixelData(key));
 
     unsigned int pitch = accessor.GetWidth() * bytesPerPixel;
     Uint8* target = NULL;
@@ -1414,7 +1444,7 @@
     ReplacePlainString(FromDcmtkBridge::Convert(DCM_MIMETypeOfEncapsulatedDocument), MIME_PDF);
     //ReplacePlainString(FromDcmtkBridge::Convert(DCM_SeriesNumber), "1");
 
-    std::auto_ptr<DcmPolymorphOBOW> element(new DcmPolymorphOBOW(DCM_EncapsulatedDocument));
+    std::unique_ptr<DcmPolymorphOBOW> element(new DcmPolymorphOBOW(DCM_EncapsulatedDocument));
 
     size_t s = pdf.size();
     if (s & 1)
@@ -1483,12 +1513,13 @@
 
 
   ParsedDicomFile* ParsedDicomFile::CreateFromJson(const Json::Value& json,
-                                                   DicomFromJsonFlags flags)
+                                                   DicomFromJsonFlags flags,
+                                                   const std::string& privateCreator)
   {
     const bool generateIdentifiers = (flags & DicomFromJsonFlags_GenerateIdentifiers) ? true : false;
     const bool decodeDataUriScheme = (flags & DicomFromJsonFlags_DecodeDataUriScheme) ? true : false;
 
-    std::auto_ptr<ParsedDicomFile> result(new ParsedDicomFile(generateIdentifiers));
+    std::unique_ptr<ParsedDicomFile> result(new ParsedDicomFile(generateIdentifiers));
     result->SetEncoding(FromDcmtkBridge::ExtractEncoding(json, GetDefaultDicomEncoding()));
 
     const Json::Value::Members tags = json.getMemberNames();
@@ -1512,7 +1543,7 @@
       }
       else if (tag != DICOM_TAG_SPECIFIC_CHARACTER_SET)
       {
-        result->Replace(tag, value, decodeDataUriScheme, DicomReplaceMode_InsertIfAbsent);
+        result->Replace(tag, value, decodeDataUriScheme, DicomReplaceMode_InsertIfAbsent, privateCreator);
       }
     }
 
@@ -1526,7 +1557,9 @@
   {
     if (pimpl_->frameIndex_.get() == NULL)
     {
-      pimpl_->frameIndex_.reset(new DicomFrameIndex(*pimpl_->file_));
+      assert(pimpl_->file_ != NULL &&
+             pimpl_->file_->getDataset() != NULL);
+      pimpl_->frameIndex_.reset(new DicomFrameIndex(*pimpl_->file_->getDataset()));
     }
 
     pimpl_->frameIndex_->GetRawFrame(target, frameId);
@@ -1558,7 +1591,9 @@
 
   unsigned int ParsedDicomFile::GetFramesCount() const
   {
-    return DicomFrameIndex::GetFramesCount(*pimpl_->file_);
+    assert(pimpl_->file_ != NULL &&
+           pimpl_->file_->getDataset() != NULL);
+    return DicomFrameIndex::GetFramesCount(*pimpl_->file_->getDataset());
   }
 
 
@@ -1581,6 +1616,13 @@
   }
 
 
+  void ParsedDicomFile::ExtractDicomSummary(DicomMap& target,
+                                            const std::set<DicomTag>& ignoreTagLength) const
+  {
+    FromDcmtkBridge::ExtractDicomSummary(target, *pimpl_->file_->getDataset(), ignoreTagLength);
+  }
+
+
   bool ParsedDicomFile::LookupTransferSyntax(std::string& result)
   {
     return FromDcmtkBridge::LookupTransferSyntax(result, *pimpl_->file_);
--- a/Core/DicomParsing/ParsedDicomFile.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomParsing/ParsedDicomFile.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -53,6 +53,14 @@
 #  error The macro ORTHANC_SANDBOXED must be defined
 #endif
 
+#if !defined(ORTHANC_ENABLE_DCMTK)
+#  error The macro ORTHANC_ENABLE_DCMTK must be defined
+#endif
+
+#if ORTHANC_ENABLE_DCMTK != 1
+#  error The macro ORTHANC_ENABLE_DCMTK must be set to 1 to use this file
+#endif
+
 #include "ITagVisitor.h"
 #include "../DicomFormat/DicomInstanceHasher.h"
 #include "../Images/ImageAccessor.h"
@@ -130,32 +138,27 @@
     void Replace(const DicomTag& tag,
                  const std::string& utf8Value,
                  bool decodeDataUriScheme,
-                 DicomReplaceMode mode);
+                 DicomReplaceMode mode,
+                 const std::string& privateCreator /* used only for private tags */);
 
     void Replace(const DicomTag& tag,
                  const Json::Value& value,  // Assumed to be encoded with UTF-8
                  bool decodeDataUriScheme,
-                 DicomReplaceMode mode);
+                 DicomReplaceMode mode,
+                 const std::string& privateCreator /* used only for private tags */);
 
     void Insert(const DicomTag& tag,
                 const Json::Value& value,   // Assumed to be encoded with UTF-8
-                bool decodeDataUriScheme);
+                bool decodeDataUriScheme,
+                const std::string& privateCreator /* used only for private tags */);
 
+    // Cannot be applied to private tags
     void ReplacePlainString(const DicomTag& tag,
-                            const std::string& utf8Value)
-    {
-      Replace(tag, utf8Value, false, DicomReplaceMode_InsertIfAbsent);
-    }
+                            const std::string& utf8Value);
 
+    // Cannot be applied to private tags
     void SetIfAbsent(const DicomTag& tag,
-                     const std::string& utf8Value)
-    {
-      std::string currentValue;
-      if (!GetTagValue(currentValue, tag))
-      {
-        ReplacePlainString(tag, utf8Value);
-      }
-    }
+                     const std::string& utf8Value);
 
     void RemovePrivateTags()
     {
@@ -226,12 +229,16 @@
     unsigned int GetFramesCount() const;
 
     static ParsedDicomFile* CreateFromJson(const Json::Value& value,
-                                           DicomFromJsonFlags flags);
+                                           DicomFromJsonFlags flags,
+                                           const std::string& privateCreator);
 
     void ChangeEncoding(Encoding target);
 
     void ExtractDicomSummary(DicomMap& target) const;
 
+    void ExtractDicomSummary(DicomMap& target,
+                             const std::set<DicomTag>& ignoreTagLength) const;
+
     bool LookupTransferSyntax(std::string& result);
 
     bool LookupPhotometricInterpretation(PhotometricInterpretation& result) const;
--- a/Core/DicomParsing/ToDcmtkBridge.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomParsing/ToDcmtkBridge.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/DicomParsing/ToDcmtkBridge.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/DicomParsing/ToDcmtkBridge.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Endianness.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Endianness.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/EnumerationDictionary.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/EnumerationDictionary.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Enumerations.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Enumerations.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -366,6 +366,9 @@
       case ErrorCode_AlreadyExistingTag:
         return "Cannot override the value of a tag that already exists";
 
+      case ErrorCode_NoStorageCommitmentHandler:
+        return "No request handler factory for DICOM N-ACTION SCP (storage commitment)";
+
       case ErrorCode_UnsupportedMediaType:
         return "Unsupported media type";
 
@@ -824,12 +827,6 @@
       case ModalityManufacturer_StoreScp:
         return "StoreScp";
       
-      case ModalityManufacturer_ClearCanvas:
-        return "ClearCanvas";
-      
-      case ModalityManufacturer_Dcm4Chee:
-        return "Dcm4Chee";
-      
       case ModalityManufacturer_Vitrea:
         return "Vitrea";
       
@@ -866,6 +863,14 @@
         return "Store";
         break;
 
+      case DicomRequestType_NAction:
+        return "N-ACTION";
+        break;
+
+      case DicomRequestType_NEventReport:
+        return "N-EVENT-REPORT";
+        break;
+
       default: 
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
@@ -894,6 +899,9 @@
       case TransferSyntax_Mpeg2:
         return "MPEG2";
 
+      case TransferSyntax_Mpeg4:
+        return "MPEG4";
+
       case TransferSyntax_Rle:
         return "RLE";
 
@@ -1160,6 +1168,41 @@
   }
 
 
+  const char* EnumerationToString(StorageCommitmentFailureReason reason)
+  {
+    switch (reason)
+    {
+      case StorageCommitmentFailureReason_Success:
+        return "Success";
+
+      case StorageCommitmentFailureReason_ProcessingFailure:
+        return "A general failure in processing the operation was encountered";
+
+      case StorageCommitmentFailureReason_NoSuchObjectInstance:
+        return "One or more of the elements in the Referenced SOP "
+          "Instance Sequence was not available";
+        
+      case StorageCommitmentFailureReason_ResourceLimitation:
+        return "The SCP does not currently have enough resources to "
+          "store the requested SOP Instance(s)";
+
+      case StorageCommitmentFailureReason_ReferencedSOPClassNotSupported:
+        return "Storage Commitment has been requested for a SOP Instance "
+          "with a SOP Class that is not supported by the SCP";
+
+      case StorageCommitmentFailureReason_ClassInstanceConflict:
+        return "The SOP Class of an element in the Referenced SOP Instance Sequence "
+          "did not correspond to the SOP class registered for this SOP Instance at the SCP";
+
+      case StorageCommitmentFailureReason_DuplicateTransactionUID:
+        return "The Transaction UID of the Storage Commitment Request is already in use";
+
+      default:
+        return "Unknown failure reason";
+    }
+  }
+
+
   Encoding StringToEncoding(const char* encoding)
   {
     std::string s(encoding);
@@ -1560,18 +1603,10 @@
     {
       return ModalityManufacturer_GenericNoUniversalWildcard;
     }
-    else if (manufacturer == "ClearCanvas")
-    {
-      return ModalityManufacturer_ClearCanvas;
-    }
     else if (manufacturer == "StoreScp")
     {
       return ModalityManufacturer_StoreScp;
     }
-    else if (manufacturer == "Dcm4Chee")
-    {
-      return ModalityManufacturer_Dcm4Chee;
-    }
     else if (manufacturer == "Vitrea")
     {
       return ModalityManufacturer_Vitrea;
@@ -1587,7 +1622,10 @@
       obsolete = true;
     }
     else if (manufacturer == "EFilm2" ||
-             manufacturer == "MedInria")
+             manufacturer == "MedInria" ||
+             manufacturer == "ClearCanvas" ||
+             manufacturer == "Dcm4Chee"
+             )
     {
       result = ModalityManufacturer_Generic;
       obsolete = true;
@@ -1600,8 +1638,8 @@
 
     if (obsolete)
     {
-      LOG(WARNING) << "The \"" << manufacturer << "\" manufacturer is obsolete since "
-                   << "Orthanc 1.3.0. To guarantee compatibility with future Orthanc "
+      LOG(WARNING) << "The \"" << manufacturer << "\" manufacturer is now obsolete. "
+                   << "To guarantee compatibility with future Orthanc "
                    << "releases, you should replace it by \""
                    << EnumerationToString(result)
                    << "\" in your configuration file.";
@@ -2252,5 +2290,7 @@
 
     LOG(INFO) << "Default encoding for DICOM was changed to: " << name;
   }
-
 }
+
+
+#include "./Enumerations_TransferSyntaxes.impl.h"
--- a/Core/Enumerations.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Enumerations.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -239,10 +239,59 @@
     ErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
     ErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
     ErrorCode_AlreadyExistingTag = 2042    /*!< Cannot override the value of a tag that already exists */,
+    ErrorCode_NoStorageCommitmentHandler = 2043    /*!< No request handler factory for DICOM N-ACTION SCP (storage commitment) */,
     ErrorCode_UnsupportedMediaType = 3000    /*!< Unsupported media type */,
     ErrorCode_START_PLUGINS = 1000000
   };
 
+  // This enumeration is autogenerated by the script
+  // "Resources/GenerateTransferSyntaxes.py"
+  enum DicomTransferSyntax
+  {
+    DicomTransferSyntax_LittleEndianImplicit    /*!< Implicit VR Little Endian */,
+    DicomTransferSyntax_LittleEndianExplicit    /*!< Explicit VR Little Endian */,
+    DicomTransferSyntax_DeflatedLittleEndianExplicit    /*!< Deflated Explicit VR Little Endian */,
+    DicomTransferSyntax_BigEndianExplicit    /*!< Explicit VR Big Endian */,
+    DicomTransferSyntax_JPEGProcess1    /*!< JPEG Baseline (process 1, lossy) */,
+    DicomTransferSyntax_JPEGProcess2_4    /*!< JPEG Extended Sequential (processes 2 & 4) */,
+    DicomTransferSyntax_JPEGProcess3_5    /*!< JPEG Extended Sequential (lossy, 8/12 bit), arithmetic coding */,
+    DicomTransferSyntax_JPEGProcess6_8    /*!< JPEG Spectral Selection, Nonhierarchical (lossy, 8/12 bit) */,
+    DicomTransferSyntax_JPEGProcess7_9    /*!< JPEG Spectral Selection, Nonhierarchical (lossy, 8/12 bit), arithmetic coding */,
+    DicomTransferSyntax_JPEGProcess10_12    /*!< JPEG Full Progression, Nonhierarchical (lossy, 8/12 bit) */,
+    DicomTransferSyntax_JPEGProcess11_13    /*!< JPEG Full Progression, Nonhierarchical (lossy, 8/12 bit), arithmetic coding */,
+    DicomTransferSyntax_JPEGProcess14    /*!< JPEG Lossless, Nonhierarchical with any selection value (process 14) */,
+    DicomTransferSyntax_JPEGProcess15    /*!< JPEG Lossless with any selection value, arithmetic coding */,
+    DicomTransferSyntax_JPEGProcess16_18    /*!< JPEG Extended Sequential, Hierarchical (lossy, 8/12 bit) */,
+    DicomTransferSyntax_JPEGProcess17_19    /*!< JPEG Extended Sequential, Hierarchical (lossy, 8/12 bit), arithmetic coding */,
+    DicomTransferSyntax_JPEGProcess20_22    /*!< JPEG Spectral Selection, Hierarchical (lossy, 8/12 bit) */,
+    DicomTransferSyntax_JPEGProcess21_23    /*!< JPEG Spectral Selection, Hierarchical (lossy, 8/12 bit), arithmetic coding */,
+    DicomTransferSyntax_JPEGProcess24_26    /*!< JPEG Full Progression, Hierarchical (lossy, 8/12 bit) */,
+    DicomTransferSyntax_JPEGProcess25_27    /*!< JPEG Full Progression, Hierarchical (lossy, 8/12 bit), arithmetic coding */,
+    DicomTransferSyntax_JPEGProcess28    /*!< JPEG Lossless, Hierarchical */,
+    DicomTransferSyntax_JPEGProcess29    /*!< JPEG Lossless, Hierarchical, arithmetic coding */,
+    DicomTransferSyntax_JPEGProcess14SV1    /*!< JPEG Lossless, Nonhierarchical, First-Order Prediction (Processes 14 [Selection Value 1]) */,
+    DicomTransferSyntax_JPEGLSLossless    /*!< JPEG-LS (lossless) */,
+    DicomTransferSyntax_JPEGLSLossy    /*!< JPEG-LS (lossy or near-lossless) */,
+    DicomTransferSyntax_JPEG2000LosslessOnly    /*!< JPEG 2000 (lossless) */,
+    DicomTransferSyntax_JPEG2000    /*!< JPEG 2000 (lossless or lossy) */,
+    DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly    /*!< JPEG 2000 part 2 multicomponent extensions (lossless) */,
+    DicomTransferSyntax_JPEG2000Multicomponent    /*!< JPEG 2000 part 2 multicomponent extensions (lossless or lossy) */,
+    DicomTransferSyntax_JPIPReferenced    /*!< JPIP Referenced */,
+    DicomTransferSyntax_JPIPReferencedDeflate    /*!< JPIP Referenced Deflate */,
+    DicomTransferSyntax_MPEG2MainProfileAtMainLevel    /*!< MPEG2 Main Profile at Main Level */,
+    DicomTransferSyntax_MPEG2MainProfileAtHighLevel    /*!< MPEG2 Main Profile at High Level */,
+    DicomTransferSyntax_MPEG4HighProfileLevel4_1    /*!< MPEG4 High Profile / Level 4.1 */,
+    DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1    /*!< MPEG4 BD-compatible High Profile / Level 4.1 */,
+    DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo    /*!< MPEG4 High Profile / Level 4.2 For 2D Video */,
+    DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo    /*!< MPEG4 High Profile / Level 4.2 For 3D Video */,
+    DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2    /*!< 1.2.840.10008.1.2.4.106 */,
+    DicomTransferSyntax_HEVCMainProfileLevel5_1    /*!< HEVC/H.265 Main Profile / Level 5.1 */,
+    DicomTransferSyntax_HEVCMain10ProfileLevel5_1    /*!< HEVC/H.265 Main 10 Profile / Level 5.1 */,
+    DicomTransferSyntax_RLELossless    /*!< RLE - Run Length Encoding (lossless) */,
+    DicomTransferSyntax_RFC2557MimeEncapsulation    /*!< RFC 2557 MIME Encapsulation */,
+    DicomTransferSyntax_XML    /*!< XML Encoding */
+  };
+
   enum LogLevel
   {
     LogLevel_Error,
@@ -313,7 +362,7 @@
 
     /**
      * {summary}{Graylevel, unsigned 64bpp image.}
-     * {description}{The image is graylevel. Each pixel is unsigned and stored in 4 bytes.}
+     * {description}{The image is graylevel. Each pixel is unsigned and stored in 8 bytes.}
      **/
     PixelFormat_Grayscale64 = 10
   };
@@ -612,8 +661,6 @@
     ModalityManufacturer_GenericNoWildcardInDates,
     ModalityManufacturer_GenericNoUniversalWildcard,
     ModalityManufacturer_StoreScp,
-    ModalityManufacturer_ClearCanvas,
-    ModalityManufacturer_Dcm4Chee,
     ModalityManufacturer_Vitrea,
     ModalityManufacturer_GE
   };
@@ -624,7 +671,9 @@
     DicomRequestType_Find,
     DicomRequestType_Get,
     DicomRequestType_Move,
-    DicomRequestType_Store
+    DicomRequestType_Store,
+    DicomRequestType_NAction,
+    DicomRequestType_NEventReport
   };
 
   enum TransferSyntax
@@ -635,6 +684,7 @@
     TransferSyntax_JpegLossless,
     TransferSyntax_Jpip,
     TransferSyntax_Mpeg2,
+    TransferSyntax_Mpeg4,  // New in Orthanc 1.6.0
     TransferSyntax_Rle
   };
 
@@ -665,6 +715,36 @@
     JobStopReason_Retry
   };
 
+  
+  // http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.14.html#sect_C.14.1.1
+  enum StorageCommitmentFailureReason
+  {
+    StorageCommitmentFailureReason_Success = 0,
+
+    // A general failure in processing the operation was encountered
+    StorageCommitmentFailureReason_ProcessingFailure = 0x0110,
+
+    // One or more of the elements in the Referenced SOP Instance
+    // Sequence was not available
+    StorageCommitmentFailureReason_NoSuchObjectInstance = 0x0112,
+
+    // The SCP does not currently have enough resources to store the
+    // requested SOP Instance(s)
+    StorageCommitmentFailureReason_ResourceLimitation = 0x0213,
+
+    // Storage Commitment has been requested for a SOP Instance with a
+    // SOP Class that is not supported by the SCP
+    StorageCommitmentFailureReason_ReferencedSOPClassNotSupported = 0x0122,
+
+    // The SOP Class of an element in the Referenced SOP Instance
+    // Sequence did not correspond to the SOP class registered for
+    // this SOP Instance at the SCP
+    StorageCommitmentFailureReason_ClassInstanceConflict = 0x0119,
+
+    // The Transaction UID of the Storage Commitment Request is already in use
+    StorageCommitmentFailureReason_DuplicateTransactionUID = 0x0131
+  };
+
 
   /**
    * WARNING: Do not change the explicit values in the enumerations
@@ -751,6 +831,8 @@
 
   const char* EnumerationToString(Endianness endianness);
 
+  const char* EnumerationToString(StorageCommitmentFailureReason reason);
+
   Encoding StringToEncoding(const char* encoding);
 
   ResourceType StringToResourceType(const char* type);
@@ -799,4 +881,11 @@
   Encoding GetDefaultDicomEncoding();
 
   void SetDefaultDicomEncoding(Encoding encoding);
+
+  const char* GetTransferSyntaxUid(DicomTransferSyntax syntax);
+
+  bool IsRetiredTransferSyntax(DicomTransferSyntax syntax);
+
+  bool LookupTransferSyntax(DicomTransferSyntax& target,
+                            const std::string& uid);
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/Enumerations_TransferSyntaxes.impl.h	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,566 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+// This file is autogenerated by "../Resources/GenerateTransferSyntaxes.py"
+
+namespace Orthanc
+{
+  const char* GetTransferSyntaxUid(DicomTransferSyntax syntax)
+  {
+    switch (syntax)
+    {
+      case DicomTransferSyntax_LittleEndianImplicit:
+        return "1.2.840.10008.1.2";
+
+      case DicomTransferSyntax_LittleEndianExplicit:
+        return "1.2.840.10008.1.2.1";
+
+      case DicomTransferSyntax_DeflatedLittleEndianExplicit:
+        return "1.2.840.10008.1.2.1.99";
+
+      case DicomTransferSyntax_BigEndianExplicit:
+        return "1.2.840.10008.1.2.2";
+
+      case DicomTransferSyntax_JPEGProcess1:
+        return "1.2.840.10008.1.2.4.50";
+
+      case DicomTransferSyntax_JPEGProcess2_4:
+        return "1.2.840.10008.1.2.4.51";
+
+      case DicomTransferSyntax_JPEGProcess3_5:
+        return "1.2.840.10008.1.2.4.52";
+
+      case DicomTransferSyntax_JPEGProcess6_8:
+        return "1.2.840.10008.1.2.4.53";
+
+      case DicomTransferSyntax_JPEGProcess7_9:
+        return "1.2.840.10008.1.2.4.54";
+
+      case DicomTransferSyntax_JPEGProcess10_12:
+        return "1.2.840.10008.1.2.4.55";
+
+      case DicomTransferSyntax_JPEGProcess11_13:
+        return "1.2.840.10008.1.2.4.56";
+
+      case DicomTransferSyntax_JPEGProcess14:
+        return "1.2.840.10008.1.2.4.57";
+
+      case DicomTransferSyntax_JPEGProcess15:
+        return "1.2.840.10008.1.2.4.58";
+
+      case DicomTransferSyntax_JPEGProcess16_18:
+        return "1.2.840.10008.1.2.4.59";
+
+      case DicomTransferSyntax_JPEGProcess17_19:
+        return "1.2.840.10008.1.2.4.60";
+
+      case DicomTransferSyntax_JPEGProcess20_22:
+        return "1.2.840.10008.1.2.4.61";
+
+      case DicomTransferSyntax_JPEGProcess21_23:
+        return "1.2.840.10008.1.2.4.62";
+
+      case DicomTransferSyntax_JPEGProcess24_26:
+        return "1.2.840.10008.1.2.4.63";
+
+      case DicomTransferSyntax_JPEGProcess25_27:
+        return "1.2.840.10008.1.2.4.64";
+
+      case DicomTransferSyntax_JPEGProcess28:
+        return "1.2.840.10008.1.2.4.65";
+
+      case DicomTransferSyntax_JPEGProcess29:
+        return "1.2.840.10008.1.2.4.66";
+
+      case DicomTransferSyntax_JPEGProcess14SV1:
+        return "1.2.840.10008.1.2.4.70";
+
+      case DicomTransferSyntax_JPEGLSLossless:
+        return "1.2.840.10008.1.2.4.80";
+
+      case DicomTransferSyntax_JPEGLSLossy:
+        return "1.2.840.10008.1.2.4.81";
+
+      case DicomTransferSyntax_JPEG2000LosslessOnly:
+        return "1.2.840.10008.1.2.4.90";
+
+      case DicomTransferSyntax_JPEG2000:
+        return "1.2.840.10008.1.2.4.91";
+
+      case DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly:
+        return "1.2.840.10008.1.2.4.92";
+
+      case DicomTransferSyntax_JPEG2000Multicomponent:
+        return "1.2.840.10008.1.2.4.93";
+
+      case DicomTransferSyntax_JPIPReferenced:
+        return "1.2.840.10008.1.2.4.94";
+
+      case DicomTransferSyntax_JPIPReferencedDeflate:
+        return "1.2.840.10008.1.2.4.95";
+
+      case DicomTransferSyntax_MPEG2MainProfileAtMainLevel:
+        return "1.2.840.10008.1.2.4.100";
+
+      case DicomTransferSyntax_MPEG2MainProfileAtHighLevel:
+        return "1.2.840.10008.1.2.4.101";
+
+      case DicomTransferSyntax_MPEG4HighProfileLevel4_1:
+        return "1.2.840.10008.1.2.4.102";
+
+      case DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1:
+        return "1.2.840.10008.1.2.4.103";
+
+      case DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo:
+        return "1.2.840.10008.1.2.4.104";
+
+      case DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo:
+        return "1.2.840.10008.1.2.4.105";
+
+      case DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2:
+        return "1.2.840.10008.1.2.4.106";
+
+      case DicomTransferSyntax_HEVCMainProfileLevel5_1:
+        return "1.2.840.10008.1.2.4.107";
+
+      case DicomTransferSyntax_HEVCMain10ProfileLevel5_1:
+        return "1.2.840.10008.1.2.4.108";
+
+      case DicomTransferSyntax_RLELossless:
+        return "1.2.840.10008.1.2.5";
+
+      case DicomTransferSyntax_RFC2557MimeEncapsulation:
+        return "1.2.840.10008.1.2.6.1";
+
+      case DicomTransferSyntax_XML:
+        return "1.2.840.10008.1.2.6.2";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  bool IsRetiredTransferSyntax(DicomTransferSyntax syntax)
+  {
+    switch (syntax)
+    {
+      case DicomTransferSyntax_LittleEndianImplicit:
+        return false;
+
+      case DicomTransferSyntax_LittleEndianExplicit:
+        return false;
+
+      case DicomTransferSyntax_DeflatedLittleEndianExplicit:
+        return false;
+
+      case DicomTransferSyntax_BigEndianExplicit:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess1:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess2_4:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess3_5:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess6_8:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess7_9:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess10_12:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess11_13:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess14:
+        return false;
+
+      case DicomTransferSyntax_JPEGProcess15:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess16_18:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess17_19:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess20_22:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess21_23:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess24_26:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess25_27:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess28:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess29:
+        return true;
+
+      case DicomTransferSyntax_JPEGProcess14SV1:
+        return false;
+
+      case DicomTransferSyntax_JPEGLSLossless:
+        return false;
+
+      case DicomTransferSyntax_JPEGLSLossy:
+        return false;
+
+      case DicomTransferSyntax_JPEG2000LosslessOnly:
+        return false;
+
+      case DicomTransferSyntax_JPEG2000:
+        return false;
+
+      case DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly:
+        return false;
+
+      case DicomTransferSyntax_JPEG2000Multicomponent:
+        return false;
+
+      case DicomTransferSyntax_JPIPReferenced:
+        return false;
+
+      case DicomTransferSyntax_JPIPReferencedDeflate:
+        return false;
+
+      case DicomTransferSyntax_MPEG2MainProfileAtMainLevel:
+        return false;
+
+      case DicomTransferSyntax_MPEG2MainProfileAtHighLevel:
+        return false;
+
+      case DicomTransferSyntax_MPEG4HighProfileLevel4_1:
+        return false;
+
+      case DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1:
+        return false;
+
+      case DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo:
+        return false;
+
+      case DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo:
+        return false;
+
+      case DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2:
+        return false;
+
+      case DicomTransferSyntax_HEVCMainProfileLevel5_1:
+        return false;
+
+      case DicomTransferSyntax_HEVCMain10ProfileLevel5_1:
+        return false;
+
+      case DicomTransferSyntax_RLELossless:
+        return false;
+
+      case DicomTransferSyntax_RFC2557MimeEncapsulation:
+        return true;
+
+      case DicomTransferSyntax_XML:
+        return true;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  bool LookupTransferSyntax(DicomTransferSyntax& target,
+                            const std::string& uid)
+  {
+    if (uid == "1.2.840.10008.1.2")
+    {
+      target = DicomTransferSyntax_LittleEndianImplicit;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.1")
+    {
+      target = DicomTransferSyntax_LittleEndianExplicit;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.1.99")
+    {
+      target = DicomTransferSyntax_DeflatedLittleEndianExplicit;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.2")
+    {
+      target = DicomTransferSyntax_BigEndianExplicit;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.50")
+    {
+      target = DicomTransferSyntax_JPEGProcess1;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.51")
+    {
+      target = DicomTransferSyntax_JPEGProcess2_4;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.52")
+    {
+      target = DicomTransferSyntax_JPEGProcess3_5;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.53")
+    {
+      target = DicomTransferSyntax_JPEGProcess6_8;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.54")
+    {
+      target = DicomTransferSyntax_JPEGProcess7_9;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.55")
+    {
+      target = DicomTransferSyntax_JPEGProcess10_12;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.56")
+    {
+      target = DicomTransferSyntax_JPEGProcess11_13;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.57")
+    {
+      target = DicomTransferSyntax_JPEGProcess14;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.58")
+    {
+      target = DicomTransferSyntax_JPEGProcess15;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.59")
+    {
+      target = DicomTransferSyntax_JPEGProcess16_18;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.60")
+    {
+      target = DicomTransferSyntax_JPEGProcess17_19;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.61")
+    {
+      target = DicomTransferSyntax_JPEGProcess20_22;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.62")
+    {
+      target = DicomTransferSyntax_JPEGProcess21_23;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.63")
+    {
+      target = DicomTransferSyntax_JPEGProcess24_26;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.64")
+    {
+      target = DicomTransferSyntax_JPEGProcess25_27;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.65")
+    {
+      target = DicomTransferSyntax_JPEGProcess28;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.66")
+    {
+      target = DicomTransferSyntax_JPEGProcess29;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.70")
+    {
+      target = DicomTransferSyntax_JPEGProcess14SV1;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.80")
+    {
+      target = DicomTransferSyntax_JPEGLSLossless;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.81")
+    {
+      target = DicomTransferSyntax_JPEGLSLossy;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.90")
+    {
+      target = DicomTransferSyntax_JPEG2000LosslessOnly;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.91")
+    {
+      target = DicomTransferSyntax_JPEG2000;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.92")
+    {
+      target = DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.93")
+    {
+      target = DicomTransferSyntax_JPEG2000Multicomponent;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.94")
+    {
+      target = DicomTransferSyntax_JPIPReferenced;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.95")
+    {
+      target = DicomTransferSyntax_JPIPReferencedDeflate;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.100")
+    {
+      target = DicomTransferSyntax_MPEG2MainProfileAtMainLevel;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.101")
+    {
+      target = DicomTransferSyntax_MPEG2MainProfileAtHighLevel;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.102")
+    {
+      target = DicomTransferSyntax_MPEG4HighProfileLevel4_1;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.103")
+    {
+      target = DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.104")
+    {
+      target = DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.105")
+    {
+      target = DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.106")
+    {
+      target = DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.107")
+    {
+      target = DicomTransferSyntax_HEVCMainProfileLevel5_1;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.4.108")
+    {
+      target = DicomTransferSyntax_HEVCMain10ProfileLevel5_1;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.5")
+    {
+      target = DicomTransferSyntax_RLELossless;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.6.1")
+    {
+      target = DicomTransferSyntax_RFC2557MimeEncapsulation;
+      return true;
+    }
+    
+    if (uid == "1.2.840.10008.1.2.6.2")
+    {
+      target = DicomTransferSyntax_XML;
+      return true;
+    }
+    
+    return false;
+  }
+}
--- a/Core/FileBuffer.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/FileBuffer.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/FileBuffer.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/FileBuffer.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/FileStorage/FileInfo.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/FileStorage/FileInfo.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/FileStorage/FilesystemStorage.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/FileStorage/FilesystemStorage.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/FileStorage/FilesystemStorage.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/FileStorage/FilesystemStorage.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/FileStorage/IStorageArea.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/FileStorage/IStorageArea.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/FileStorage/MemoryStorageArea.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/FileStorage/MemoryStorageArea.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/FileStorage/MemoryStorageArea.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/FileStorage/MemoryStorageArea.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/FileStorage/StorageAccessor.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/FileStorage/StorageAccessor.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -34,6 +34,7 @@
 #include "../PrecompiledHeaders.h"
 #include "StorageAccessor.h"
 
+#include "../Compatibility.h"
 #include "../Compression/ZlibCompressor.h"
 #include "../MetricsRegistry.h"
 #include "../OrthancException.h"
@@ -54,7 +55,7 @@
   class StorageAccessor::MetricsTimer : public boost::noncopyable
   {
   private:
-    std::auto_ptr<MetricsRegistry::Timer>  timer_;
+    std::unique_ptr<MetricsRegistry::Timer>  timer_;
 
   public:
     MetricsTimer(StorageAccessor& that,
--- a/Core/FileStorage/StorageAccessor.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/FileStorage/StorageAccessor.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpClient.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpClient.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -45,7 +45,6 @@
 #include <boost/algorithm/string/predicate.hpp>
 #include <boost/thread/mutex.hpp>
 
-
 // Default timeout = 60 seconds (in Orthanc <= 1.5.6, it was 10 seconds)
 static const unsigned int DEFAULT_HTTP_TIMEOUT = 60;
 
@@ -209,7 +208,8 @@
   {
   private:
     HttpClient::IRequestBody*  body_;
-    std::string                buffer_;
+    std::string                sourceBuffer_;
+    size_t                     sourceBufferTransmittedSize_;
 
     size_t CallbackInternal(char* curlBuffer,
                             size_t curlBufferSize)
@@ -225,43 +225,64 @@
       }
 
       // Read chunks from the body stream so as to fill the target buffer
-      std::string chunk;
+      size_t curlBufferFilledSize = 0;
+      size_t sourceRemainingSize = sourceBuffer_.size() - sourceBufferTransmittedSize_;
+      bool hasMore = true;
       
-      while (buffer_.size() < curlBufferSize &&
-             body_->ReadNextChunk(chunk))
+      while (sourceRemainingSize < curlBufferSize && hasMore)
       {
-        buffer_ += chunk;
+        if (sourceRemainingSize > 0)
+        {
+          // transmit the end of current source buffer
+          memcpy(curlBuffer + curlBufferFilledSize,
+                 sourceBuffer_.data() + sourceBufferTransmittedSize_, sourceRemainingSize);
+
+          curlBufferFilledSize += sourceRemainingSize;
+        }
+
+        // start filling a new source buffer
+        sourceBufferTransmittedSize_ = 0;
+        sourceBuffer_.clear();
+
+        hasMore = body_->ReadNextChunk(sourceBuffer_);
+
+        sourceRemainingSize = sourceBuffer_.size();
       }
 
-      size_t s = std::min(buffer_.size(), curlBufferSize);
-      
-      if (s != 0)
+      if (sourceRemainingSize > 0 &&
+          curlBufferSize > curlBufferFilledSize)
       {
-        memcpy(curlBuffer, buffer_.c_str(), s);
+        size_t s = std::min(sourceRemainingSize, curlBufferSize - curlBufferFilledSize);
 
-        // Remove the bytes that were actually sent from the buffer
-        buffer_.erase(0, s);
+        memcpy(curlBuffer + curlBufferFilledSize,
+               sourceBuffer_.data() + sourceBufferTransmittedSize_, s);
+
+        sourceBufferTransmittedSize_ += s;
+        curlBufferFilledSize += s;
       }
 
-      return s;
+      return curlBufferFilledSize;
     }
     
   public:
     CurlRequestBody() :
-      body_(NULL)
+      body_(NULL),
+      sourceBufferTransmittedSize_(0)
     {
     }
 
     void SetBody(HttpClient::IRequestBody& body)
     {
       body_ = &body;
-      buffer_.clear();
+      sourceBufferTransmittedSize_ = 0;
+      sourceBuffer_.clear();
     }
 
     void Clear()
     {
       body_ = NULL;
-      buffer_.clear();
+      sourceBufferTransmittedSize_ = 0;
+      sourceBuffer_.clear();
     }
 
     bool IsValid() const
--- a/Core/HttpClient.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpClient.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/BufferHttpSender.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/BufferHttpSender.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/BufferHttpSender.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/BufferHttpSender.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/EmbeddedResourceHttpHandler.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/EmbeddedResourceHttpHandler.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -51,7 +51,7 @@
       const std::string& baseUri,
       EmbeddedResources::DirectoryResourceId resourceId);
 
-    virtual bool CreateChunkedRequestReader(std::auto_ptr<IChunkedRequestReader>& target,
+    virtual bool CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader>& target,
                                             RequestOrigin origin,
                                             const char* remoteIp,
                                             const char* username,
--- a/Core/HttpServer/FilesystemHttpHandler.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/FilesystemHttpHandler.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/FilesystemHttpHandler.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/FilesystemHttpHandler.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -53,7 +53,7 @@
     FilesystemHttpHandler(const std::string& baseUri,
                           const std::string& root);
 
-    virtual bool CreateChunkedRequestReader(std::auto_ptr<IChunkedRequestReader>& target,
+    virtual bool CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader>& target,
                                             RequestOrigin origin,
                                             const char* remoteIp,
                                             const char* username,
--- a/Core/HttpServer/FilesystemHttpSender.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/FilesystemHttpSender.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/FilesystemHttpSender.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/FilesystemHttpSender.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/HttpContentNegociation.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/HttpContentNegociation.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -170,18 +170,22 @@
   }
 
 
-  void HttpContentNegociation::SelectBestMatch(std::auto_ptr<Reference>& best,
+  void HttpContentNegociation::SelectBestMatch(std::unique_ptr<Reference>& best,
                                                const Handler& handler,
                                                const std::string& type,
                                                const std::string& subtype,
                                                float quality)
   {
-    std::auto_ptr<Reference> match(new Reference(handler, type, subtype, quality));
+    std::unique_ptr<Reference> match(new Reference(handler, type, subtype, quality));
 
     if (best.get() == NULL ||
         *best < *match)
     {
-      best = match;
+#if __cplusplus < 201103L
+      best.reset(match.release());
+#else
+      best = std::move(match);
+#endif
     }
   }
 
@@ -227,7 +231,7 @@
     Tokens mediaRanges;
     Toolbox::TokenizeString(mediaRanges, accept, ',');
 
-    std::auto_ptr<Reference> bestMatch;
+    std::unique_ptr<Reference> bestMatch;
 
     for (Tokens::const_iterator it = mediaRanges.begin();
          it != mediaRanges.end(); ++it)
--- a/Core/HttpServer/HttpContentNegociation.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/HttpContentNegociation.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -32,6 +32,8 @@
 
 #pragma once
 
+#include "../Compatibility.h"
+
 #include <memory>
 #include <boost/noncopyable.hpp>
 #include <map>
@@ -95,7 +97,7 @@
 
     static float GetQuality(const Tokens& parameters);
 
-    static void SelectBestMatch(std::auto_ptr<Reference>& best,
+    static void SelectBestMatch(std::unique_ptr<Reference>& best,
                                 const Handler& handler,
                                 const std::string& type,
                                 const std::string& subtype,
--- a/Core/HttpServer/HttpFileSender.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/HttpFileSender.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/HttpFileSender.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/HttpFileSender.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/HttpOutput.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/HttpOutput.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -463,6 +463,21 @@
     }
 
     boundary = Toolbox::GenerateUuid() + "-" + Toolbox::GenerateUuid();
+
+    /**
+     * Fix for issue #165: "Encapsulation boundaries must not appear
+     * within the encapsulations, and must be no longer than 70
+     * characters, not counting the two leading hyphens."
+     * https://tools.ietf.org/html/rfc1521
+     * https://bitbucket.org/sjodogne/orthanc/issues/165/
+     **/
+    if (boundary.size() != 36 + 1 + 36)  // one UUID contains 36 characters
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    
+    boundary = boundary.substr(0, 70);
+    
     contentTypeHeader = ("multipart/" + subType + "; type=" + tmp + "; boundary=" + boundary);
   }
 
--- a/Core/HttpServer/HttpOutput.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/HttpOutput.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/HttpServer.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/HttpServer.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -61,6 +61,7 @@
 #include <string.h>
 #include <boost/lexical_cast.hpp>
 #include <boost/algorithm/string.hpp>
+#include <boost/filesystem.hpp>
 #include <iostream>
 #include <string.h>
 #include <stdio.h>
@@ -852,7 +853,7 @@
 
       if (!isMultipartForm)
       {
-        std::auto_ptr<IHttpHandler::IChunkedRequestReader> stream;
+        std::unique_ptr<IHttpHandler::IChunkedRequestReader> stream;
 
         if (server.HasHandler())
         {
@@ -968,10 +969,15 @@
           throw OrthancException(ErrorCode_BadParameterType,
                                  "Syntax error in some user-supplied data");
         }
+        catch (boost::filesystem::filesystem_error& e)
+        {
+          throw OrthancException(ErrorCode_InternalError,
+                                 "Error while accessing the filesystem: " + e.path1().string());
+        }
         catch (std::runtime_error&)
         {
-          // Presumably an error while parsing the JSON body
-          throw OrthancException(ErrorCode_BadRequest);
+          throw OrthancException(ErrorCode_BadRequest,
+                                 "Presumably an error while parsing the JSON body");
         }
         catch (std::bad_alloc&)
         {
--- a/Core/HttpServer/HttpServer.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/HttpServer.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/HttpStreamTranscoder.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/HttpStreamTranscoder.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/HttpStreamTranscoder.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/HttpStreamTranscoder.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -35,7 +35,9 @@
 
 #include "BufferHttpSender.h"
 
-#include <memory>  // For std::auto_ptr
+#include "../Compatibility.h"
+
+#include <memory>  // For std::unique_ptr
 
 namespace Orthanc
 {
@@ -49,7 +51,7 @@
     uint64_t           currentChunkOffset_;
     bool               ready_;
 
-    std::auto_ptr<BufferHttpSender>  uncompressed_;
+    std::unique_ptr<BufferHttpSender>  uncompressed_;
 
     void ReadSource(std::string& buffer);
 
--- a/Core/HttpServer/HttpToolbox.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/HttpToolbox.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/HttpToolbox.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/HttpToolbox.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/IHttpHandler.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/IHttpHandler.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -33,6 +33,7 @@
 
 #pragma once
 
+#include "../Compatibility.h"
 #include "../Toolbox.h"
 #include "HttpOutput.h"
 
@@ -73,7 +74,7 @@
      * This function allows to deal with chunked transfers (new in
      * Orthanc 1.5.7). It is only called if "method" is POST or PUT.
      **/
-    virtual bool CreateChunkedRequestReader(std::auto_ptr<IChunkedRequestReader>& target,
+    virtual bool CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader>& target,
                                             RequestOrigin origin,
                                             const char* remoteIp,
                                             const char* username,
--- a/Core/HttpServer/IHttpOutputStream.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/IHttpOutputStream.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/IHttpStreamAnswer.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/IHttpStreamAnswer.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/IIncomingHttpRequestFilter.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/IIncomingHttpRequestFilter.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/MultipartStreamReader.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/MultipartStreamReader.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/MultipartStreamReader.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/MultipartStreamReader.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/StringHttpOutput.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/StringHttpOutput.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/StringHttpOutput.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/StringHttpOutput.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/StringMatcher.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/StringMatcher.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/HttpServer/StringMatcher.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/HttpServer/StringMatcher.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/IDynamicObject.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/IDynamicObject.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/Font.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/Font.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -103,7 +103,7 @@
         throw OrthancException(ErrorCode_BadFont);
       }
 
-      std::auto_ptr<Character> c(new Character);
+      std::unique_ptr<Character> c(new Character);
       
       c->advance_ = info["Advance"].asUInt();
       c->height_ = info["Height"].asUInt();
@@ -407,7 +407,7 @@
     unsigned int width, height;
     ComputeTextExtent(width, height, utf8);
     
-    std::auto_ptr<ImageAccessor>  target(new Image(format, width, height, false));
+    std::unique_ptr<ImageAccessor>  target(new Image(format, width, height, false));
     ImageProcessing::Set(*target, 0, 0, 0, 255);
     Draw(*target, utf8, 0, 0, r, g, b);
 
@@ -420,7 +420,7 @@
     unsigned int width, height;
     ComputeTextExtent(width, height, utf8);
 
-    std::auto_ptr<ImageAccessor>  target(new Image(PixelFormat_Grayscale8, width, height, false));
+    std::unique_ptr<ImageAccessor>  target(new Image(PixelFormat_Grayscale8, width, height, false));
     ImageProcessing::Set(*target, 0);
     Draw(*target, utf8, 0, 0, 255);
 
--- a/Core/Images/Font.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/Font.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/FontRegistry.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/FontRegistry.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -51,7 +51,7 @@
 
   void FontRegistry::AddFromMemory(const std::string& font)
   {
-    std::auto_ptr<Font> f(new Font);
+    std::unique_ptr<Font> f(new Font);
     f->LoadFromMemory(font);
     fonts_.push_back(f.release());
   }
@@ -60,7 +60,7 @@
 #if ORTHANC_SANDBOXED == 0
   void FontRegistry::AddFromFile(const std::string& path)
   {
-    std::auto_ptr<Font> f(new Font);
+    std::unique_ptr<Font> f(new Font);
     f->LoadFromFile(path);
     fonts_.push_back(f.release());
   }
--- a/Core/Images/FontRegistry.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/FontRegistry.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/IImageWriter.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/IImageWriter.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/IImageWriter.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/IImageWriter.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/Image.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/Image.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -33,6 +33,7 @@
 
 #include "Image.h"
 
+#include "../Compatibility.h"
 #include "ImageProcessing.h"
 
 #include <memory>
@@ -54,7 +55,7 @@
 
   Image* Image::Clone(const ImageAccessor& source)
   {
-    std::auto_ptr<Image> target(new Image(source.GetFormat(), source.GetWidth(), source.GetHeight(), false));
+    std::unique_ptr<Image> target(new Image(source.GetFormat(), source.GetWidth(), source.GetHeight(), false));
     ImageProcessing::Copy(*target, source);
     return target.release();
   }
--- a/Core/Images/Image.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/Image.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/ImageAccessor.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/ImageAccessor.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/ImageAccessor.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/ImageAccessor.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/ImageBuffer.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/ImageBuffer.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/ImageBuffer.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/ImageBuffer.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/ImageProcessing.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/ImageProcessing.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -39,6 +39,17 @@
 #include "PixelTraits.h"
 #include "../OrthancException.h"
 
+#ifdef __EMSCRIPTEN__
+/* 
+   Avoid this error:
+   -----------------
+   .../boost/math/special_functions/round.hpp:118:12: warning: implicit conversion from 'std::__2::numeric_limits<long long>::type' (aka 'long long') to 'float' changes value from 9223372036854775807 to 9223372036854775808 [-Wimplicit-int-float-conversion]
+   .../mnt/c/osi/dev/orthanc/Core/Images/ImageProcessing.cpp:333:28: note: in instantiation of function template specialization 'boost::math::llround<float>' requested here
+   .../mnt/c/osi/dev/orthanc/Core/Images/ImageProcessing.cpp:1006:9: note: in instantiation of function template specialization 'Orthanc::MultiplyConstantInternal<unsigned char, true>' requested here
+*/
+#pragma GCC diagnostic ignored "-Wimplicit-int-float-conversion"
+#endif 
+
 #include <boost/math/special_functions/round.hpp>
 
 #include <cassert>
@@ -56,6 +67,11 @@
     return sqrt(dx * dx + dy * dy);
   }
 
+  double ImageProcessing::ImagePoint::GetDistanceToLine(double a, double b, double c) const // where ax + by + c = 0 is the equation of the line
+  {
+    return std::abs(a * static_cast<double>(GetX()) + b * static_cast<double>(GetY()) + c) / pow(a * a + b * b, 0.5);
+  }
+
   template <typename TargetType, typename SourceType>
   static void ConvertInternal(ImageAccessor& target,
                               const ImageAccessor& source)
@@ -369,28 +385,47 @@
   }
 
 
-  template <typename PixelType,
-            bool UseRound>
-  static void ShiftScaleInternal(ImageAccessor& image,
-                                 float offset,
-                                 float scaling,
-                                 const PixelType LowestValue = std::numeric_limits<PixelType>::min())
+  // Computes "a * x + b" at each pixel => Note that this is not the
+  // same convention as in "ShiftScale()"
+  template <typename TargetType,
+            typename SourceType,
+            bool UseRound,
+            bool Invert>
+  static void ShiftScaleInternal(ImageAccessor& target,
+                                 const ImageAccessor& source,
+                                 float a,
+                                 float b,
+                                 const TargetType LowestValue)
+  // This function can be applied inplace (source == target)
   {
-    const PixelType minPixelValue = LowestValue;
-    const PixelType maxPixelValue = std::numeric_limits<PixelType>::max();
+    if (source.GetWidth() != target.GetWidth() ||
+        source.GetHeight() != target.GetHeight())
+    {
+      throw OrthancException(ErrorCode_IncompatibleImageSize);
+    }
+
+    if (&source == &target &&
+        source.GetFormat() != target.GetFormat())
+    {
+      throw OrthancException(ErrorCode_IncompatibleImageFormat);
+    }
+    
+    const TargetType minPixelValue = LowestValue;
+    const TargetType maxPixelValue = std::numeric_limits<TargetType>::max();
     const float minFloatValue = static_cast<float>(LowestValue);
     const float maxFloatValue = static_cast<float>(maxPixelValue);
 
-    const unsigned int height = image.GetHeight();
-    const unsigned int width = image.GetWidth();
+    const unsigned int height = target.GetHeight();
+    const unsigned int width = target.GetWidth();
     
     for (unsigned int y = 0; y < height; y++)
     {
-      PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
+      TargetType* p = reinterpret_cast<TargetType*>(target.GetRow(y));
+      const SourceType* q = reinterpret_cast<const SourceType*>(source.GetRow(y));
 
-      for (unsigned int x = 0; x < width; x++, p++)
+      for (unsigned int x = 0; x < width; x++, p++, q++)
       {
-        float v = (static_cast<float>(*p) + offset) * scaling;
+        float v = a * static_cast<float>(*q) + b;
 
         if (v >= maxFloatValue)
         {
@@ -403,16 +438,56 @@
         else if (UseRound)
         {
           // The "round" operation is very costly
-          *p = static_cast<PixelType>(boost::math::iround(v));
+          *p = static_cast<TargetType>(boost::math::iround(v));
         }
         else
         {
-          *p = static_cast<PixelType>(v);
+          *p = static_cast<TargetType>(std::floor(v));
+        }
+
+        if (Invert)
+        {
+          *p = maxPixelValue - *p;
         }
       }
     }
   }
 
+  template <typename PixelType>
+  static void ShiftRightInternal(ImageAccessor& image,
+                                 unsigned int shift)
+  {
+    const unsigned int height = image.GetHeight();
+    const unsigned int width = image.GetWidth();
+
+    for (unsigned int y = 0; y < height; y++)
+    {
+      PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
+
+      for (unsigned int x = 0; x < width; x++, p++)
+      {
+        *p = *p >> shift;
+      }
+    }
+  }
+
+  template <typename PixelType>
+  static void ShiftLeftInternal(ImageAccessor& image,
+                                unsigned int shift)
+  {
+    const unsigned int height = image.GetHeight();
+    const unsigned int width = image.GetWidth();
+
+    for (unsigned int y = 0; y < height; y++)
+    {
+      PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
+
+      for (unsigned int x = 0; x < width; x++, p++)
+      {
+        *p = *p << shift;
+      }
+    }
+  }
 
   void ImageProcessing::Copy(ImageAccessor& target,
                              const ImageAccessor& source)
@@ -438,6 +513,104 @@
     }
   }
 
+  template <typename TargetType, typename SourceType>
+  static void ApplyWindowingInternal(ImageAccessor& target,
+                                     const ImageAccessor& source,
+                                     float windowCenter,
+                                     float windowWidth,
+                                     float rescaleSlope,
+                                     float rescaleIntercept,
+                                     bool invert)
+  {
+    assert(sizeof(SourceType) == source.GetBytesPerPixel() &&
+           sizeof(TargetType) == target.GetBytesPerPixel());
+    
+    // WARNING - "::min()" should be replaced by "::lowest()" if
+    // dealing with float or double (which is not the case so far)
+    assert(sizeof(TargetType) <= 2);  // Safeguard to remember about "float/double"
+    const TargetType minTargetValue = std::numeric_limits<TargetType>::min();
+    const TargetType maxTargetValue = std::numeric_limits<TargetType>::max();
+    const float maxFloatValue = static_cast<float>(maxTargetValue);
+    
+    const float windowIntercept = windowCenter - windowWidth / 2.0f;
+    const float windowSlope = (maxFloatValue + 1.0f) / windowWidth;
+
+    const float a = rescaleSlope * windowSlope;
+    const float b = (rescaleIntercept - windowIntercept) * windowSlope;
+
+    if (invert)
+    {
+      ShiftScaleInternal<TargetType, SourceType, false, true>(target, source, a, b, minTargetValue);
+    }
+    else
+    {
+      ShiftScaleInternal<TargetType, SourceType, false, false>(target, source, a, b, minTargetValue);
+    }
+  }
+
+  void ImageProcessing::ApplyWindowing_Deprecated(ImageAccessor& target,
+                                                  const ImageAccessor& source,
+                                                  float windowCenter,
+                                                  float windowWidth,
+                                                  float rescaleSlope,
+                                                  float rescaleIntercept,
+                                                  bool invert)
+  {
+    if (target.GetWidth() != source.GetWidth() ||
+        target.GetHeight() != source.GetHeight())
+    {
+      throw OrthancException(ErrorCode_IncompatibleImageSize);
+    }
+
+    switch (source.GetFormat())
+    {
+      case Orthanc::PixelFormat_Float32:
+      {
+        switch (target.GetFormat())
+        {
+          case Orthanc::PixelFormat_Grayscale8:
+            ApplyWindowingInternal<uint8_t, float>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert);
+            break;
+          case Orthanc::PixelFormat_Grayscale16:
+            ApplyWindowingInternal<uint16_t, float>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert);
+            break;
+          default:
+            throw OrthancException(ErrorCode_NotImplemented);
+        }
+      };break;
+      case Orthanc::PixelFormat_Grayscale8:
+      {
+        switch (target.GetFormat())
+        {
+          case Orthanc::PixelFormat_Grayscale8:
+            ApplyWindowingInternal<uint8_t, uint8_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert);
+            break;
+          case Orthanc::PixelFormat_Grayscale16:
+            ApplyWindowingInternal<uint16_t, uint8_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert);
+            break;
+          default:
+            throw OrthancException(ErrorCode_NotImplemented);
+        }
+      };break;
+      case Orthanc::PixelFormat_Grayscale16:
+      {
+        switch (target.GetFormat())
+        {
+          case Orthanc::PixelFormat_Grayscale8:
+            ApplyWindowingInternal<uint8_t, uint16_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert);
+            break;
+          case Orthanc::PixelFormat_Grayscale16:
+            ApplyWindowingInternal<uint16_t, uint16_t>(target, source, windowCenter, windowWidth, rescaleSlope, rescaleIntercept, invert);
+            break;
+          default:
+            throw OrthancException(ErrorCode_NotImplemented);
+        }
+      };break;
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
 
   void ImageProcessing::Convert(ImageAccessor& target,
                                 const ImageAccessor& source)
@@ -768,6 +941,29 @@
       return;
     }
 
+    if ((target.GetFormat() == PixelFormat_BGRA32 &&
+         source.GetFormat() == PixelFormat_RGBA32)
+        || (target.GetFormat() == PixelFormat_RGBA32 &&
+            source.GetFormat() == PixelFormat_BGRA32))
+    {
+      for (unsigned int y = 0; y < height; y++)
+      {
+        const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
+        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
+        for (unsigned int x = 0; x < width; x++)
+        {
+          q[0] = p[2];
+          q[1] = p[1];
+          q[2] = p[0];
+          q[3] = p[3];
+          p += 4;
+          q += 4;
+        }
+      }
+
+      return;
+    }
+
     if (target.GetFormat() == PixelFormat_RGB24 &&
         source.GetFormat() == PixelFormat_RGB48)
     {
@@ -909,6 +1105,63 @@
     }
   }
 
+  void ImageProcessing::Set(ImageAccessor& image,
+                            uint8_t red,
+                            uint8_t green,
+                            uint8_t blue,
+                            ImageAccessor& alpha)
+  {
+    uint8_t p[4];
+
+    if (alpha.GetWidth() != image.GetWidth() || alpha.GetHeight() != image.GetHeight())
+    {
+      throw OrthancException(ErrorCode_IncompatibleImageSize);
+    }
+
+    if (alpha.GetFormat() != PixelFormat_Grayscale8)
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    switch (image.GetFormat())
+    {
+      case PixelFormat_RGBA32:
+        p[0] = red;
+        p[1] = green;
+        p[2] = blue;
+        break;
+
+      case PixelFormat_BGRA32:
+        p[0] = blue;
+        p[1] = green;
+        p[2] = red;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    const unsigned int width = image.GetWidth();
+    const unsigned int height = image.GetHeight();
+
+    for (unsigned int y = 0; y < height; y++)
+    {
+      uint8_t* q = reinterpret_cast<uint8_t*>(image.GetRow(y));
+      uint8_t* a = reinterpret_cast<uint8_t*>(alpha.GetRow(y));
+
+      for (unsigned int x = 0; x < width; x++)
+      {
+        for (unsigned int i = 0; i < 3; i++)
+        {
+          q[i] = p[i];
+        }
+        q[3] = *a;
+        q += 4;
+        ++a;
+      }
+    }
+  }
+
 
   void ImageProcessing::ShiftRight(ImageAccessor& image,
                                    unsigned int shift)
@@ -921,9 +1174,52 @@
       return;
     }
 
-    throw OrthancException(ErrorCode_NotImplemented);
+    switch (image.GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+      {
+        ShiftRightInternal<uint8_t>(image, shift);
+        break;
+      }
+
+      case PixelFormat_Grayscale16:
+      {
+        ShiftRightInternal<uint16_t>(image, shift);
+        break;
+      }
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
   }
 
+  void ImageProcessing::ShiftLeft(ImageAccessor& image,
+                                  unsigned int shift)
+  {
+    if (image.GetWidth() == 0 ||
+        image.GetHeight() == 0 ||
+        shift == 0)
+    {
+      // Nothing to do
+      return;
+    }
+
+    switch (image.GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+      {
+        ShiftLeftInternal<uint8_t>(image, shift);
+        break;
+      }
+
+      case PixelFormat_Grayscale16:
+      {
+        ShiftLeftInternal<uint16_t>(image, shift);
+        break;
+      }
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
 
   void ImageProcessing::GetMinMaxIntegerValue(int64_t& minValue,
                                               int64_t& maxValue,
@@ -1076,49 +1372,55 @@
                                    float scaling,
                                    bool useRound)
   {
+    // Rewrite "(x + offset) * scaling" as "a * x + b"
+
+    const float a = scaling;
+    const float b = offset * scaling;
+    
     switch (image.GetFormat())
     {
       case PixelFormat_Grayscale8:
         if (useRound)
         {
-          ShiftScaleInternal<uint8_t, true>(image, offset, scaling);
+          ShiftScaleInternal<uint8_t, uint8_t, true, false>(image, image, a, b, std::numeric_limits<uint8_t>::min());
         }
         else
         {
-          ShiftScaleInternal<uint8_t, false>(image, offset, scaling);
+          ShiftScaleInternal<uint8_t, uint8_t, false, false>(image, image, a, b, std::numeric_limits<uint8_t>::min());
         }
         return;
 
       case PixelFormat_Grayscale16:
         if (useRound)
         {
-          ShiftScaleInternal<uint16_t, true>(image, offset, scaling);
+          ShiftScaleInternal<uint16_t, uint16_t, true, false>(image, image, a, b, std::numeric_limits<uint16_t>::min());
         }
         else
         {
-          ShiftScaleInternal<uint16_t, false>(image, offset, scaling);
+          ShiftScaleInternal<uint16_t, uint16_t, false, false>(image, image, a, b, std::numeric_limits<uint16_t>::min());
         }
         return;
 
       case PixelFormat_SignedGrayscale16:
         if (useRound)
         {
-          ShiftScaleInternal<int16_t, true>(image, offset, scaling);
+          ShiftScaleInternal<int16_t, int16_t, true, false>(image, image, a, b, std::numeric_limits<int16_t>::min());
         }
         else
         {
-          ShiftScaleInternal<int16_t, false>(image, offset, scaling);
+          ShiftScaleInternal<int16_t, int16_t, false, false>(image, image, a, b, std::numeric_limits<int16_t>::min());
         }
         return;
 
       case PixelFormat_Float32:
+        // "::min()" must be replaced by "::lowest()" or "-::max()" if dealing with float or double.
         if (useRound)
         {
-          ShiftScaleInternal<float, true>(image, offset, scaling, -std::numeric_limits<float>::max());
+          ShiftScaleInternal<float, float, true, false>(image, image, a, b, -std::numeric_limits<float>::max());
         }
         else
         {
-          ShiftScaleInternal<float, false>(image, offset, scaling, -std::numeric_limits<float>::max());
+          ShiftScaleInternal<float, float, false, false>(image, image, a, b, -std::numeric_limits<float>::max());
         }
         return;
 
@@ -1128,6 +1430,46 @@
   }
 
 
+  void ImageProcessing::ShiftScale(ImageAccessor& target,
+                                   const ImageAccessor& source,
+                                   float offset,
+                                   float scaling,
+                                   bool useRound)
+  {
+    // Rewrite "(x + offset) * scaling" as "a * x + b"
+
+    const float a = scaling;
+    const float b = offset * scaling;
+    
+    switch (target.GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+
+        switch (source.GetFormat())
+        {
+          case PixelFormat_Float32:
+            if (useRound)
+            {
+              ShiftScaleInternal<uint8_t, float, true, false>(
+                target, source, a, b, std::numeric_limits<uint8_t>::min());
+            }
+            else
+            {
+              ShiftScaleInternal<uint8_t, float, false, false>(
+                target, source, a, b, std::numeric_limits<uint8_t>::min());
+            }
+            return;
+
+          default:
+            throw OrthancException(ErrorCode_NotImplemented);
+        }
+        
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
   void ImageProcessing::Invert(ImageAccessor& image, int64_t maxValue)
   {
     const unsigned int width = image.GetWidth();
@@ -1626,7 +1968,7 @@
       
     for (unsigned int x = 0; x < targetWidth; x++)
     {
-      int sourceX = std::floor((static_cast<float>(x) + 0.5f) * scaleX);
+      int sourceX = static_cast<int>(std::floor((static_cast<float>(x) + 0.5f) * scaleX));
       if (sourceX < 0)
       {
         sourceX = 0;  // Should never happen
@@ -1643,7 +1985,7 @@
       
     for (unsigned int y = 0; y < targetHeight; y++)
     {
-      int sourceY = std::floor((static_cast<float>(y) + 0.5f) * scaleY);
+      int sourceY = static_cast<int>(std::floor((static_cast<float>(y) + 0.5f) * scaleY));
       if (sourceY < 0)
       {
         sourceY = 0;  // Should never happen
@@ -1692,13 +2034,17 @@
       Copy(target, source);
       return;
     }
-      
+
     switch (source.GetFormat())
     {
       case PixelFormat_Grayscale8:
         ResizeInternal<PixelFormat_Grayscale8>(target, source);
         break;
 
+      case PixelFormat_Float32:
+        ResizeInternal<PixelFormat_Float32>(target, source);
+        break;
+
       case PixelFormat_RGB24:
         ResizeInternal<PixelFormat_RGB24>(target, source);
         break;
@@ -1712,8 +2058,8 @@
   ImageAccessor* ImageProcessing::Halve(const ImageAccessor& source,
                                         bool forceMinimalPitch)
   {
-    std::auto_ptr<Image> target(new Image(source.GetFormat(), source.GetWidth() / 2,
-                                          source.GetHeight() / 2, forceMinimalPitch));
+    std::unique_ptr<Image> target(new Image(source.GetFormat(), source.GetWidth() / 2,
+                                            source.GetHeight() / 2, forceMinimalPitch));
     Resize(*target, source);
     return target.release();
   }
@@ -1921,7 +2267,8 @@
         }
 
         // Deal with the right border
-        for (unsigned int x = horizontalAnchor + width - horizontal.size() + 1; x < width; x++)
+        for (unsigned int x = static_cast<unsigned int>(
+               horizontalAnchor + width - horizontal.size() + 1); x < width; x++)
         {
           for (unsigned int c = 0; c < ChannelsCount; c++, p++)
           {
@@ -1953,7 +2300,7 @@
         }
         else
         {
-          rows[k] = reinterpret_cast<const float*>(tmp.GetConstRow(y + k - verticalAnchor));
+          rows[k] = reinterpret_cast<const float*>(tmp.GetConstRow(static_cast<unsigned int>(y + k - verticalAnchor)));
         }
       }
 
@@ -2063,4 +2410,57 @@
 
     SeparableConvolution(image, kernel, 2, kernel, 2);
   }
+
+
+  void ImageProcessing::FitSize(ImageAccessor& target,
+                                const ImageAccessor& source)
+  {
+    if (target.GetWidth() == 0 ||
+        target.GetHeight() == 0)
+    {
+      return;
+    }
+
+    if (source.GetWidth() == target.GetWidth() &&
+        source.GetHeight() == target.GetHeight())
+    {
+      Copy(target, source);
+      return;
+    }
+
+    Set(target, 0);
+
+    // Preserve the aspect ratio
+    float cw = static_cast<float>(source.GetWidth());
+    float ch = static_cast<float>(source.GetHeight());
+    float r = std::min(
+      static_cast<float>(target.GetWidth()) / cw,
+      static_cast<float>(target.GetHeight()) / ch);
+
+    unsigned int sw = std::min(static_cast<unsigned int>(boost::math::iround(cw * r)), target.GetWidth());  
+    unsigned int sh = std::min(static_cast<unsigned int>(boost::math::iround(ch * r)), target.GetHeight());
+    Image resized(target.GetFormat(), sw, sh, false);
+  
+    //ImageProcessing::SmoothGaussian5x5(source);
+    ImageProcessing::Resize(resized, source);
+
+    assert(target.GetWidth() >= resized.GetWidth() &&
+           target.GetHeight() >= resized.GetHeight());
+    unsigned int offsetX = (target.GetWidth() - resized.GetWidth()) / 2;
+    unsigned int offsetY = (target.GetHeight() - resized.GetHeight()) / 2;
+
+    ImageAccessor region;
+    target.GetRegion(region, offsetX, offsetY, resized.GetWidth(), resized.GetHeight());
+    ImageProcessing::Copy(region, resized);
+  }
+
+
+  ImageAccessor* ImageProcessing::FitSize(const ImageAccessor& source,
+                                          unsigned int width,
+                                          unsigned int height)
+  {
+    std::unique_ptr<ImageAccessor> target(new Image(source.GetFormat(), width, height, false));
+    FitSize(*target, source);
+    return target.release();
+  }
 }
--- a/Core/Images/ImageProcessing.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/ImageProcessing.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -72,6 +72,8 @@
       }
 
       double GetDistanceTo(const ImagePoint& other) const;
+
+      double GetDistanceToLine(double a, double b, double c) const; // where ax + by + c = 0 is the equation of the line
     };
 
     void Copy(ImageAccessor& target,
@@ -80,6 +82,14 @@
     void Convert(ImageAccessor& target,
                  const ImageAccessor& source);
 
+    void ApplyWindowing_Deprecated(ImageAccessor& target,
+                                   const ImageAccessor& source,
+                                   float windowCenter,
+                                   float windowWidth,
+                                   float rescaleSlope,
+                                   float rescaleIntercept,
+                                   bool invert);
+
     void Set(ImageAccessor& image,
              int64_t value);
 
@@ -89,9 +99,18 @@
              uint8_t blue,
              uint8_t alpha);
 
+    void Set(ImageAccessor& image,
+             uint8_t red,
+             uint8_t green,
+             uint8_t blue,
+             ImageAccessor& alpha);
+
     void ShiftRight(ImageAccessor& target,
                     unsigned int shift);
 
+    void ShiftLeft(ImageAccessor& target,
+                   unsigned int shift);
+
     void GetMinMaxIntegerValue(int64_t& minValue,
                                int64_t& maxValue,
                                const ImageAccessor& image);
@@ -108,12 +127,18 @@
                           float factor,
                           bool useRound);
 
-    // "useRound" is expensive
+    // Computes "(x + offset) * scaling" inplace. "useRound" is expensive.
     void ShiftScale(ImageAccessor& image,
                     float offset,
                     float scaling,
                     bool useRound);
 
+    void ShiftScale(ImageAccessor& target,
+                    const ImageAccessor& source,
+                    float offset,
+                    float scaling,
+                    bool useRound);
+
     void Invert(ImageAccessor& image);
 
     void Invert(ImageAccessor& image, int64_t maxValue);
@@ -156,5 +181,12 @@
                               size_t verticalAnchor);
 
     void SmoothGaussian5x5(ImageAccessor& image);
+
+    void FitSize(ImageAccessor& target,
+                 const ImageAccessor& source);
+    
+    ImageAccessor* FitSize(const ImageAccessor& source,
+                           unsigned int width,
+                           unsigned int height);
   }
 }
--- a/Core/Images/ImageTraits.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/ImageTraits.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/JpegErrorManager.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/JpegErrorManager.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/JpegErrorManager.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/JpegErrorManager.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/JpegReader.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/JpegReader.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/JpegReader.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/JpegReader.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/JpegWriter.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/JpegWriter.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/JpegWriter.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/JpegWriter.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/PamReader.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/PamReader.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/PamReader.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/PamReader.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/PamWriter.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/PamWriter.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/PamWriter.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/PamWriter.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/PixelTraits.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/PixelTraits.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -383,6 +383,20 @@
     }
     
     ORTHANC_FORCE_INLINE
+    static void SetMinValue(PixelType& target)
+    {
+      // std::numeric_limits<float>::lowest is not supported on
+      // all compilers (for instance, Visual Studio 9.0 2008)
+      target = -std::numeric_limits<float>::max();
+    }
+
+    ORTHANC_FORCE_INLINE
+    static void SetMaxValue(PixelType& target)
+    {
+      target = std::numeric_limits<float>::max();
+    }
+
+    ORTHANC_FORCE_INLINE
     static void FloatToPixel(PixelType& target,
                              float value)
     {
--- a/Core/Images/PngReader.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/PngReader.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/PngReader.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/PngReader.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/PngWriter.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/PngWriter.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Images/PngWriter.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Images/PngWriter.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/GenericJobUnserializer.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/GenericJobUnserializer.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/GenericJobUnserializer.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/GenericJobUnserializer.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/IJob.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/IJob.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -50,7 +50,7 @@
     // Method called once the job enters the jobs engine
     virtual void Start() = 0;
     
-    virtual JobStepResult Step() = 0;
+    virtual JobStepResult Step(const std::string& jobId) = 0;
 
     // Method called once the job is resubmitted after a failure
     virtual void Reset() = 0;
--- a/Core/JobsEngine/IJobUnserializer.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/IJobUnserializer.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/JobInfo.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/JobInfo.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -32,6 +32,22 @@
 
 
 #include "../PrecompiledHeaders.h"
+
+#ifdef __EMSCRIPTEN__
+/* 
+Avoid this error:
+
+.../boost/math/special_functions/round.hpp:118:12: warning: implicit conversion from 'std::__2::numeric_limits<long long>::type' (aka 'long long') to 'float' changes value from 9223372036854775807 to 9223372036854775808 [-Wimplicit-int-float-conversion]
+.../boost/math/special_functions/round.hpp:125:11: note: in instantiation of function template specialization 'boost::math::llround<float, boost::math::policies::policy<boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy> >' requested here
+.../orthanc/Core/JobsEngine/JobInfo.cpp:69:44: note: in instantiation of function template specialization 'boost::math::llround<float>' requested here
+
+.../boost/math/special_functions/round.hpp:86:12: warning: implicit conversion from 'std::__2::numeric_limits<int>::type' (aka 'int') to 'float' changes value from 2147483647 to 2147483648 [-Wimplicit-int-float-conversion]
+.../boost/math/special_functions/round.hpp:93:11: note: in instantiation of function template specialization 'boost::math::iround<float, boost::math::policies::policy<boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy, boost::math::policies::default_policy> >' requested here
+.../orthanc/Core/JobsEngine/JobInfo.cpp:133:39: note: in instantiation of function template specialization 'boost::math::iround<float>' requested here
+*/
+#pragma GCC diagnostic ignored "-Wimplicit-int-float-conversion"
+#endif 
+
 #include "JobInfo.h"
 
 #include "../OrthancException.h"
--- a/Core/JobsEngine/JobInfo.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/JobInfo.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/JobStatus.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/JobStatus.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/JobStatus.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/JobStatus.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/JobStepResult.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/JobStepResult.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/JobStepResult.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/JobStepResult.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/JobsEngine.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/JobsEngine.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -71,7 +71,7 @@
 
     try
     {
-      result = running.GetJob().Step();
+      result = running.GetJob().Step(running.GetId());
     }
     catch (OrthancException& e)
     {
--- a/Core/JobsEngine/JobsEngine.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/JobsEngine.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -35,11 +35,13 @@
 
 #include "JobsRegistry.h"
 
+#include "../Compatibility.h"
+
 #include <boost/thread.hpp>
 
 namespace Orthanc
 {
-  class JobsEngine
+  class JobsEngine : public boost::noncopyable
   {
   private:
     enum State
@@ -52,7 +54,7 @@
 
     boost::mutex                 stateMutex_;
     State                        state_;
-    std::auto_ptr<JobsRegistry>  registry_;
+    std::unique_ptr<JobsRegistry>  registry_;
     boost::thread                retryHandler_;
     unsigned int                 threadSleep_;
     std::vector<boost::thread*>  workers_;
--- a/Core/JobsEngine/JobsRegistry.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/JobsRegistry.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -58,7 +58,7 @@
     std::string                       id_;
     JobState                          state_;
     std::string                       jobType_;
-    std::auto_ptr<IJob>               job_;
+    std::unique_ptr<IJob>             job_;
     int                               priority_;  // "+inf()" means highest priority
     boost::posix_time::ptime          creationTime_;
     boost::posix_time::ptime          lastStateChangeTime_;
@@ -669,7 +669,7 @@
       throw OrthancException(ErrorCode_NullPointer);
     }
     
-    std::auto_ptr<JobHandler>  protection(handler);
+    std::unique_ptr<JobHandler>  protection(handler);
 
     {
       boost::mutex::scoped_lock lock(mutex_);
@@ -1396,7 +1396,7 @@
     for (Json::Value::Members::const_iterator it = members.begin();
          it != members.end(); ++it)
     {
-      std::auto_ptr<JobHandler> job;
+      std::unique_ptr<JobHandler> job;
 
       try
       {
--- a/Core/JobsEngine/JobsRegistry.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/JobsRegistry.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/Operations/IJobOperation.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/Operations/IJobOperation.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/Operations/JobOperationValue.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/Operations/JobOperationValue.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/Operations/JobOperationValues.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/Operations/JobOperationValues.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -129,7 +129,7 @@
       throw OrthancException(ErrorCode_BadFileFormat);
     }
 
-    std::auto_ptr<JobOperationValues> result(new JobOperationValues);
+    std::unique_ptr<JobOperationValues> result(new JobOperationValues);
 
     result->Reserve(source.size());
     
--- a/Core/JobsEngine/Operations/JobOperationValues.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/Operations/JobOperationValues.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/Operations/LogJobOperation.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/Operations/LogJobOperation.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/Operations/LogJobOperation.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/Operations/LogJobOperation.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/Operations/NullOperationValue.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/Operations/NullOperationValue.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/Operations/SequenceOfOperationsJob.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/Operations/SequenceOfOperationsJob.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -56,12 +56,12 @@
   class SequenceOfOperationsJob::Operation : public boost::noncopyable
   {
   private:
-    size_t                             index_;
-    std::auto_ptr<IJobOperation>       operation_;
-    std::auto_ptr<JobOperationValues>  originalInputs_;
-    std::auto_ptr<JobOperationValues>  workInputs_;
-    std::list<Operation*>              nextOperations_;
-    size_t                             currentInput_;
+    size_t                               index_;
+    std::unique_ptr<IJobOperation>       operation_;
+    std::unique_ptr<JobOperationValues>  originalInputs_;
+    std::unique_ptr<JobOperationValues>  workInputs_;
+    std::list<Operation*>                nextOperations_;
+    size_t                               currentInput_;
 
   public:
     Operation(size_t index,
@@ -319,7 +319,7 @@
   }
 
 
-  JobStepResult SequenceOfOperationsJob::Step()
+  JobStepResult SequenceOfOperationsJob::Step(const std::string& jobId)
   {
     boost::mutex::scoped_lock lock(mutex_);
 
--- a/Core/JobsEngine/Operations/SequenceOfOperationsJob.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/Operations/SequenceOfOperationsJob.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -126,30 +126,30 @@
                    size_t output);
     };
 
-    virtual void Start()
+    virtual void Start() ORTHANC_OVERRIDE
     {
     }
 
-    virtual JobStepResult Step();
+    virtual JobStepResult Step(const std::string& jobId) ORTHANC_OVERRIDE;
 
-    virtual void Reset();
+    virtual void Reset() ORTHANC_OVERRIDE;
 
-    virtual void Stop(JobStopReason reason);
+    virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE;
 
-    virtual float GetProgress();
+    virtual float GetProgress() ORTHANC_OVERRIDE;
 
-    virtual void GetJobType(std::string& target)
+    virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE
     {
       target = "SequenceOfOperations";
     }
 
-    virtual void GetPublicContent(Json::Value& value);
+    virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE;
 
-    virtual bool Serialize(Json::Value& value);
+    virtual bool Serialize(Json::Value& value) ORTHANC_OVERRIDE;
 
     virtual bool GetOutput(std::string& output,
                            MimeType& mime,
-                           const std::string& key)
+                           const std::string& key) ORTHANC_OVERRIDE
     {
       return false;
     }
--- a/Core/JobsEngine/Operations/StringOperationValue.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/Operations/StringOperationValue.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/JobsEngine/SetOfCommandsJob.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/SetOfCommandsJob.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -145,7 +145,7 @@
   }
       
 
-  JobStepResult SetOfCommandsJob::Step()
+  JobStepResult SetOfCommandsJob::Step(const std::string& jobId)
   {
     if (!started_)
     {
@@ -169,7 +169,7 @@
     try
     {
       // Not at the trailing step: Handle the current command
-      if (!commands_[position_]->Execute())
+      if (!commands_[position_]->Execute(jobId))
       {
         // Error
         if (!permissive_)
@@ -250,7 +250,7 @@
                                      const Json::Value& source) :
     started_(false)
   {
-    std::auto_ptr<ICommandUnserializer> raii(unserializer);
+    std::unique_ptr<ICommandUnserializer> raii(unserializer);
 
     permissive_ = SerializationToolbox::ReadBoolean(source, KEY_PERMISSIVE);
     position_ = SerializationToolbox::ReadUnsignedInteger(source, KEY_POSITION);
--- a/Core/JobsEngine/SetOfCommandsJob.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/SetOfCommandsJob.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -49,7 +49,7 @@
       {
       }
 
-      virtual bool Execute() = 0;
+      virtual bool Execute(const std::string& jobId) = 0;
 
       virtual void Serialize(Json::Value& target) const = 0;
     };
@@ -110,14 +110,14 @@
 
     void SetPermissive(bool permissive);
 
-    virtual void Reset();
+    virtual void Reset() ORTHANC_OVERRIDE;
     
-    virtual void Start()
+    virtual void Start() ORTHANC_OVERRIDE
     {
       started_ = true;
     }
     
-    virtual float GetProgress();
+    virtual float GetProgress() ORTHANC_OVERRIDE;
 
     bool IsStarted() const
     {
@@ -126,15 +126,15 @@
 
     const ICommand& GetCommand(size_t index) const;
       
-    virtual JobStepResult Step();
+    virtual JobStepResult Step(const std::string& jobId) ORTHANC_OVERRIDE;
     
-    virtual void GetPublicContent(Json::Value& value);
+    virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE;
     
-    virtual bool Serialize(Json::Value& target);
+    virtual bool Serialize(Json::Value& target) ORTHANC_OVERRIDE;
 
     virtual bool GetOutput(std::string& output,
                            MimeType& mime,
-                           const std::string& key)
+                           const std::string& key) ORTHANC_OVERRIDE
     {
       return false;
     }
--- a/Core/JobsEngine/SetOfInstancesJob.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/SetOfInstancesJob.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -60,7 +60,7 @@
       return instance_;
     }
       
-    virtual bool Execute()
+    virtual bool Execute(const std::string& jobId) ORTHANC_OVERRIDE
     {
       if (!that_.HandleInstance(instance_))
       {
@@ -73,7 +73,7 @@
       }
     }
 
-    virtual void Serialize(Json::Value& target) const
+    virtual void Serialize(Json::Value& target) const ORTHANC_OVERRIDE
     {
       target = instance_;
     }
@@ -91,12 +91,12 @@
     {
     }       
       
-    virtual bool Execute()
+    virtual bool Execute(const std::string& jobId) ORTHANC_OVERRIDE
     {
       return that_.HandleTrailingStep();
     }
 
-    virtual void Serialize(Json::Value& target) const
+    virtual void Serialize(Json::Value& target) const ORTHANC_OVERRIDE
     {
       target = Json::nullValue;
     }
--- a/Core/JobsEngine/SetOfInstancesJob.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/JobsEngine/SetOfInstancesJob.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Logging.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Logging.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -315,6 +315,7 @@
  * behavior from Google Log.
  *********************************************************/
 
+#include "Compatibility.h"
 #include "OrthancException.h"
 #include "Enumerations.h"
 #include "Toolbox.h"
@@ -344,7 +345,7 @@
     std::ostream* warning_;
     std::ostream* info_;
 
-    std::auto_ptr<std::ofstream> file_;
+    std::unique_ptr<std::ofstream> file_;
 
     LoggingContext() : 
       infoEnabled_(false),
@@ -372,7 +373,7 @@
 
 
 
-static std::auto_ptr<LoggingContext> loggingContext_;
+static std::unique_ptr<LoggingContext> loggingContext_;
 static boost::mutex  loggingMutex_;
 
 
@@ -424,7 +425,7 @@
     }
 
 
-    static void PrepareLogFolder(std::auto_ptr<std::ofstream>& file,
+    static void PrepareLogFolder(std::unique_ptr<std::ofstream>& file,
                                  const std::string& suffix,
                                  const std::string& directory)
     {
@@ -455,7 +456,7 @@
     void Reset()
     {
       // Recover the old logging context
-      std::auto_ptr<LoggingContext> old;
+      std::unique_ptr<LoggingContext> old;
 
       {
         boost::mutex::scoped_lock lock(loggingMutex_);
@@ -465,7 +466,11 @@
         }
         else
         {
-          old = loggingContext_;
+#if __cplusplus < 201103L
+          old.reset(loggingContext_.release());
+#else
+          old = std::move(loggingContext_);
+#endif
 
           // Create a new logging context, 
           loggingContext_.reset(new LoggingContext);
@@ -512,7 +517,7 @@
       if (!memento->valid_)
         throw std::runtime_error("Memento already used");
       memento->valid_ = false;
-      std::auto_ptr<LoggingMementoImpl> deleter(memento);
+      std::unique_ptr<LoggingMementoImpl> deleter(memento);
       {
         boost::mutex::scoped_lock lock(loggingMutex_);
         loggingContext_.reset(new LoggingContext);
@@ -553,7 +558,7 @@
       }
     }
 
-    bool IsInfoLevelEnable()
+    bool IsInfoLevelEnabled()
     {
       boost::mutex::scoped_lock lock(loggingMutex_);
       assert(loggingContext_.get() != NULL);
@@ -575,7 +580,7 @@
       }
     }
 
-    bool IsTraceLevelEnable()
+    bool IsTraceLevelEnabled()
     {
       boost::mutex::scoped_lock lock(loggingMutex_);
       assert(loggingContext_.get() != NULL);
@@ -584,7 +589,7 @@
     }
 
 
-    static void CheckFile(std::auto_ptr<std::ofstream>& f)
+    static void CheckFile(std::unique_ptr<std::ofstream>& f)
     {
       if (loggingContext_->file_.get() == NULL ||
           !loggingContext_->file_->is_open())
@@ -783,7 +788,14 @@
       std::ostream* infoStream)
     {
       boost::mutex::scoped_lock lock(loggingMutex_);
-      std::auto_ptr<LoggingContext> old = loggingContext_;
+      std::unique_ptr<LoggingContext> old;
+
+#if __cplusplus < 201103L
+      old.reset(loggingContext_.release());
+#else
+      old = std::move(loggingContext_);
+#endif
+      
       loggingContext_.reset(new LoggingContext);
       loggingContext_->error_ = errorStream;
       loggingContext_->warning_ = warningStream;
@@ -797,15 +809,15 @@
 
     FuncStreamBuf<decltype(emscripten_console_error)> 
       globalEmscriptenErrorStreamBuf(emscripten_console_error);
-    std::auto_ptr<std::ostream> globalEmscriptenErrorStream;
+    std::unique_ptr<std::ostream> globalEmscriptenErrorStream;
 
     FuncStreamBuf<decltype(emscripten_console_warn)>
       globalEmscriptenWarningStreamBuf(emscripten_console_warn);
-    std::auto_ptr<std::ostream> globalEmscriptenWarningStream;
+    std::unique_ptr<std::ostream> globalEmscriptenWarningStream;
 
     FuncStreamBuf<decltype(emscripten_console_log)>
       globalEmscriptenInfoStreamBuf(emscripten_console_log);
-    std::auto_ptr<std::ostream> globalEmscriptenInfoStream;
+    std::unique_ptr<std::ostream> globalEmscriptenInfoStream;
 
     void EnableEmscriptenLogging()
     {
--- a/Core/Logging.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Logging.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Lua/LuaContext.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Lua/LuaContext.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Lua/LuaContext.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Lua/LuaContext.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Lua/LuaFunctionCall.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Lua/LuaFunctionCall.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Lua/LuaFunctionCall.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Lua/LuaFunctionCall.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/MetricsRegistry.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/MetricsRegistry.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -34,8 +34,9 @@
 #include "PrecompiledHeaders.h"
 #include "MetricsRegistry.h"
 
+#include "ChunkedBuffer.h"
+#include "Compatibility.h"
 #include "OrthancException.h"
-#include "ChunkedBuffer.h"
 
 namespace Orthanc
 {
@@ -228,7 +229,7 @@
 
     if (found == content_.end())
     {
-      std::auto_ptr<Item> item(new Item(type));
+      std::unique_ptr<Item> item(new Item(type));
       item->Update(value);
       content_[name] = item.release();
     }
--- a/Core/MetricsRegistry.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/MetricsRegistry.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/MultiThreading/IRunnableBySteps.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/MultiThreading/IRunnableBySteps.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/MultiThreading/RunnableWorkersPool.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/MultiThreading/RunnableWorkersPool.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -35,6 +35,7 @@
 #include "RunnableWorkersPool.h"
 
 #include "SharedMessageQueue.h"
+#include "../Compatibility.h"
 #include "../OrthancException.h"
 #include "../Logging.h"
 
@@ -55,7 +56,7 @@
         {
           try
           {
-            std::auto_ptr<IDynamicObject>  obj(that->queue_.Dequeue(100));
+            std::unique_ptr<IDynamicObject>  obj(that->queue_.Dequeue(100));
             if (obj.get() != NULL)
             {
               IRunnableBySteps& runnable = *dynamic_cast<IRunnableBySteps*>(obj.get());
--- a/Core/MultiThreading/RunnableWorkersPool.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/MultiThreading/RunnableWorkersPool.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/MultiThreading/Semaphore.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/MultiThreading/Semaphore.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/MultiThreading/Semaphore.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/MultiThreading/Semaphore.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/MultiThreading/SharedMessageQueue.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/MultiThreading/SharedMessageQueue.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -35,6 +35,8 @@
 #include "SharedMessageQueue.h"
 
 
+#include "../Compatibility.h"
+
 
 /**
  * FIFO (queue):
@@ -137,7 +139,7 @@
       }
     }
 
-    std::auto_ptr<IDynamicObject> message(queue_.front());
+    std::unique_ptr<IDynamicObject> message(queue_.front());
     queue_.pop_front();
 
     if (queue_.empty())
@@ -199,7 +201,7 @@
     {
       while (!queue_.empty())
       {
-        std::auto_ptr<IDynamicObject> message(queue_.front());
+        std::unique_ptr<IDynamicObject> message(queue_.front());
         queue_.pop_front();
       }
 
--- a/Core/MultiThreading/SharedMessageQueue.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/MultiThreading/SharedMessageQueue.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/OrthancException.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/OrthancException.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -33,6 +33,7 @@
 
 #pragma once
 
+#include "Compatibility.h"
 #include "Enumerations.h"
 #include "Logging.h"
 
@@ -53,7 +54,7 @@
     HttpStatus httpStatus_;
 
     // New in Orthanc 1.5.0
-    std::auto_ptr<std::string>  details_;
+    std::unique_ptr<std::string>  details_;
     
   public:
     OrthancException(const OrthancException& other) : 
--- a/Core/Pkcs11.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Pkcs11.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -213,10 +213,11 @@
           !ENGINE_set_load_privkey_function(engine, EngineLoadPrivateKey) ||
 
           !ENGINE_set_RSA(engine, PKCS11_get_rsa_method()) ||
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L // OpenSSL 1.0.2
           !ENGINE_set_ECDSA(engine, PKCS11_get_ecdsa_method()) ||
           !ENGINE_set_ECDH(engine, PKCS11_get_ecdh_method()) ||
-
-#if OPENSSL_VERSION_NUMBER  >= 0x10100002L
+#else
           !ENGINE_set_EC(engine, PKCS11_get_ec_key_method()) ||
 #endif
 
--- a/Core/Pkcs11.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Pkcs11.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/PrecompiledHeaders.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/PrecompiledHeaders.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/PrecompiledHeaders.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/PrecompiledHeaders.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -39,11 +39,11 @@
 
 #if ORTHANC_USE_PRECOMPILED_HEADERS == 1
 
-#include <boost/date_time/posix_time/posix_time.hpp>
-#include <boost/filesystem.hpp>
+//#include <boost/date_time/posix_time/posix_time.hpp>
+//#include <boost/filesystem.hpp>
 #include <boost/lexical_cast.hpp>
-#include <boost/locale.hpp>
-#include <boost/regex.hpp>
+//#include <boost/locale.hpp>
+//#include <boost/regex.hpp>
 #include <boost/thread.hpp>
 #include <boost/thread/shared_mutex.hpp>
 
@@ -53,6 +53,7 @@
 #  include <pugixml.hpp>
 #endif
 
+#include "../Core/Compatibility.h"
 #include "Enumerations.h"
 #include "Logging.h"
 #include "OrthancException.h"
--- a/Core/RestApi/RestApi.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/RestApi/RestApi.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApi.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/RestApi/RestApi.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -34,6 +34,7 @@
 #pragma once
 
 #include "RestApiHierarchy.h"
+#include "../Compatibility.h"
 
 #include <list>
 
@@ -47,7 +48,7 @@
   public:
     static void AutoListChildren(RestApiGetCall& call);
 
-    virtual bool CreateChunkedRequestReader(std::auto_ptr<IChunkedRequestReader>& target,
+    virtual bool CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader>& target,
                                             RequestOrigin origin,
                                             const char* remoteIp,
                                             const char* username,
--- a/Core/RestApi/RestApiCall.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/RestApi/RestApiCall.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiCall.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/RestApi/RestApiCall.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiDeleteCall.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/RestApi/RestApiDeleteCall.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiGetCall.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/RestApi/RestApiGetCall.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiGetCall.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/RestApi/RestApiGetCall.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiHierarchy.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/RestApi/RestApiHierarchy.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiHierarchy.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/RestApi/RestApiHierarchy.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiOutput.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/RestApi/RestApiOutput.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiOutput.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/RestApi/RestApiOutput.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiPath.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/RestApi/RestApiPath.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiPath.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/RestApi/RestApiPath.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiPostCall.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/RestApi/RestApiPostCall.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/RestApi/RestApiPutCall.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/RestApi/RestApiPutCall.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/SQLite/FunctionContext.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/SQLite/FunctionContext.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -121,7 +121,7 @@
 
     void FunctionContext::SetStringResult(const std::string& str)
     {
-      sqlite3_result_text(context_, str.data(), str.size(), SQLITE_TRANSIENT);
+      sqlite3_result_text(context_, str.data(), static_cast<int>(str.size()), SQLITE_TRANSIENT);
     }
   }
 }
--- a/Core/SQLite/Statement.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/SQLite/Statement.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -218,7 +218,7 @@
       CheckOk(sqlite3_bind_text(GetStatement(),
                                 col + 1,
                                 val.data(),
-                                val.size(),
+                                static_cast<int>(val.size()),
                                 SQLITE_TRANSIENT),
               ErrorCode_BadParameterType);
     }
--- a/Core/SQLite/StatementReference.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/SQLite/StatementReference.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -82,8 +82,12 @@
       if (error != SQLITE_OK)
       {
 #if ORTHANC_SQLITE_STANDALONE != 1
-        LOG(ERROR) << "SQLite: " << sqlite3_errmsg(database)
-                   << " (" << sqlite3_extended_errcode(database) << ")";
+        int extended = sqlite3_extended_errcode(database);
+        LOG(ERROR) << "SQLite: " << sqlite3_errmsg(database) << " (" << extended << ")";
+        if (extended == SQLITE_IOERR_SHMSIZE  /* 4874 */)
+        {
+          LOG(ERROR) << "  This probably indicates that your filesystem is full";
+        }        
 #endif
 
         throw OrthancSQLiteException(ErrorCode_SQLitePrepareStatement);
--- a/Core/SerializationToolbox.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/SerializationToolbox.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -320,6 +320,28 @@
     }
 
 
+    void WriteListOfStrings(Json::Value& target,
+                            const std::list<std::string>& values,
+                            const std::string& field)
+    {
+      if (target.type() != Json::objectValue ||
+          target.isMember(field.c_str()))
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+
+      Json::Value& value = target[field];
+
+      value = Json::arrayValue;
+
+      for (std::list<std::string>::const_iterator it = values.begin();
+           it != values.end(); ++it)
+      {
+        value.append(*it);
+      }
+    }
+
+
     void WriteSetOfStrings(Json::Value& target,
                            const std::set<std::string>& values,
                            const std::string& field)
--- a/Core/SerializationToolbox.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/SerializationToolbox.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -83,6 +83,10 @@
                              const std::vector<std::string>& values,
                              const std::string& field);
 
+    void WriteListOfStrings(Json::Value& target,
+                            const std::list<std::string>& values,
+                            const std::string& field);
+
     void WriteSetOfStrings(Json::Value& target,
                            const std::set<std::string>& values,
                            const std::string& field);
--- a/Core/SharedLibrary.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/SharedLibrary.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -62,7 +62,19 @@
     }
 
 #elif defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__)
-    handle_ = ::dlopen(path_.c_str(), RTLD_NOW);
+
+    /**
+     * "RTLD_LOCAL" is the default, and is only present to be
+     * explicit. "RTLD_DEEPBIND" was added in Orthanc 1.6.0, in order
+     * to avoid crashes while loading plugins from the LSB binaries of
+     * the Orthanc core.
+     **/
+#if defined(RTLD_DEEPBIND)  // This is a GNU extension
+    handle_ = ::dlopen(path_.c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND);
+#else
+    handle_ = ::dlopen(path_.c_str(), RTLD_NOW | RTLD_LOCAL);
+#endif
+
     if (handle_ == NULL) 
     {
       std::string explanation;
--- a/Core/SharedLibrary.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/SharedLibrary.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/SystemToolbox.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/SystemToolbox.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/SystemToolbox.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/SystemToolbox.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/TemporaryFile.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/TemporaryFile.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/TemporaryFile.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/TemporaryFile.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Core/Toolbox.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Toolbox.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -34,6 +34,7 @@
 #include "PrecompiledHeaders.h"
 #include "Toolbox.h"
 
+#include "Compatibility.h"
 #include "OrthancException.h"
 #include "Logging.h"
 
@@ -1434,7 +1435,7 @@
 
 
 #if ORTHANC_ENABLE_LOCALE == 1
-  static std::auto_ptr<std::locale>  globalLocale_;
+  static std::unique_ptr<std::locale>  globalLocale_;
 
   static bool SetGlobalLocale(const char* locale)
   {
@@ -1699,7 +1700,11 @@
 #ifdef FIPS_mode_set
     FIPS_mode_set(0);
 #endif
+
+#if !defined(OPENSSL_NO_ENGINE)
     ENGINE_cleanup();
+#endif
+    
     CONF_modules_unload(1);
     EVP_cleanup();
     CRYPTO_cleanup_all_ex_data();
@@ -2073,6 +2078,108 @@
       throw OrthancException(ErrorCode_BadFileFormat, "Invalid UTF-8 string");
     }
   }
+
+
+  std::string Toolbox::LargeHexadecimalToDecimal(const std::string& hex)
+  {
+    /**
+     * NB: Focus of the code below is *not* efficiency, but
+     * readability!
+     **/
+    
+    for (size_t i = 0; i < hex.size(); i++)
+    {
+      const char c = hex[i];
+      if (!((c >= 'A' && c <= 'F') ||
+            (c >= 'a' && c <= 'f') ||
+            (c >= '0' && c <= '9')))
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
+                                        "Not an hexadecimal number");
+      }
+    }
+    
+    std::vector<uint8_t> decimal;
+    decimal.push_back(0);
+
+    for (size_t i = 0; i < hex.size(); i++)
+    {
+      uint8_t hexDigit = static_cast<uint8_t>(Hex2Dec(hex[i]));
+      assert(hexDigit <= 15);
+
+      for (size_t j = 0; j < decimal.size(); j++)
+      {
+        uint8_t val = static_cast<uint8_t>(decimal[j]) * 16 + hexDigit;  // Maximum: 9 * 16 + 15
+        assert(val <= 159 /* == 9 * 16 + 15 */);
+      
+        decimal[j] = val % 10;
+        hexDigit = val / 10;
+        assert(hexDigit <= 15 /* == 159 / 10 */);
+      }
+
+      while (hexDigit > 0)
+      {
+        decimal.push_back(hexDigit % 10);
+        hexDigit /= 10;
+      }
+    }
+
+    size_t start = 0;
+    while (start < decimal.size() &&
+           decimal[start] == '0')
+    {
+      start++;
+    }
+
+    std::string s;
+    s.reserve(decimal.size() - start);
+
+    for (size_t i = decimal.size(); i > start; i--)
+    {
+      s.push_back(decimal[i - 1] + '0');
+    }
+
+    return s;
+  }
+
+
+  std::string Toolbox::GenerateDicomPrivateUniqueIdentifier()
+  {
+    /**
+     * REFERENCE: "Creating a Privately Defined Unique Identifier
+     * (Informative)" / "UUID Derived UID"
+     * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part05/sect_B.2.html
+     * https://stackoverflow.com/a/46316162/881731
+     **/
+
+    std::string uuid = GenerateUuid();
+    assert(IsUuid(uuid) && uuid.size() == 36);
+
+    /**
+     * After removing the four dashes ("-") out of the 36-character
+     * UUID, we get a large hexadecimal number with 32 characters,
+     * each of those characters lying in the range [0,16[. The large
+     * number is thus in the [0,16^32[ = [0,256^16[ range. This number
+     * has a maximum of 39 decimal digits, as can be seen in Python:
+     * 
+     * # python -c 'import math; print(math.log(16**32))/math.log(10))'
+     * 38.531839445
+     *
+     * We now to convert the large hexadecimal number to a decimal
+     * number with up to 39 digits, remove the leading zeros, then
+     * prefix it with "2.25."
+     **/
+
+    // Remove the dashes
+    std::string hex = (uuid.substr(0, 8) +
+                       uuid.substr(9, 4) +
+                       uuid.substr(14, 4) +
+                       uuid.substr(19, 4) +
+                       uuid.substr(24, 12));
+    assert(hex.size() == 32);
+
+    return "2.25." + LargeHexadecimalToDecimal(hex);
+  }
 }
 
 
--- a/Core/Toolbox.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/Toolbox.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -257,6 +257,11 @@
                                 size_t& utf8Length,
                                 const std::string& utf8,
                                 size_t position);
+
+    std::string LargeHexadecimalToDecimal(const std::string& hex);
+
+    // http://dicom.nema.org/medical/dicom/2019a/output/chtml/part05/sect_B.2.html
+    std::string GenerateDicomPrivateUniqueIdentifier();
   }
 }
 
--- a/Core/WebServiceParameters.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/WebServiceParameters.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -304,7 +304,8 @@
             break;
 
           default:
-            throw OrthancException(ErrorCode_BadFileFormat);
+            throw OrthancException(ErrorCode_BadFileFormat,
+                                   "User-defined properties associated with a Web service must be strings: " + *it);
         }
       }
     }
--- a/Core/WebServiceParameters.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Core/WebServiceParameters.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/INSTALL	Wed Mar 18 08:59:06 2020 +0100
+++ b/INSTALL	Thu Mar 19 11:48:30 2020 +0100
@@ -123,7 +123,21 @@
 The option "-T host=x64" is necessary to prevent error "C1060:
 compiler is out of heap space" when compiling Orthanc with ICU.
 
+Native 64-bit Windows build with Microsoft Visual Studio 2017 (msbuild)
+-----------------------------------------------------------------------
+# cd [...]\OrthancBuild
+# cmake -G "Visual Studio 15 2017 Win64" -DMSVC_MULTIPLE_PROCESSES=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DALLOW_DOWNLOADS=ON [...]\Orthanc
 
+Instructions to include support for Asian encodings:
+# cmake -G "Visual Studio 15 2017 Win64" -T host=x64 -DSTATIC_BUILD=ON -DBOOST_LOCALE_BACKEND=icu -DMSVC_MULTIPLE_PROCESSES=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DALLOW_DOWNLOADS=ON [...]\Orthanc
+
+Native 64-bit Windows build with Microsoft Visual Studio 2019 (msbuild)
+-----------------------------------------------------------------------
+# cd [...]\OrthancBuild
+# cmake -G "Visual Studio 16 2019" -A x64 -DMSVC_MULTIPLE_PROCESSES=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DALLOW_DOWNLOADS=ON [...]\Orthanc
+
+Instructions to include support for Asian encodings:
+# cmake -G "Visual Studio 16 2019" -A x64 -T host=x64 -DSTATIC_BUILD=ON -DBOOST_LOCALE_BACKEND=icu -DMSVC_MULTIPLE_PROCESSES=ON -DSTATIC_BUILD=ON -DOPENSSL_NO_CAPIENG=ON -DALLOW_DOWNLOADS=ON [...]\Orthanc
 
 Cross-Compilation for Windows under GNU/Linux
 ---------------------------------------------
--- a/LinuxCompilation.txt	Wed Mar 18 08:59:06 2020 +0100
+++ b/LinuxCompilation.txt	Thu Mar 19 11:48:30 2020 +0100
@@ -119,10 +119,10 @@
 ----------------------------
 
 # sudo apt-get install build-essential unzip cmake mercurial \
-       	       	       uuid-dev libcurl4-openssl-dev liblua5.3-0-dev \
+       	       	       uuid-dev libcurl4-openssl-dev liblua5.3-dev \
        	       	       libgtest-dev libpng-dev libsqlite3-dev libssl-dev libjpeg-dev \
-		       zlib1g-dev libdcmtk2-dev libboost-all-dev libwrap0-dev \
-                       libcharls-dev libjsoncpp-dev libpugixml-dev
+		       zlib1g-dev libdcmtk-dev libboost-all-dev libwrap0-dev \
+                       libcharls-dev libjsoncpp-dev libpugixml-dev tzdata
 
 # cmake -DALLOW_DOWNLOADS=ON \
         -DUSE_GOOGLE_TEST_DEBIAN_PACKAGE=ON \
--- a/NEWS	Wed Mar 18 08:59:06 2020 +0100
+++ b/NEWS	Thu Mar 19 11:48:30 2020 +0100
@@ -2,6 +2,72 @@
 ===============================
 
 
+Version 1.6.0 (2020-03-18)
+==========================
+
+General
+-------
+
+* Support of DICOM storage commitment
+
+REST API
+--------
+
+* API version has been upgraded to 5
+* Added:
+  - "/peers/{id}/system": Test the connectivity with a remote peer
+    (and also retrieve its version number)
+  - "/tools/log-level": Access and/or change the log level without restarting Orthanc
+  - "/instances/{id}/frames/{frame}/rendered" and "/instances/{id}/rendered":
+    Render frames, taking windowing and resizing into account
+  - "/modalities/{...}/storage-commitment": Trigger storage commitment SCU
+  - "/storage-commitment/{...}": Access storage commitment reports
+  - "/storage-commitment/{...}/remove": Remove instances from storage commitment reports
+* Improved:
+  - "/changes": Allow the "limit" argument to be greater than 100
+  - "/instances": Support "Content-Encoding: gzip" to upload gzip-compressed DICOM files
+  - ".../modify" and "/tools/create-dicom": New option "PrivateCreator" for private tags
+  - "/modalities/{...}/store": New Boolean argument "StorageCommitment"
+
+Plugins
+-------
+
+* New sample plugin: "ConnectivityChecks"
+* New primitives to handle storage commitment SCP by plugins
+
+Lua
+---
+
+* New events:
+  - "OnDeletedPatient", "OnDeletedStudy", "OnDeletedSeries", "OnDeletedInstance":
+    triggered when a resource is deleted
+  - "OnUpdatedPatient", "OnUpdatedStudy", "OnUpdatedSeries", "OnUpdatedInstance":
+    triggered when an attachment or a metadata is updated
+
+Maintenance
+-----------
+
+* New configuration options: "DefaultPrivateCreator" and "StorageCommitmentReportsSize"
+* Support of MPEG4 transfer syntaxes in C-Store SCP
+* C-FIND SCU at Instance level now sets the 0008,0052 tag to IMAGE per default (was INSTANCE).
+  Therefore, the "ClearCanvas" and "Dcm4Chee" modality manufacturer have now been deprecated.
+* More strict C-FIND SCP wrt. the DICOM standard: Forbid wildcard
+  matching on some VRs, ignore main tags below the queried level
+* Fix issue #65 (Logging improvements)
+* Fix issue #103 ("queries/.../retrieve" API returns HTTP code 200 even on server errors)
+* Fix issue #140 (Modifying private tags with REST API changes VR from LO to UN)
+* Fix issue #154 (Matching against list of UID-s by C-MOVE)
+* Fix issue #156 (Chunked Dicom-web transfer uses 100% CPU)
+* Fix issue #165 (Boundary parameter in multipart Content-Type is too long)
+* Fix issue #166 (CMake find_boost version is now broken with newer boost/cmake)
+* Fix issue #167 (Job can't be cancelled - Handling of timeouts after established association)
+* Fix issue #168 (Plugins can't read private tags from the configuration file)
+* Upgraded dependencies for static builds (notably on Windows):
+  - dcmtk 3.6.5
+  - openssl 1.1.1d
+  - jsoncpp 0.10.7 for pre-C++11 compilers
+
+
 Version 1.5.8 (2019-10-16)
 ==========================
 
--- a/OrthancExplorer/explorer.js	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancExplorer/explorer.js	Thu Mar 19 11:48:30 2020 +0100
@@ -163,6 +163,22 @@
     return d.toString('dddd, MMMM d, yyyy');
 }
 
+function FormatFloatSequence(s)
+{
+  if (s == undefined || s.length == 0)
+    return "-";
+
+  if (s.indexOf("\\") == -1)
+    return s;
+
+  var oldValues = s.split("\\");
+  var newValues = [];
+  for (var i = 0; i < oldValues.length; i++)
+  {
+    newValues.push(parseFloat(oldValues[i]).toFixed(3));
+  }
+  return newValues.join("\\");
+}
 
 function Sort(arr, fieldExtractor, isInteger, reverse)
 {
@@ -277,6 +293,11 @@
       {
         v = SplitLongUid(v);
       }
+      else if (i == "ImagePositionPatient" ||
+               i == "ImageOrientationPatient")
+      {
+        v = FormatFloatSequence(v);
+      }
       
       target.append($('<p>')
                     .text(i + ': ')
@@ -371,8 +392,7 @@
     "AcquisitionNumber", 
     "InstanceNumber", 
     "InstanceCreationDate", 
-    "InstanceCreationTime",
-    "ImagePositionPatient"
+    "InstanceCreationTime"
   ]);
     
   return CompleteFormatting(node, link, isReverse);
--- a/OrthancServer/Database/Compatibility/DatabaseLookup.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Database/Compatibility/DatabaseLookup.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/Compatibility/DatabaseLookup.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Database/Compatibility/DatabaseLookup.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/Compatibility/ICreateInstance.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Database/Compatibility/ICreateInstance.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/Compatibility/ICreateInstance.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Database/Compatibility/ICreateInstance.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/Compatibility/IGetChildrenMetadata.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Database/Compatibility/IGetChildrenMetadata.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/Compatibility/IGetChildrenMetadata.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Database/Compatibility/IGetChildrenMetadata.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/Compatibility/ILookupResourceAndParent.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Database/Compatibility/ILookupResourceAndParent.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/Compatibility/ILookupResourceAndParent.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Database/Compatibility/ILookupResourceAndParent.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/Compatibility/ILookupResources.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Database/Compatibility/ILookupResources.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/Compatibility/ILookupResources.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Database/Compatibility/ILookupResources.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/Compatibility/ISetResourcesContent.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Database/Compatibility/ISetResourcesContent.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/Compatibility/SetOfResources.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Database/Compatibility/SetOfResources.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -55,7 +55,7 @@
       }
       else
       {
-        std::auto_ptr<Resources> filtered(new Resources);
+        std::unique_ptr<Resources> filtered(new Resources);
 
         for (std::list<int64_t>::const_iterator
                it = resources.begin(); it != resources.end(); ++it)
@@ -66,7 +66,11 @@
           }
         }
 
-        resources_ = filtered;
+#if __cplusplus < 201103L
+        resources_.reset(filtered.release());
+#else
+        resources_ = std::move(filtered);
+#endif
       }
     }
 
@@ -80,7 +84,7 @@
 
       if (resources_.get() != NULL)
       {
-        std::auto_ptr<Resources> children(new Resources);
+        std::unique_ptr<Resources> children(new Resources);
 
         for (Resources::const_iterator it = resources_->begin(); 
              it != resources_->end(); ++it)
@@ -95,7 +99,11 @@
           }
         }
 
-        resources_ = children;
+#if __cplusplus < 201103L
+        resources_.reset(children.release());
+#else
+        resources_ = std::move(children);
+#endif
       }
 
       switch (level_)
--- a/OrthancServer/Database/Compatibility/SetOfResources.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Database/Compatibility/SetOfResources.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -33,6 +33,7 @@
 
 #pragma once
 
+#include "../../../Core/Compatibility.h"
 #include "../IDatabaseWrapper.h"
 #include "ILookupResources.h"
 
@@ -48,9 +49,9 @@
     private:
       typedef std::set<int64_t>  Resources;
 
-      IDatabaseWrapper&         database_;
-      ResourceType              level_;
-      std::auto_ptr<Resources>  resources_;
+      IDatabaseWrapper&           database_;
+      ResourceType                level_;
+      std::unique_ptr<Resources>  resources_;
     
     public:
       SetOfResources(IDatabaseWrapper& database,
--- a/OrthancServer/Database/IDatabaseListener.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Database/IDatabaseListener.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/IDatabaseWrapper.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Database/IDatabaseWrapper.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/ResourcesContent.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Database/ResourcesContent.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/ResourcesContent.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Database/ResourcesContent.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Database/SQLiteDatabaseWrapper.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Database/SQLiteDatabaseWrapper.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -583,9 +583,9 @@
   class SQLiteDatabaseWrapper::Transaction : public IDatabaseWrapper::ITransaction
   {
   private:
-    SQLiteDatabaseWrapper&              that_;
-    std::auto_ptr<SQLite::Transaction>  transaction_;
-    int64_t                             initialDiskSize_;
+    SQLiteDatabaseWrapper&                that_;
+    std::unique_ptr<SQLite::Transaction>  transaction_;
+    int64_t                               initialDiskSize_;
 
   public:
     Transaction(SQLiteDatabaseWrapper& that) :
@@ -1181,7 +1181,7 @@
     resourcesId.clear();
     instancesId.clear();
     
-    std::auto_ptr<SQLite::Statement> statement;
+    std::unique_ptr<SQLite::Statement> statement;
     
     switch (level)
     {
--- a/OrthancServer/Database/SQLiteDatabaseWrapper.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Database/SQLiteDatabaseWrapper.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/DefaultDicomImageDecoder.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/DefaultDicomImageDecoder.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/DicomInstanceOrigin.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/DicomInstanceOrigin.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/DicomInstanceOrigin.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/DicomInstanceOrigin.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/DicomInstanceToStore.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/DicomInstanceToStore.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -157,7 +157,7 @@
     MetadataMap                          metadata_;
 
   private:
-    std::auto_ptr<DicomInstanceHasher>  hasher_;
+    std::unique_ptr<DicomInstanceHasher>  hasher_;
 
     void ComputeMissingInformation()
     {
--- a/OrthancServer/DicomInstanceToStore.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/DicomInstanceToStore.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ExportedResource.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ExportedResource.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ExportedResource.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ExportedResource.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/IDicomImageDecoder.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/IDicomImageDecoder.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/IServerListener.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/IServerListener.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/LuaScripting.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/LuaScripting.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -259,6 +259,114 @@
   };
 
 
+  class LuaScripting::DeleteEvent : public LuaScripting::IEvent
+  {
+  private:
+    ResourceType  level_;
+    std::string   publicId_;
+
+  public:
+    DeleteEvent(ResourceType level,
+                const std::string& publicId) :
+      level_(level),
+      publicId_(publicId)
+    {
+    }
+
+    virtual void Apply(LuaScripting& that)
+    {
+      std::string functionName;
+      
+      switch (level_)
+      {
+        case ResourceType_Patient:
+          functionName = "OnDeletedPatient";
+          break;
+
+        case ResourceType_Study:
+          functionName = "OnDeletedStudy";
+          break;
+
+        case ResourceType_Series:
+          functionName = "OnDeletedSeries";
+          break;
+
+        case ResourceType_Instance:
+          functionName = "OnDeletedInstance";
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+
+      {
+        LuaScripting::Lock lock(that);
+
+        if (lock.GetLua().IsExistingFunction(functionName.c_str()))
+        {
+          LuaFunctionCall call(lock.GetLua(), functionName.c_str());
+          call.PushString(publicId_);
+          call.Execute();
+        }
+      }
+    }
+  };
+
+
+  class LuaScripting::UpdateEvent : public LuaScripting::IEvent
+  {
+  private:
+    ResourceType  level_;
+    std::string   publicId_;
+
+  public:
+    UpdateEvent(ResourceType level,
+                const std::string& publicId) :
+      level_(level),
+      publicId_(publicId)
+    {
+    }
+
+    virtual void Apply(LuaScripting& that)
+    {
+      std::string functionName;
+      
+      switch (level_)
+      {
+        case ResourceType_Patient:
+          functionName = "OnUpdatedPatient";
+          break;
+
+        case ResourceType_Study:
+          functionName = "OnUpdatedStudy";
+          break;
+
+        case ResourceType_Series:
+          functionName = "OnUpdatedSeries";
+          break;
+
+        case ResourceType_Instance:
+          functionName = "OnUpdatedInstance";
+          break;
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+
+      {
+        LuaScripting::Lock lock(that);
+
+        if (lock.GetLua().IsExistingFunction(functionName.c_str()))
+        {
+          LuaFunctionCall call(lock.GetLua(), functionName.c_str());
+          call.PushString(publicId_);
+          call.Execute();
+        }
+      }
+    }
+  };
+
+
   ServerContext* LuaScripting::GetServerContext(lua_State *state)
   {
     const void* value = LuaContext::GetGlobalVariable(state, "_ServerContext");
@@ -502,7 +610,7 @@
 
     if (operation == "modify")
     {
-      std::auto_ptr<DicomModification> modification(new DicomModification);
+      std::unique_ptr<DicomModification> modification(new DicomModification);
       modification->ParseModifyRequest(parameters);
 
       return lock.AddModifyInstanceOperation(context_, modification.release());
@@ -641,7 +749,7 @@
   {
     for (;;)
     {
-      std::auto_ptr<IDynamicObject> event(that->pendingEvents_.Dequeue(100));
+      std::unique_ptr<IDynamicObject> event(that->pendingEvents_.Dequeue(100));
 
       if (event.get() == NULL)
       {
@@ -738,6 +846,15 @@
     {
       pendingEvents_.Enqueue(new StableResourceEvent(change));
     }
+    else if (change.GetChangeType() == ChangeType_Deleted)
+    {
+      pendingEvents_.Enqueue(new DeleteEvent(change.GetResourceType(), change.GetPublicId()));
+    }
+    else if (change.GetChangeType() == ChangeType_UpdatedAttachment ||
+             change.GetChangeType() == ChangeType_UpdatedMetadata)
+    {
+      pendingEvents_.Enqueue(new UpdateEvent(change.GetResourceType(), change.GetPublicId()));
+    }
   }
 
 
--- a/OrthancServer/LuaScripting.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/LuaScripting.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -59,6 +59,8 @@
     class OnStoredInstanceEvent;
     class StableResourceEvent;
     class JobEvent;
+    class DeleteEvent;
+    class UpdateEvent;
 
     static ServerContext* GetServerContext(lua_State *state);
 
--- a/OrthancServer/OrthancConfiguration.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/OrthancConfiguration.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -865,4 +865,11 @@
       return new TemporaryFile;
     }
   }
+
+
+  std::string OrthancConfiguration::GetDefaultPrivateCreator() const
+  {
+    // New configuration option in Orthanc 1.6.0
+    return GetStringParameter("DefaultPrivateCreator", "");
+  }
 }
--- a/OrthancServer/OrthancConfiguration.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/OrthancConfiguration.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -231,5 +231,7 @@
     void ResetServerIndex();
 
     TemporaryFile* CreateTemporaryFile() const;
+
+    std::string GetDefaultPrivateCreator() const;
   };
 }
--- a/OrthancServer/OrthancFindRequestHandler.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/OrthancFindRequestHandler.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -273,7 +273,7 @@
       throw OrthancException(ErrorCode_UnknownResource);  // The resource was deleted in between
     }
 
-    std::auto_ptr<DicomMap> result(new DicomMap);
+    std::unique_ptr<DicomMap> result(new DicomMap);
 
     switch (level)
     {
@@ -394,7 +394,8 @@
             content.append(item);
           }
 
-          dicom.Replace(*tag, content, false, DicomReplaceMode_InsertIfAbsent);
+          dicom.Replace(*tag, content, false, DicomReplaceMode_InsertIfAbsent,
+                        "" /* no private creator */);
         }
       }
 
@@ -536,7 +537,7 @@
                        const DicomMap& mainDicomTags,
                        const Json::Value* dicomAsJson) 
     {
-      std::auto_ptr<DicomMap> counters(ComputeCounters(context_, instanceId, level_, query_));
+      std::unique_ptr<DicomMap> counters(ComputeCounters(context_, instanceId, level_, query_));
 
       AddAnswer(answers_, mainDicomTags, dicomAsJson,
                 queryAsArray_, sequencesToReturn_, counters.get());
--- a/OrthancServer/OrthancFindRequestHandler.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/OrthancFindRequestHandler.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/OrthancHttpHandler.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/OrthancHttpHandler.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -40,7 +40,7 @@
 namespace Orthanc
 {
   bool OrthancHttpHandler::CreateChunkedRequestReader(
-    std::auto_ptr<IHttpHandler::IChunkedRequestReader>& target,
+    std::unique_ptr<IHttpHandler::IChunkedRequestReader>& target,
     RequestOrigin origin,
     const char* remoteIp,
     const char* username,
--- a/OrthancServer/OrthancHttpHandler.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/OrthancHttpHandler.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -50,7 +50,7 @@
     {
     }
 
-    virtual bool CreateChunkedRequestReader(std::auto_ptr<IChunkedRequestReader>& target,
+    virtual bool CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader>& target,
                                             RequestOrigin origin,
                                             const char* remoteIp,
                                             const char* username,
--- a/OrthancServer/OrthancInitialization.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/OrthancInitialization.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/OrthancInitialization.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/OrthancInitialization.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/OrthancMoveRequestHandler.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/OrthancMoveRequestHandler.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -59,7 +59,7 @@
       RemoteModalityParameters remote_;
       std::string originatorAet_;
       uint16_t originatorId_;
-      std::auto_ptr<DicomUserConnection> connection_;
+      std::unique_ptr<DicomUserConnection> connection_;
 
     public:
       SynchronousMove(ServerContext& context,
@@ -116,7 +116,8 @@
           connection_.reset(new DicomUserConnection(localAet_, remote_));
         }
 
-        connection_->Store(dicom, originatorAet_, originatorId_);
+        std::string sopClassUid, sopInstanceUid;  // Unused
+        connection_->Store(sopClassUid, sopInstanceUid, dicom, originatorAet_, originatorId_);
 
         return Status_Success;
       }
@@ -126,10 +127,10 @@
     class AsynchronousMove : public IMoveRequestIterator
     {
     private:
-      ServerContext&                        context_;
-      std::auto_ptr<DicomModalityStoreJob>  job_;
-      size_t                                position_;
-      size_t                                countInstances_;
+      ServerContext&                          context_;
+      std::unique_ptr<DicomModalityStoreJob>  job_;
+      size_t                                  position_;
+      size_t                                  countInstances_;
       
     public:
       AsynchronousMove(ServerContext& context,
@@ -142,7 +143,8 @@
         position_(0)
       {
         job_->SetDescription("C-MOVE");
-        job_->SetPermissive(true);
+        //job_->SetPermissive(true);  // This was the behavior of Orthanc < 1.6.0
+        job_->SetPermissive(false);
         job_->SetLocalAet(context.GetDefaultLocalApplicationEntityTitle());
 
         {
@@ -241,7 +243,24 @@
     else
     {
       const std::string& content = value.GetContent();
-      context_.GetIndex().LookupIdentifierExact(publicIds, level, tag, content);
+
+      /**
+       * This tokenization fixes issue 154 ("Matching against list of
+       * UID-s by C-MOVE").
+       * https://bitbucket.org/sjodogne/orthanc/issues/154/
+       **/
+
+      std::vector<std::string> tokens;
+      Toolbox::TokenizeString(tokens, content, '\\');
+      for (size_t i = 0; i < tokens.size(); i++)
+      {
+        std::vector<std::string> matches;
+        context_.GetIndex().LookupIdentifierExact(matches, level, tag, tokens[i]);
+
+        // Concatenate "publicIds" with "matches"
+        publicIds.insert(publicIds.end(), matches.begin(), matches.end());
+      }
+
       return true;
     }
   }
--- a/OrthancServer/OrthancMoveRequestHandler.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/OrthancMoveRequestHandler.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -37,6 +37,7 @@
 #include "../../Core/DicomParsing/FromDcmtkBridge.h"
 #include "../../Core/Logging.h"
 #include "../../Core/SerializationToolbox.h"
+#include "../OrthancConfiguration.h"
 #include "../ServerContext.h"
 #include "../ServerJobs/MergeStudyJob.h"
 #include "../ServerJobs/ResourceModificationJob.h"
@@ -63,6 +64,11 @@
   {
     // curl http://localhost:8042/series/95a6e2bf-9296e2cc-bf614e2f-22b391ee-16e010e0/modify -X POST -d '{"Replace":{"InstitutionName":"My own clinic"},"Priority":9}'
 
+    {
+      OrthancConfiguration::ReaderLock lock;
+      target.SetPrivateCreator(lock.GetConfiguration().GetDefaultPrivateCreator());
+    }
+    
     if (call.ParseJsonRequest(request))
     {
       target.ParseModifyRequest(request);
@@ -80,6 +86,11 @@
   {
     // curl http://localhost:8042/instances/6e67da51-d119d6ae-c5667437-87b9a8a5-0f07c49f/anonymize -X POST -d '{"Replace":{"PatientName":"hello","0010-0020":"world"},"Keep":["StudyDescription", "SeriesDescription"],"KeepPrivateTags": true,"Remove":["Modality"]}' > Anonymized.dcm
 
+    {
+      OrthancConfiguration::ReaderLock lock;
+      target.SetPrivateCreator(lock.GetConfiguration().GetDefaultPrivateCreator());
+    }
+    
     if (call.ParseJsonRequest(request) &&
         request.isObject())
     {
@@ -105,7 +116,7 @@
   {
     std::string id = call.GetUriComponent("id", "");
 
-    std::auto_ptr<ParsedDicomFile> modified;
+    std::unique_ptr<ParsedDicomFile> modified;
 
     {
       ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), id);
@@ -158,7 +169,7 @@
   }
 
 
-  static void SubmitModificationJob(std::auto_ptr<DicomModification>& modification,
+  static void SubmitModificationJob(std::unique_ptr<DicomModification>& modification,
                                     bool isAnonymization,
                                     RestApiPostCall& call,
                                     const Json::Value& body,
@@ -166,7 +177,7 @@
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
 
-    std::auto_ptr<ResourceModificationJob> job(new ResourceModificationJob(context));
+    std::unique_ptr<ResourceModificationJob> job(new ResourceModificationJob(context));
     
     job->SetModification(modification.release(), level, isAnonymization);
     job->SetOrigin(call);
@@ -181,7 +192,7 @@
   template <enum ResourceType resourceType>
   static void ModifyResource(RestApiPostCall& call)
   {
-    std::auto_ptr<DicomModification> modification(new DicomModification);
+    std::unique_ptr<DicomModification> modification(new DicomModification);
 
     Json::Value body;
     ParseModifyRequest(body, *modification, call);
@@ -196,7 +207,7 @@
   template <enum ResourceType resourceType>
   static void AnonymizeResource(RestApiPostCall& call)
   {
-    std::auto_ptr<DicomModification> modification(new DicomModification);
+    std::unique_ptr<DicomModification> modification(new DicomModification);
 
     Json::Value body;
     ParseAnonymizationRequest(body, *modification, call);
@@ -267,7 +278,8 @@
 
   static void InjectTags(ParsedDicomFile& dicom,
                          const Json::Value& tags,
-                         bool decodeBinaryTags)
+                         bool decodeBinaryTags,
+                         const std::string& privateCreator)
   {
     if (tags.type() != Json::objectValue)
     {
@@ -305,7 +317,7 @@
         }
         else
         {
-          dicom.Replace(tag, tags[name], decodeBinaryTags, DicomReplaceMode_InsertIfAbsent);
+          dicom.Replace(tag, tags[name], decodeBinaryTags, DicomReplaceMode_InsertIfAbsent, privateCreator);
         }
       }
     }
@@ -315,7 +327,8 @@
   static void CreateSeries(RestApiPostCall& call,
                            ParsedDicomFile& base /* in */,
                            const Json::Value& content,
-                           bool decodeBinaryTags)
+                           bool decodeBinaryTags,
+                           const std::string& privateCreator)
   {
     assert(content.isArray());
     assert(content.size() > 0);
@@ -330,7 +343,7 @@
     {
       for (Json::ArrayIndex i = 0; i < content.size(); i++)
       {
-        std::auto_ptr<ParsedDicomFile> dicom(base.Clone(false));
+        std::unique_ptr<ParsedDicomFile> dicom(base.Clone(false));
         const Json::Value* payload = NULL;
 
         if (content[i].type() == Json::stringValue)
@@ -348,7 +361,7 @@
 
           if (content[i].isMember("Tags"))
           {
-            InjectTags(*dicom, content[i]["Tags"], decodeBinaryTags);
+            InjectTags(*dicom, content[i]["Tags"], decodeBinaryTags, privateCreator);
           }
         }
 
@@ -538,6 +551,25 @@
       decodeBinaryTags = v.asBool();
     }
 
+
+    // New argument in Orthanc 1.6.0
+    std::string privateCreator;
+    if (request.isMember("PrivateCreator"))
+    {
+      const Json::Value& v = request["PrivateCreator"];
+      if (v.type() != Json::stringValue)
+      {
+        throw OrthancException(ErrorCode_BadRequest);
+      }
+
+      privateCreator = v.asString();
+    }
+    else
+    {
+      OrthancConfiguration::ReaderLock lock;
+      privateCreator = lock.GetConfiguration().GetDefaultPrivateCreator();
+    }
+
     
     // Inject time-related information
     std::string date, time;
@@ -565,7 +597,7 @@
     }
 
 
-    InjectTags(dicom, request["Tags"], decodeBinaryTags);
+    InjectTags(dicom, request["Tags"], decodeBinaryTags, privateCreator);
 
 
     // Inject the content (either an image, or a PDF file)
@@ -583,7 +615,7 @@
         if (content.size() > 0)
         {
           // Let's create a series instead of a single instance
-          CreateSeries(call, dicom, content, decodeBinaryTags);
+          CreateSeries(call, dicom, content, decodeBinaryTags, privateCreator);
           return;
         }
       }
@@ -636,7 +668,7 @@
 
     const std::string study = call.GetUriComponent("id", "");
 
-    std::auto_ptr<SplitStudyJob> job(new SplitStudyJob(context, study));    
+    std::unique_ptr<SplitStudyJob> job(new SplitStudyJob(context, study));    
     job->SetOrigin(call);
 
     std::vector<std::string> series;
@@ -719,7 +751,7 @@
 
     const std::string study = call.GetUriComponent("id", "");
 
-    std::auto_ptr<MergeStudyJob> job(new MergeStudyJob(context, study));    
+    std::unique_ptr<MergeStudyJob> job(new MergeStudyJob(context, study));    
     job->SetOrigin(call);
 
     std::vector<std::string> resources;
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -34,11 +34,14 @@
 #include "../PrecompiledHeadersServer.h"
 #include "OrthancRestApi.h"
 
+#include "../../Core/Compression/GzipCompressor.h"
 #include "../../Core/Logging.h"
 #include "../../Core/MetricsRegistry.h"
 #include "../../Core/SerializationToolbox.h"
 #include "../ServerContext.h"
 
+#include <boost/algorithm/string/predicate.hpp>
+
 namespace Orthanc
 {
   static void SetupResourceAnswer(Json::Value& result,
@@ -118,13 +121,22 @@
                              "Received an empty DICOM file");
     }
 
-    // TODO Remove unneccessary memcpy
-    std::string postData;
-    call.BodyToString(postData);
+    std::string dicom;
+
+    if (boost::iequals(call.GetHttpHeader("content-encoding", ""), "gzip"))
+    {
+      GzipCompressor compressor;
+      compressor.Uncompress(dicom, call.GetBodyData(), call.GetBodySize());
+    }
+    else
+    {
+      // TODO Remove unneccessary memcpy
+      call.BodyToString(dicom);
+    }
 
     DicomInstanceToStore toStore;
     toStore.SetOrigin(DicomInstanceOrigin::FromRest(call));
-    toStore.SetBuffer(postData);
+    toStore.SetBuffer(dicom);
 
     std::string publicId;
     StoreStatus status = context.Store(publicId, toStore);
@@ -242,7 +254,7 @@
                                         bool synchronous,
                                         int priority)
   {
-    std::auto_ptr<IJob> raii(job);
+    std::unique_ptr<IJob> raii(job);
     
     if (job == NULL)
     {
@@ -278,7 +290,7 @@
                                         bool isDefaultSynchronous,
                                         const Json::Value& body) const
   {
-    std::auto_ptr<IJob> raii(job);
+    std::unique_ptr<IJob> raii(job);
 
     if (body.type() != Json::objectValue)
     {
@@ -297,7 +309,7 @@
                                          bool isDefaultSynchronous,
                                          const Json::Value& body) const
   {
-    std::auto_ptr<SetOfCommandsJob> raii(job);
+    std::unique_ptr<SetOfCommandsJob> raii(job);
     
     if (body.type() != Json::objectValue)
     {
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestApi.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -123,7 +123,7 @@
 
   static void SubmitJob(RestApiOutput& output,
                         ServerContext& context,
-                        std::auto_ptr<ArchiveJob>& job,
+                        std::unique_ptr<ArchiveJob>& job,
                         int priority,
                         bool synchronous,
                         const std::string& filename)
@@ -179,7 +179,7 @@
       int priority;
       GetJobParameters(synchronous, extended, priority, body, DEFAULT_IS_EXTENDED);
       
-      std::auto_ptr<ArchiveJob> job(new ArchiveJob(context, IS_MEDIA, extended));
+      std::unique_ptr<ArchiveJob> job(new ArchiveJob(context, IS_MEDIA, extended));
       AddResourcesOfInterest(*job, body);
       SubmitJob(call.GetOutput(), context, job, priority, synchronous, "Archive.zip");
     }
@@ -209,7 +209,7 @@
       extended = false;
     }
     
-    std::auto_ptr<ArchiveJob> job(new ArchiveJob(context, IS_MEDIA, extended));
+    std::unique_ptr<ArchiveJob> job(new ArchiveJob(context, IS_MEDIA, extended));
     job->AddResource(id);
 
     SubmitJob(call.GetOutput(), context, job, 0 /* priority */,
@@ -232,7 +232,7 @@
       int priority;
       GetJobParameters(synchronous, extended, priority, body, DEFAULT_IS_EXTENDED);
       
-      std::auto_ptr<ArchiveJob> job(new ArchiveJob(context, IS_MEDIA, extended));
+      std::unique_ptr<ArchiveJob> job(new ArchiveJob(context, IS_MEDIA, extended));
       job->AddResource(id);
       SubmitJob(call.GetOutput(), context, job, priority, synchronous, id + ".zip");
     }
--- a/OrthancServer/OrthancRestApi/OrthancRestChanges.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestChanges.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -45,7 +45,7 @@
                                bool& last,
                                const RestApiGetCall& call)
   {
-    static const unsigned int MAX_RESULTS = 100;
+    static const unsigned int DEFAULT_LIMIT = 100;
     
     if (call.HasArgument("last"))
     {
@@ -58,17 +58,14 @@
     try
     {
       since = boost::lexical_cast<int64_t>(call.GetArgument("since", "0"));
-      limit = boost::lexical_cast<unsigned int>(call.GetArgument("limit", "0"));
+      limit = boost::lexical_cast<unsigned int>(call.GetArgument("limit", boost::lexical_cast<std::string>(DEFAULT_LIMIT)));
     }
     catch (boost::bad_lexical_cast&)
     {
+      since = 0;
+      limit = DEFAULT_LIMIT;
       return;
     }
-
-    if (limit == 0 || limit > MAX_RESULTS)
-    {
-      limit = MAX_RESULTS;
-    }
   }
 
   static void GetChanges(RestApiGetCall& call)
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -46,6 +46,7 @@
 #include "../ServerJobs/DicomMoveScuJob.h"
 #include "../ServerJobs/OrthancPeerStoreJob.h"
 #include "../ServerToolbox.h"
+#include "../StorageCommitmentReports.h"
 
 
 namespace Orthanc
@@ -416,7 +417,7 @@
    ***************************************************************************/
 
   static void AnswerQueryHandler(RestApiPostCall& call,
-                                 std::auto_ptr<QueryRetrieveHandler>& handler)
+                                 std::unique_ptr<QueryRetrieveHandler>& handler)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
 
@@ -466,7 +467,7 @@
     }
     else
     {
-      std::auto_ptr<QueryRetrieveHandler>  handler(new QueryRetrieveHandler(context));
+      std::unique_ptr<QueryRetrieveHandler>  handler(new QueryRetrieveHandler(context));
       
       handler->SetModality(call.GetUriComponent("id", ""));
       handler->SetLevel(StringToResourceType(request[KEY_LEVEL].asCString()));
@@ -618,7 +619,7 @@
       call.BodyToString(targetAet);
     }
     
-    std::auto_ptr<DicomMoveScuJob> job(new DicomMoveScuJob(context));
+    std::unique_ptr<DicomMoveScuJob> job(new DicomMoveScuJob(context));
     
     {
       QueryAccessor query(call);
@@ -744,7 +745,7 @@
     
     ServerContext& context = OrthancRestApi::GetContext(call);
 
-    std::auto_ptr<QueryRetrieveHandler>  handler(new QueryRetrieveHandler(context));
+    std::unique_ptr<QueryRetrieveHandler>  handler(new QueryRetrieveHandler(context));
       
     {
       const QueryAccessor parent(call);
@@ -944,7 +945,7 @@
     std::string remote = call.GetUriComponent("id", "");
 
     Json::Value request;
-    std::auto_ptr<DicomModalityStoreJob> job(new DicomModalityStoreJob(context));
+    std::unique_ptr<DicomModalityStoreJob> job(new DicomModalityStoreJob(context));
 
     GetInstancesToExport(request, *job, remote, call);
 
@@ -963,6 +964,12 @@
       job->SetMoveOriginator(moveOriginatorAET, moveOriginatorID);
     }
 
+    // New in Orthanc 1.6.0
+    if (Toolbox::GetJsonBooleanField(request, "StorageCommitment", false))
+    {
+      job->EnableStorageCommitment(true);
+    }
+
     OrthancRestApi::GetApi(call).SubmitCommandsJob
       (call, job.release(), true /* synchronous by default */, request);
   }
@@ -1084,7 +1091,7 @@
     std::string remote = call.GetUriComponent("id", "");
 
     Json::Value request;
-    std::auto_ptr<OrthancPeerStoreJob> job(new OrthancPeerStoreJob(context));
+    std::unique_ptr<OrthancPeerStoreJob> job(new OrthancPeerStoreJob(context));
 
     GetInstancesToExport(request, *job, remote, call);
     
@@ -1104,6 +1111,35 @@
     }
   }
 
+  static void PeerSystem(RestApiGetCall& call)
+  {
+    std::string remote = call.GetUriComponent("id", "");
+
+    OrthancConfiguration::ReaderLock lock;
+
+    WebServiceParameters peer;
+    if (lock.GetConfiguration().LookupOrthancPeer(peer, remote))
+    {
+      HttpClient client(peer, "system");
+      std::string answer;
+
+      client.SetMethod(HttpMethod_Get);
+
+      if (!client.Apply(answer))
+      {
+        LOG(ERROR) << "Unable to get the system info from remote Orthanc peer: " << peer.GetUrl();
+        call.GetOutput().SignalError(client.GetLastStatus());
+        return;
+      }
+
+      call.GetOutput().AnswerBuffer(answer, MimeType_Json);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_UnknownResource,
+                             "No peer with symbolic name: " + remote);
+    }
+  }
 
   // DICOM bridge -------------------------------------------------------------
 
@@ -1244,11 +1280,12 @@
     if (call.ParseJsonRequest(json))
     {
       const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle();
-      RemoteModalityParameters remote =
+      const RemoteModalityParameters remote =
         MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
 
-      std::auto_ptr<ParsedDicomFile> query
-        (ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(0)));
+      std::unique_ptr<ParsedDicomFile> query
+        (ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(0),
+                                         "" /* no private creator */));
 
       DicomFindAnswers answers(true);
 
@@ -1269,6 +1306,251 @@
   }
 
 
+  // Storage commitment SCU ---------------------------------------------------
+
+  static void StorageCommitmentScu(RestApiPostCall& call)
+  {
+    static const char* const ORTHANC_RESOURCES = "Resources";
+    static const char* const DICOM_INSTANCES = "DicomInstances";
+    static const char* const SOP_CLASS_UID = "SOPClassUID";
+    static const char* const SOP_INSTANCE_UID = "SOPInstanceUID";
+
+    ServerContext& context = OrthancRestApi::GetContext(call);
+
+    Json::Value json;
+    if (!call.ParseJsonRequest(json) ||
+        json.type() != Json::objectValue)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat,
+                             "Must provide a JSON object with a list of resources");
+    }
+    else if (!json.isMember(ORTHANC_RESOURCES) &&
+             !json.isMember(DICOM_INSTANCES))
+    {
+      throw OrthancException(ErrorCode_BadFileFormat,
+                             "Empty storage commitment request, one of these fields is mandatory: \"" +
+                             std::string(ORTHANC_RESOURCES) + "\" or \"" + std::string(DICOM_INSTANCES) + "\"");
+    }
+    else
+    {
+      std::list<std::string> sopClassUids, sopInstanceUids;
+
+      if (json.isMember(ORTHANC_RESOURCES))
+      {
+        const Json::Value& resources = json[ORTHANC_RESOURCES];
+          
+        if (resources.type() != Json::arrayValue)
+        {
+          throw OrthancException(ErrorCode_BadFileFormat,
+                                 "The \"" + std::string(ORTHANC_RESOURCES) +
+                                 "\" field must provide an array of Orthanc resources");
+        }
+        else
+        {
+          for (Json::Value::ArrayIndex i = 0; i < resources.size(); i++)
+          {
+            if (resources[i].type() != Json::stringValue)
+            {
+              throw OrthancException(ErrorCode_BadFileFormat,
+                                     "The \"" + std::string(ORTHANC_RESOURCES) +
+                                     "\" field must provide an array of strings, found: " + resources[i].toStyledString());
+            }
+
+            std::list<std::string> instances;
+            context.GetIndex().GetChildInstances(instances, resources[i].asString());
+            
+            for (std::list<std::string>::const_iterator
+                   it = instances.begin(); it != instances.end(); ++it)
+            {
+              std::string sopClassUid, sopInstanceUid;
+              DicomMap tags;
+              if (context.LookupOrReconstructMetadata(sopClassUid, *it, MetadataType_Instance_SopClassUid) &&
+                  context.GetIndex().GetAllMainDicomTags(tags, *it) &&
+                  tags.LookupStringValue(sopInstanceUid, DICOM_TAG_SOP_INSTANCE_UID, false))
+              {
+                sopClassUids.push_back(sopClassUid);
+                sopInstanceUids.push_back(sopInstanceUid);
+              }
+              else
+              {
+                throw OrthancException(ErrorCode_InternalError,
+                                       "Cannot retrieve SOP Class/Instance UID of Orthanc instance: " + *it);
+              }
+            }
+          }
+        }
+      }
+
+      if (json.isMember(DICOM_INSTANCES))
+      {
+        const Json::Value& instances = json[DICOM_INSTANCES];
+          
+        if (instances.type() != Json::arrayValue)
+        {
+          throw OrthancException(ErrorCode_BadFileFormat,
+                                 "The \"" + std::string(DICOM_INSTANCES) +
+                                 "\" field must provide an array of DICOM instances");
+        }
+        else
+        {
+          for (Json::Value::ArrayIndex i = 0; i < instances.size(); i++)
+          {
+            if (instances[i].type() == Json::arrayValue)
+            {
+              if (instances[i].size() != 2 ||
+                  instances[i][0].type() != Json::stringValue ||
+                  instances[i][1].type() != Json::stringValue)
+              {
+                throw OrthancException(ErrorCode_BadFileFormat,
+                                       "An instance entry must provide an array with 2 strings: "
+                                       "SOP Class UID and SOP Instance UID");
+              }
+              else
+              {
+                sopClassUids.push_back(instances[i][0].asString());
+                sopInstanceUids.push_back(instances[i][1].asString());
+              }
+            }
+            else if (instances[i].type() == Json::objectValue)
+            {
+              if (!instances[i].isMember(SOP_CLASS_UID) ||
+                  !instances[i].isMember(SOP_INSTANCE_UID) ||
+                  instances[i][SOP_CLASS_UID].type() != Json::stringValue ||
+                  instances[i][SOP_INSTANCE_UID].type() != Json::stringValue)
+              {
+                throw OrthancException(ErrorCode_BadFileFormat,
+                                       "An instance entry must provide an object with 2 string fiels: "
+                                       "\"" + std::string(SOP_CLASS_UID) + "\" and \"" +
+                                       std::string(SOP_INSTANCE_UID));
+              }
+              else
+              {
+                sopClassUids.push_back(instances[i][SOP_CLASS_UID].asString());
+                sopInstanceUids.push_back(instances[i][SOP_INSTANCE_UID].asString());
+              }
+            }
+            else
+            {
+              throw OrthancException(ErrorCode_BadFileFormat,
+                                     "JSON array or object is expected to specify one "
+                                     "instance to be queried, found: " + instances[i].toStyledString());
+            }
+          }
+        }
+      }
+
+      if (sopClassUids.size() != sopInstanceUids.size())
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      const std::string transactionUid = Toolbox::GenerateDicomPrivateUniqueIdentifier();
+
+      if (sopClassUids.empty())
+      {
+        LOG(WARNING) << "Issuing an outgoing storage commitment request that is empty: " << transactionUid;
+      }
+
+      {
+        const RemoteModalityParameters remote =
+          MyGetModalityUsingSymbolicName(call.GetUriComponent("id", ""));
+
+        const std::string& remoteAet = remote.GetApplicationEntityTitle();
+        const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle();
+        
+        // Create a "pending" storage commitment report BEFORE the
+        // actual SCU call in order to avoid race conditions
+        context.GetStorageCommitmentReports().Store(
+          transactionUid, new StorageCommitmentReports::Report(remoteAet));
+
+        DicomUserConnection scu(localAet, remote);
+
+        std::vector<std::string> a(sopClassUids.begin(), sopClassUids.end());
+        std::vector<std::string> b(sopInstanceUids.begin(), sopInstanceUids.end());
+        scu.RequestStorageCommitment(transactionUid, a, b);
+      }
+
+      Json::Value result = Json::objectValue;
+      result["ID"] = transactionUid;
+      result["Path"] = "/storage-commitment/" + transactionUid;
+      call.GetOutput().AnswerJson(result);
+    }
+  }
+
+
+  static void GetStorageCommitmentReport(RestApiGetCall& call)
+  {
+    ServerContext& context = OrthancRestApi::GetContext(call);
+
+    const std::string& transactionUid = call.GetUriComponent("id", "");
+
+    {
+      StorageCommitmentReports::Accessor accessor(
+        context.GetStorageCommitmentReports(), transactionUid);
+
+      if (accessor.IsValid())
+      {
+        Json::Value json;
+        accessor.GetReport().Format(json);
+        call.GetOutput().AnswerJson(json);
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_InexistentItem,
+                               "No storage commitment transaction with UID: " + transactionUid);
+      }
+    }
+  }
+  
+
+  static void RemoveAfterStorageCommitment(RestApiPostCall& call)
+  {
+    ServerContext& context = OrthancRestApi::GetContext(call);
+
+    const std::string& transactionUid = call.GetUriComponent("id", "");
+
+    {
+      StorageCommitmentReports::Accessor accessor(
+        context.GetStorageCommitmentReports(), transactionUid);
+
+      if (!accessor.IsValid())
+      {
+        throw OrthancException(ErrorCode_InexistentItem,
+                               "No storage commitment transaction with UID: " + transactionUid);
+      }
+      else if (accessor.GetReport().GetStatus() != StorageCommitmentReports::Report::Status_Success)
+      {
+        throw OrthancException(ErrorCode_BadSequenceOfCalls,
+                               "Cannot remove DICOM instances after failure "
+                               "in storage commitment transaction: " + transactionUid);
+      }
+      else
+      {
+        std::vector<std::string> sopInstanceUids;
+        accessor.GetReport().GetSuccessSopInstanceUids(sopInstanceUids);
+
+        for (size_t i = 0; i < sopInstanceUids.size(); i++)
+        {
+          std::vector<std::string> orthancId;
+          context.GetIndex().LookupIdentifierExact(
+            orthancId, ResourceType_Instance, DICOM_TAG_SOP_INSTANCE_UID, sopInstanceUids[i]);
+
+          for (size_t j = 0; j < orthancId.size(); j++)
+          {
+            LOG(INFO) << "Storage commitment - Removing SOP instance UID / Orthanc ID: "
+                      << sopInstanceUids[i] << " / " << orthancId[j];
+
+            Json::Value tmp;
+            context.GetIndex().DeleteResource(tmp, orthancId[j], ResourceType_Instance);
+          }
+        }
+          
+        call.GetOutput().AnswerBuffer("{}", MimeType_Json);
+      }
+    }
+  }
+  
+
   void OrthancRestApi::RegisterModalities()
   {
     Register("/modalities", ListModalities);
@@ -1309,7 +1591,13 @@
     Register("/peers/{id}", UpdatePeer);
     Register("/peers/{id}", DeletePeer);
     Register("/peers/{id}/store", PeerStore);
+    Register("/peers/{id}/system", PeerSystem);
 
     Register("/modalities/{id}/find-worklist", DicomFindWorklist);
+
+    // Storage commitment
+    Register("/modalities/{id}/storage-commitment", StorageCommitmentScu);
+    Register("/storage-commitment/{id}", GetStorageCommitmentReport);
+    Register("/storage-commitment/{id}/remove", RemoveAfterStorageCommitment);
   }
 }
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -35,10 +35,13 @@
 #include "OrthancRestApi.h"
 
 #include "../../Core/Compression/GzipCompressor.h"
+#include "../../Core/DicomFormat/DicomImageInformation.h"
 #include "../../Core/DicomParsing/DicomWebJsonVisitor.h"
 #include "../../Core/DicomParsing/FromDcmtkBridge.h"
 #include "../../Core/DicomParsing/Internals/DicomImageDecoder.h"
 #include "../../Core/HttpServer/HttpContentNegociation.h"
+#include "../../Core/Images/Image.h"
+#include "../../Core/Images/ImageProcessing.h"
 #include "../../Core/Logging.h"
 #include "../DefaultDicomImageDecoder.h"
 #include "../OrthancConfiguration.h"
@@ -49,6 +52,9 @@
 
 #include "../../Plugins/Engine/OrthancPlugins.h"
 
+// This "include" is mandatory for Release builds using Linux Standard Base
+#include <boost/math/special_functions/round.hpp>
+
 
 namespace Orthanc
 {
@@ -382,14 +388,14 @@
     class ImageToEncode
     {
     private:
-      std::auto_ptr<ImageAccessor>&  image_;
+      std::unique_ptr<ImageAccessor>&  image_;
       ImageExtractionMode            mode_;
       bool                           invert_;
       MimeType                       format_;
       std::string                    answer_;
 
     public:
-      ImageToEncode(std::auto_ptr<ImageAccessor>& image,
+      ImageToEncode(std::unique_ptr<ImageAccessor>& image,
                     ImageExtractionMode mode,
                     bool invert) :
         image_(image),
@@ -502,113 +508,445 @@
   }
 
 
-  template <enum ImageExtractionMode mode>
-  static void GetImage(RestApiGetCall& call)
+  namespace
   {
-    ServerContext& context = OrthancRestApi::GetContext(call);
-
-    std::string frameId = call.GetUriComponent("frame", "0");
-
-    unsigned int frame;
-    try
+    class IDecodedFrameHandler : public boost::noncopyable
     {
-      frame = boost::lexical_cast<unsigned int>(frameId);
-    }
-    catch (boost::bad_lexical_cast&)
-    {
-      return;
-    }
+    public:
+      virtual ~IDecodedFrameHandler()
+      {
+      }
+
+      virtual void Handle(RestApiGetCall& call,
+                          std::unique_ptr<ImageAccessor>& decoded,
+                          const DicomMap& dicom) = 0;
+
+      virtual bool RequiresDicomTags() const = 0;
+
+      static void Apply(RestApiGetCall& call,
+                        IDecodedFrameHandler& handler)
+      {
+        ServerContext& context = OrthancRestApi::GetContext(call);
 
-    bool invert = false;
-    std::auto_ptr<ImageAccessor> decoded;
+        std::string frameId = call.GetUriComponent("frame", "0");
 
-    try
-    {
-      std::string publicId = call.GetUriComponent("id", "");
+        unsigned int frame;
+        try
+        {
+          frame = boost::lexical_cast<unsigned int>(frameId);
+        }
+        catch (boost::bad_lexical_cast&)
+        {
+          return;
+        }
+
+        DicomMap dicom;
+        std::unique_ptr<ImageAccessor> decoded;
+
+        try
+        {
+          std::string publicId = call.GetUriComponent("id", "");
 
 #if ORTHANC_ENABLE_PLUGINS == 1
-      if (context.GetPlugins().HasCustomImageDecoder())
+          if (context.GetPlugins().HasCustomImageDecoder())
+          {
+            // TODO create a cache of file
+            std::string dicomContent;
+            context.ReadDicom(dicomContent, publicId);
+            decoded.reset(context.GetPlugins().DecodeUnsafe(dicomContent.c_str(), dicomContent.size(), frame));
+
+            /**
+             * Note that we call "DecodeUnsafe()": We do not fallback to
+             * the builtin decoder if no installed decoder plugin is able
+             * to decode the image. This allows us to take advantage of
+             * the cache below.
+             **/
+
+            if (handler.RequiresDicomTags() &&
+                decoded.get() != NULL)
+            {
+              // TODO Optimize this lookup for photometric interpretation:
+              // It should be implemented by the plugin to avoid parsing
+              // twice the DICOM file
+              ParsedDicomFile parsed(dicomContent);
+              parsed.ExtractDicomSummary(dicom);
+            }
+          }
+#endif
+
+          if (decoded.get() == NULL)
+          {
+            // Use Orthanc's built-in decoder, using the cache to speed-up
+            // things on multi-frame images
+            ServerContext::DicomCacheLocker locker(context, publicId);        
+            decoded.reset(DicomImageDecoder::Decode(locker.GetDicom(), frame));
+
+            if (handler.RequiresDicomTags())
+            {
+              locker.GetDicom().ExtractDicomSummary(dicom);
+            }
+          }
+        }
+        catch (OrthancException& e)
+        {
+          if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange ||
+              e.GetErrorCode() == ErrorCode_UnknownResource)
+          {
+            // The frame number is out of the range for this DICOM
+            // instance, the resource is not existent
+          }
+          else
+          {
+            std::string root = "";
+            for (size_t i = 1; i < call.GetFullUri().size(); i++)
+            {
+              root += "../";
+            }
+
+            call.GetOutput().Redirect(root + "app/images/unsupported.png");
+          }
+          return;
+        }
+
+        handler.Handle(call, decoded, dicom);
+      }
+
+
+      static void DefaultHandler(RestApiGetCall& call,
+                                 std::unique_ptr<ImageAccessor>& decoded,
+                                 ImageExtractionMode mode,
+                                 bool invert)
       {
-        // TODO create a cache of file
-        std::string dicomContent;
-        context.ReadDicom(dicomContent, publicId);
-        decoded.reset(context.GetPlugins().DecodeUnsafe(dicomContent.c_str(), dicomContent.size(), frame));
+        ImageToEncode image(decoded, mode, invert);
+
+        HttpContentNegociation negociation;
+        EncodePng png(image);
+        negociation.Register(MIME_PNG, png);
+
+        EncodeJpeg jpeg(image, call);
+        negociation.Register(MIME_JPEG, jpeg);
+
+        EncodePam pam(image);
+        negociation.Register(MIME_PAM, pam);
+
+        if (negociation.Apply(call.GetHttpHeaders()))
+        {
+          image.Answer(call.GetOutput());
+        }
+      }
+    };
+
+
+    class GetImageHandler : public IDecodedFrameHandler
+    {
+    private:
+      ImageExtractionMode mode_;
+
+    public:
+      GetImageHandler(ImageExtractionMode mode) :
+        mode_(mode)
+      {
+      }
+
+      virtual void Handle(RestApiGetCall& call,
+                          std::unique_ptr<ImageAccessor>& decoded,
+                          const DicomMap& dicom) ORTHANC_OVERRIDE
+      {
+        bool invert = false;
+
+        if (mode_ == ImageExtractionMode_Preview)
+        {
+          DicomImageInformation info(dicom);
+          invert = (info.GetPhotometricInterpretation() == PhotometricInterpretation_Monochrome1);
+        }
+
+        DefaultHandler(call, decoded, mode_, invert);
+      }
+
+      virtual bool RequiresDicomTags() const ORTHANC_OVERRIDE
+      {
+        return mode_ == ImageExtractionMode_Preview;
+      }
+    };
+
+
+    class RenderedFrameHandler : public IDecodedFrameHandler
+    {
+    private:
+      static void GetDicomParameters(bool& invert,
+                                     float& rescaleSlope,
+                                     float& rescaleIntercept,
+                                     float& windowWidth,
+                                     float& windowCenter,
+                                     const DicomMap& dicom)
+      {
+        DicomImageInformation info(dicom);
+
+        invert = (info.GetPhotometricInterpretation() == PhotometricInterpretation_Monochrome1);
+
+        rescaleSlope = 1.0f;
+        rescaleIntercept = 0.0f;
+
+        if (dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_SLOPE) &&
+            dicom.HasTag(Orthanc::DICOM_TAG_RESCALE_INTERCEPT))
+        {
+          dicom.ParseFloat(rescaleSlope, Orthanc::DICOM_TAG_RESCALE_SLOPE);
+          dicom.ParseFloat(rescaleIntercept, Orthanc::DICOM_TAG_RESCALE_INTERCEPT);
+        }
+
+        windowWidth = static_cast<float>(1 << info.GetBitsStored());
+        windowCenter = windowWidth / 2.0f;
+
+        if (dicom.HasTag(Orthanc::DICOM_TAG_WINDOW_CENTER) &&
+            dicom.HasTag(Orthanc::DICOM_TAG_WINDOW_WIDTH))
+        {
+          dicom.ParseFirstFloat(windowCenter, Orthanc::DICOM_TAG_WINDOW_CENTER);
+          dicom.ParseFirstFloat(windowWidth, Orthanc::DICOM_TAG_WINDOW_WIDTH);
+        }
+      }
+
+      static void GetUserArguments(float& windowWidth /* inout */,
+                                   float& windowCenter /* inout */,
+                                   unsigned int& argWidth,
+                                   unsigned int& argHeight,
+                                   bool& smooth,
+                                   RestApiGetCall& call)
+      {
+        static const char* ARG_WINDOW_CENTER = "window-center";
+        static const char* ARG_WINDOW_WIDTH = "window-width";
+        static const char* ARG_WIDTH = "width";
+        static const char* ARG_HEIGHT = "height";
+        static const char* ARG_SMOOTH = "smooth";
+
+        if (call.HasArgument(ARG_WINDOW_WIDTH))
+        {
+          try
+          {
+            windowWidth = boost::lexical_cast<float>(call.GetArgument(ARG_WINDOW_WIDTH, ""));
+          }
+          catch (boost::bad_lexical_cast&)
+          {
+            throw OrthancException(ErrorCode_ParameterOutOfRange,
+                                   "Bad value for argument: " + std::string(ARG_WINDOW_WIDTH));
+          }
+        }
 
-        /**
-         * Note that we call "DecodeUnsafe()": We do not fallback to
-         * the builtin decoder if no installed decoder plugin is able
-         * to decode the image. This allows us to take advantage of
-         * the cache below.
-         **/
+        if (call.HasArgument(ARG_WINDOW_CENTER))
+        {
+          try
+          {
+            windowCenter = boost::lexical_cast<float>(call.GetArgument(ARG_WINDOW_CENTER, ""));
+          }
+          catch (boost::bad_lexical_cast&)
+          {
+            throw OrthancException(ErrorCode_ParameterOutOfRange,
+                                   "Bad value for argument: " + std::string(ARG_WINDOW_CENTER));
+          }
+        }
+
+        argWidth = 0;
+        argHeight = 0;
 
-        if (mode == ImageExtractionMode_Preview &&
-            decoded.get() != NULL)
+        if (call.HasArgument(ARG_WIDTH))
+        {
+          try
+          {
+            int tmp = boost::lexical_cast<int>(call.GetArgument(ARG_WIDTH, ""));
+            if (tmp < 0)
+            {
+              throw OrthancException(ErrorCode_ParameterOutOfRange,
+                                     "Argument cannot be negative: " + std::string(ARG_WIDTH));
+            }
+            else
+            {
+              argWidth = static_cast<unsigned int>(tmp);
+            }
+          }
+          catch (boost::bad_lexical_cast&)
+          {
+            throw OrthancException(ErrorCode_ParameterOutOfRange,
+                                   "Bad value for argument: " + std::string(ARG_WIDTH));
+          }
+        }
+
+        if (call.HasArgument(ARG_HEIGHT))
+        {
+          try
+          {
+            int tmp = boost::lexical_cast<int>(call.GetArgument(ARG_HEIGHT, ""));
+            if (tmp < 0)
+            {
+              throw OrthancException(ErrorCode_ParameterOutOfRange,
+                                     "Argument cannot be negative: " + std::string(ARG_HEIGHT));
+            }
+            else
+            {
+              argHeight = static_cast<unsigned int>(tmp);
+            }
+          }
+          catch (boost::bad_lexical_cast&)
+          {
+            throw OrthancException(ErrorCode_ParameterOutOfRange,
+                                   "Bad value for argument: " + std::string(ARG_HEIGHT));
+          }
+        }
+
+        smooth = false;
+
+        if (call.HasArgument(ARG_SMOOTH))
         {
-          // TODO Optimize this lookup for photometric interpretation:
-          // It should be implemented by the plugin to avoid parsing
-          // twice the DICOM file
-          ParsedDicomFile parsed(dicomContent);
+          std::string value = call.GetArgument(ARG_SMOOTH, "");
+          if (value == "0" ||
+              value == "false")
+          {
+            smooth = false;
+          }
+          else if (value == "1" ||
+                   value == "true")
+          {
+            smooth = true;
+          }
+          else
+          {
+            throw OrthancException(ErrorCode_ParameterOutOfRange,
+                                   "Argument must be Boolean: " + std::string(ARG_SMOOTH));
+          }
+        }        
+      }
+                                
+      
+    public:
+      virtual void Handle(RestApiGetCall& call,
+                          std::unique_ptr<ImageAccessor>& decoded,
+                          const DicomMap& dicom) ORTHANC_OVERRIDE
+      {
+        bool invert;
+        float rescaleSlope, rescaleIntercept, windowWidth, windowCenter;
+        GetDicomParameters(invert, rescaleSlope, rescaleIntercept, windowWidth, windowCenter, dicom);
+
+        unsigned int argWidth, argHeight;
+        bool smooth;
+        GetUserArguments(windowWidth, windowCenter, argWidth, argHeight, smooth, call);
+
+        unsigned int targetWidth = decoded->GetWidth();
+        unsigned int targetHeight = decoded->GetHeight();
+
+        if (decoded->GetWidth() != 0 &&
+            decoded->GetHeight() != 0)
+        {
+          float ratio = 1;
+
+          if (argWidth != 0 &&
+              argHeight != 0)
+          {
+            float ratioX = static_cast<float>(argWidth) / static_cast<float>(decoded->GetWidth());
+            float ratioY = static_cast<float>(argHeight) / static_cast<float>(decoded->GetHeight());
+            ratio = std::min(ratioX, ratioY);
+          }
+          else if (argWidth != 0)
+          {
+            ratio = static_cast<float>(argWidth) / static_cast<float>(decoded->GetWidth());
+          }
+          else if (argHeight != 0)
+          {
+            ratio = static_cast<float>(argHeight) / static_cast<float>(decoded->GetHeight());
+          }
           
-          PhotometricInterpretation photometric;
-          if (parsed.LookupPhotometricInterpretation(photometric))
+          targetWidth = boost::math::iround(ratio * static_cast<float>(decoded->GetWidth()));
+          targetHeight = boost::math::iround(ratio * static_cast<float>(decoded->GetHeight()));
+        }
+        
+        if (decoded->GetFormat() == PixelFormat_RGB24)
+        {
+          if (targetWidth == decoded->GetWidth() &&
+              targetHeight == decoded->GetHeight())
+          {
+            DefaultHandler(call, decoded, ImageExtractionMode_Preview, false);
+          }
+          else
           {
-            invert = (photometric == PhotometricInterpretation_Monochrome1);
+            std::unique_ptr<ImageAccessor> resized(
+              new Image(decoded->GetFormat(), targetWidth, targetHeight, false));
+            
+            if (smooth &&
+                (targetWidth < decoded->GetWidth() ||
+                 targetHeight < decoded->GetHeight()))
+            {
+              ImageProcessing::SmoothGaussian5x5(*decoded);
+            }
+            
+            ImageProcessing::Resize(*resized, *decoded);
+            DefaultHandler(call, resized, ImageExtractionMode_Preview, false);
+          }
+        }
+        else
+        {
+          // Grayscale image: (1) convert to Float32, (2) apply
+          // windowing to get a Grayscale8, (3) possibly resize
+
+          Image converted(PixelFormat_Float32, decoded->GetWidth(), decoded->GetHeight(), false);
+          ImageProcessing::Convert(converted, *decoded);
+
+          // Avoid divisions by zero
+          if (windowWidth <= 1.0f)
+          {
+            windowWidth = 1;
+          }
+
+          if (std::abs(rescaleSlope) <= 0.1f)
+          {
+            rescaleSlope = 0.1f;
+          }
+
+          const float scaling = 255.0f * rescaleSlope / windowWidth;
+          const float offset = (rescaleIntercept - windowCenter + windowWidth / 2.0f) / rescaleSlope;
+
+          std::unique_ptr<ImageAccessor> rescaled(new Image(PixelFormat_Grayscale8, decoded->GetWidth(), decoded->GetHeight(), false));
+          ImageProcessing::ShiftScale(*rescaled, converted, offset, scaling, false);
+
+          if (targetWidth == decoded->GetWidth() &&
+              targetHeight == decoded->GetHeight())
+          {
+            DefaultHandler(call, rescaled, ImageExtractionMode_UInt8, invert);
+          }
+          else
+          {
+            std::unique_ptr<ImageAccessor> resized(
+              new Image(PixelFormat_Grayscale8, targetWidth, targetHeight, false));
+            
+            if (smooth &&
+                (targetWidth < decoded->GetWidth() ||
+                 targetHeight < decoded->GetHeight()))
+            {
+              ImageProcessing::SmoothGaussian5x5(*rescaled);
+            }
+            
+            ImageProcessing::Resize(*resized, *rescaled);
+            DefaultHandler(call, resized, ImageExtractionMode_UInt8, invert);
           }
         }
       }
-#endif
 
-      if (decoded.get() == NULL)
-      {
-        // Use Orthanc's built-in decoder, using the cache to speed-up
-        // things on multi-frame images
-        ServerContext::DicomCacheLocker locker(context, publicId);        
-        decoded.reset(DicomImageDecoder::Decode(locker.GetDicom(), frame));
-
-        PhotometricInterpretation photometric;
-        if (mode == ImageExtractionMode_Preview &&
-            locker.GetDicom().LookupPhotometricInterpretation(photometric))
-        {
-          invert = (photometric == PhotometricInterpretation_Monochrome1);
-        }
-      }
-    }
-    catch (OrthancException& e)
-    {
-      if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange || e.GetErrorCode() == ErrorCode_UnknownResource)
-      {
-        // The frame number is out of the range for this DICOM
-        // instance, the resource is not existent
-      }
-      else
+      virtual bool RequiresDicomTags() const ORTHANC_OVERRIDE
       {
-        std::string root = "";
-        for (size_t i = 1; i < call.GetFullUri().size(); i++)
-        {
-          root += "../";
-        }
+        return true;
+      }
+    };
+  }
 
-        call.GetOutput().Redirect(root + "app/images/unsupported.png");
-      }
-      return;
-    }
-
-    ImageToEncode image(decoded, mode, invert);
 
-    HttpContentNegociation negociation;
-    EncodePng png(image);
-    negociation.Register(MIME_PNG, png);
-
-    EncodeJpeg jpeg(image, call);
-    negociation.Register(MIME_JPEG, jpeg);
+  template <enum ImageExtractionMode mode>
+  static void GetImage(RestApiGetCall& call)
+  {
+    GetImageHandler handler(mode);
+    IDecodedFrameHandler::Apply(call, handler);
+  }
 
-    EncodePam pam(image);
-    negociation.Register(MIME_PAM, pam);
 
-    if (negociation.Apply(call.GetHttpHeaders()))
-    {
-      image.Answer(call.GetOutput());
-    }
+  static void GetRenderedFrame(RestApiGetCall& call)
+  {
+    RenderedFrameHandler handler;
+    IDecodedFrameHandler::Apply(call, handler);
   }
 
 
@@ -638,7 +976,7 @@
     DefaultDicomImageDecoder decoder;  // This is Orthanc's built-in decoder
 #endif
 
-    std::auto_ptr<ImageAccessor> decoded(decoder.Decode(dicomContent.c_str(), dicomContent.size(), frame));
+    std::unique_ptr<ImageAccessor> decoded(decoder.Decode(dicomContent.c_str(), dicomContent.size(), frame));
 
     std::string result;
     decoded->ToMatlabString(result);
@@ -1762,6 +2100,7 @@
     Register("/instances/{id}/frames", ListFrames);
 
     Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>);
+    Register("/instances/{id}/frames/{frame}/rendered", GetRenderedFrame);
     Register("/instances/{id}/frames/{frame}/image-uint8", GetImage<ImageExtractionMode_UInt8>);
     Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>);
     Register("/instances/{id}/frames/{frame}/image-int16", GetImage<ImageExtractionMode_Int16>);
@@ -1770,6 +2109,7 @@
     Register("/instances/{id}/frames/{frame}/raw.gz", GetRawFrame<true>);
     Register("/instances/{id}/pdf", ExtractPdf);
     Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>);
+    Register("/instances/{id}/rendered", GetRenderedFrame);
     Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>);
     Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>);
     Register("/instances/{id}/image-int16", GetImage<ImageExtractionMode_Int16>);
--- a/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -42,6 +42,11 @@
 #include "../ServerContext.h"
 
 
+static const char* LOG_LEVEL_DEFAULT = "default";
+static const char* LOG_LEVEL_VERBOSE = "verbose";
+static const char* LOG_LEVEL_TRACE = "trace";
+
+
 namespace Orthanc
 {
   // System information -------------------------------------------------------
@@ -490,6 +495,62 @@
   }
 
 
+  static void GetLogLevel(RestApiGetCall& call)
+  {
+    std::string s;
+    
+    if (Logging::IsTraceLevelEnabled())
+    {
+      s = LOG_LEVEL_TRACE;
+    }
+    else if (Logging::IsInfoLevelEnabled())
+    {
+      s = LOG_LEVEL_VERBOSE;
+    }
+    else
+    {
+      s = LOG_LEVEL_DEFAULT;
+    }
+    
+    call.GetOutput().AnswerBuffer(s, MimeType_PlainText);
+  }
+
+
+  static void PutLogLevel(RestApiPutCall& call)
+  {
+    std::string body;
+    call.BodyToString(body);
+
+    if (body == LOG_LEVEL_DEFAULT)
+    {
+      Logging::EnableInfoLevel(false);
+      Logging::EnableTraceLevel(false);
+    }
+    else if (body == LOG_LEVEL_VERBOSE)
+    {
+      Logging::EnableInfoLevel(true);
+      Logging::EnableTraceLevel(false);
+    }
+    else if (body == LOG_LEVEL_TRACE)
+    {
+      Logging::EnableInfoLevel(true);
+      Logging::EnableTraceLevel(true);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange,
+                             "The log level must be one of the following values: \"" +
+                             std::string(LOG_LEVEL_DEFAULT) + "\", \"" +
+                             std::string(LOG_LEVEL_VERBOSE) + "\", of \"" +
+                             std::string(LOG_LEVEL_TRACE) + "\"");
+    }
+
+    // Success
+    LOG(WARNING) << "REST API call has switched the log level to: " << body;
+    call.GetOutput().AnswerBuffer("", MimeType_PlainText);
+  }
+
+
   void OrthancRestApi::RegisterSystem()
   {
     Register("/", ServeRoot);
@@ -505,6 +566,8 @@
     Register("/tools/metrics", GetMetricsEnabled);
     Register("/tools/metrics", PutMetricsEnabled);
     Register("/tools/metrics-prometheus", GetMetricsPrometheus);
+    Register("/tools/log-level", GetLogLevel);
+    Register("/tools/log-level", PutLogLevel);
 
     Register("/plugins", ListPlugins);
     Register("/plugins/{id}", GetPlugin);
--- a/OrthancServer/PrecompiledHeadersServer.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/PrecompiledHeadersServer.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/PrecompiledHeadersServer.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/PrecompiledHeadersServer.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/QueryRetrieveHandler.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/QueryRetrieveHandler.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/QueryRetrieveHandler.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/QueryRetrieveHandler.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Search/DatabaseConstraint.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Search/DatabaseConstraint.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Search/DatabaseConstraint.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Search/DatabaseConstraint.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Search/DatabaseLookup.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Search/DatabaseLookup.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -117,9 +117,9 @@
       }
 
       std::set<DicomTag> ignoreTagLength;
-      std::auto_ptr<DicomValue> value(FromDcmtkBridge::ConvertLeafElement
-                                      (*element, DicomToJsonFlags_None, 
-                                       0, encoding, hasCodeExtensions, ignoreTagLength));
+      std::unique_ptr<DicomValue> value(FromDcmtkBridge::ConvertLeafElement
+                                        (*element, DicomToJsonFlags_None, 
+                                         0, encoding, hasCodeExtensions, ignoreTagLength));
 
       // WARNING: Also modify "HierarchicalMatcher::Setup()" if modifying this code
       if (value.get() == NULL ||
@@ -185,7 +185,7 @@
         fixedTag = DICOM_TAG_MODALITY;
       }
 
-      std::auto_ptr<DicomTagConstraint> constraint
+      std::unique_ptr<DicomTagConstraint> constraint
         (new DicomTagConstraint(fixedTag, ConstraintType_List, caseSensitive, mandatoryTag));
 
       std::vector<std::string> items;
@@ -198,8 +198,26 @@
 
       AddConstraint(constraint.release());
     }
-    else if (dicomQuery.find('*') != std::string::npos ||
-             dicomQuery.find('?') != std::string::npos)
+    else if (
+      /**
+       * New test in Orthanc 1.6.0: Wild card matching is only allowed
+       * for a subset of value representations: AE, CS, LO, LT, PN,
+       * SH, ST, UC, UR, UT.
+       * http://dicom.nema.org/medical/dicom/2019e/output/chtml/part04/sect_C.2.2.2.4.html
+       **/
+      (vr == ValueRepresentation_ApplicationEntity ||    // AE
+       vr == ValueRepresentation_CodeString ||           // CS
+       vr == ValueRepresentation_LongString ||           // LO
+       vr == ValueRepresentation_LongText ||             // LT
+       vr == ValueRepresentation_PersonName ||           // PN
+       vr == ValueRepresentation_ShortString ||          // SH
+       vr == ValueRepresentation_ShortText ||            // ST
+       vr == ValueRepresentation_UnlimitedCharacters ||  // UC
+       vr == ValueRepresentation_UniversalResource ||    // UR
+       vr == ValueRepresentation_UnlimitedText           // UT
+        ) &&
+      (dicomQuery.find('*') != std::string::npos ||
+       dicomQuery.find('?') != std::string::npos))
     {
       AddConstraint(new DicomTagConstraint
                     (tag, ConstraintType_Wildcard, dicomQuery, caseSensitive, mandatoryTag));
--- a/OrthancServer/Search/DatabaseLookup.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Search/DatabaseLookup.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Search/DicomTagConstraint.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Search/DicomTagConstraint.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Search/DicomTagConstraint.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Search/DicomTagConstraint.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Search/HierarchicalMatcher.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Search/HierarchicalMatcher.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -123,9 +123,9 @@
         flatTags_.insert(tag);
 
         std::set<DicomTag> ignoreTagLength;
-        std::auto_ptr<DicomValue> value(FromDcmtkBridge::ConvertLeafElement
-                                        (*element, DicomToJsonFlags_None, 
-                                         0, encoding, hasCodeExtensions, ignoreTagLength));
+        std::unique_ptr<DicomValue> value(FromDcmtkBridge::ConvertLeafElement
+                                          (*element, DicomToJsonFlags_None, 
+                                           0, encoding, hasCodeExtensions, ignoreTagLength));
 
         // WARNING: Also modify "DatabaseLookup::IsMatch()" if modifying this code
         if (value.get() == NULL ||
@@ -257,7 +257,7 @@
                                                    Encoding encoding,
                                                    bool hasCodeExtensions) const
   {
-    std::auto_ptr<DcmDataset> target(new DcmDataset);
+    std::unique_ptr<DcmDataset> target(new DcmDataset);
 
     for (std::set<DicomTag>::const_iterator it = flatTags_.begin();
          it != flatTags_.end(); ++it)
@@ -268,7 +268,13 @@
       if (source.findAndGetElement(tag, element).good() &&
           element != NULL)
       {
-        std::auto_ptr<DcmElement> cloned(FromDcmtkBridge::CreateElementForTag(*it));
+        if (it->IsPrivate())
+        {
+          throw OrthancException(ErrorCode_NotImplemented,
+                                 "Not applicable to private tags: " + it->Format());
+        }
+        
+        std::unique_ptr<DcmElement> cloned(FromDcmtkBridge::CreateElementForTag(*it, "" /* no private creator */));
         cloned->copyFrom(*element);
         target->insert(cloned.release());
       }
@@ -283,7 +289,7 @@
       if (source.findAndGetSequence(tag, sequence).good() &&
           sequence != NULL)
       {
-        std::auto_ptr<DcmSequenceOfItems> cloned(new DcmSequenceOfItems(tag));
+        std::unique_ptr<DcmSequenceOfItems> cloned(new DcmSequenceOfItems(tag));
 
         for (unsigned long i = 0; i < sequence->card(); i++)
         {
@@ -297,7 +303,7 @@
             // "DcmItem" object before it can be included in a
             // sequence. Otherwise, "dciodvfy" reports an error "Bad
             // tag in sequence - Expecting Item or Sequence Delimiter."
-            std::auto_ptr<DcmDataset> child(it->second->ExtractInternal(*sequence->getItem(i), encoding, hasCodeExtensions));
+            std::unique_ptr<DcmDataset> child(it->second->ExtractInternal(*sequence->getItem(i), encoding, hasCodeExtensions));
             cloned->append(new DcmItem(*child));
           }
         }
@@ -315,10 +321,10 @@
     bool hasCodeExtensions;
     Encoding encoding = dicom.DetectEncoding(hasCodeExtensions);
     
-    std::auto_ptr<DcmDataset> dataset(ExtractInternal(*dicom.GetDcmtkObject().getDataset(),
-                                                      encoding, hasCodeExtensions));
+    std::unique_ptr<DcmDataset> dataset(ExtractInternal(*dicom.GetDcmtkObject().getDataset(),
+                                                        encoding, hasCodeExtensions));
 
-    std::auto_ptr<ParsedDicomFile> result(new ParsedDicomFile(*dataset));
+    std::unique_ptr<ParsedDicomFile> result(new ParsedDicomFile(*dataset));
     result->SetEncoding(encoding);
 
     return result.release();
--- a/OrthancServer/Search/HierarchicalMatcher.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Search/HierarchicalMatcher.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Search/ISqlLookupFormatter.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Search/ISqlLookupFormatter.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/Search/ISqlLookupFormatter.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/Search/ISqlLookupFormatter.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerContext.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerContext.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -49,6 +49,7 @@
 #include "Search/DatabaseLookup.h"
 #include "ServerJobs/OrthancJobUnserializer.h"
 #include "ServerToolbox.h"
+#include "StorageCommitmentReports.h"
 
 #include <EmbeddedResources.h>
 #include <dcmtk/dcmdata/dcfilefo.h>
@@ -75,7 +76,7 @@
   {
     while (!that->done_)
     {
-      std::auto_ptr<IDynamicObject> obj(that->pendingChanges_.Dequeue(sleepDelay));
+      std::unique_ptr<IDynamicObject> obj(that->pendingChanges_.Dequeue(sleepDelay));
         
       if (obj.get() != NULL)
       {
@@ -254,6 +255,14 @@
       jobsEngine_.SetWorkersCount(lock.GetConfiguration().GetUnsignedIntegerParameter("ConcurrentJobs", 2));
       saveJobs_ = lock.GetConfiguration().GetBooleanParameter("SaveJobs", true);
       metricsRegistry_->SetEnabled(lock.GetConfiguration().GetBooleanParameter("MetricsEnabled", true));
+
+      // New configuration options in Orthanc 1.5.1
+      findStorageAccessMode_ = StringToFindStorageAccessMode(lock.GetConfiguration().GetStringParameter("StorageAccessOnFind", "Always"));
+      limitFindInstances_ = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindInstances", 0);
+      limitFindResults_ = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindResults", 0);
+
+      // New configuration option in Orthanc 1.6.0
+      storageCommitmentReports_.reset(new StorageCommitmentReports(lock.GetConfiguration().GetUnsignedIntegerParameter("StorageCommitmentReportsSize", 100)));
     }
 
     jobsEngine_.SetThreadSleep(unitTesting ? 20 : 200);
@@ -659,7 +668,7 @@
     lock_(that_.dicomCacheMutex_)
   {
 #if ENABLE_DICOM_CACHE == 0
-    static std::auto_ptr<IDynamicObject> p;
+    static std::unique_ptr<IDynamicObject> p;
     p.reset(provider_.Provide(instancePublicId));
     dicom_ = dynamic_cast<ParsedDicomFile*>(p.get());
 #else
@@ -796,44 +805,9 @@
                             size_t since,
                             size_t limit)
   {
-    LookupMode mode;
-    unsigned int databaseLimit;
+    unsigned int databaseLimit = (queryLevel == ResourceType_Instance ?
+                                  limitFindInstances_ : limitFindResults_);
       
-    {
-      // New configuration option in 1.5.1
-      OrthancConfiguration::ReaderLock lock;
-
-      std::string value = lock.GetConfiguration().GetStringParameter("StorageAccessOnFind", "Always");
-
-      if (value == "Always")
-      {
-        mode = LookupMode_DiskOnLookupAndAnswer;
-      }
-      else if (value == "Never")
-      {
-        mode = LookupMode_DatabaseOnly;
-      }
-      else if (value == "Answers")
-      {
-        mode = LookupMode_DiskOnAnswer;
-      }
-      else
-      {
-        throw OrthancException(ErrorCode_ParameterOutOfRange,
-                               "Configuration option \"StorageAccessOnFind\" "
-                               "should be \"Always\", \"Never\" or \"Answers\": " + value);
-      }
-
-      if (queryLevel == ResourceType_Instance)
-      {
-        databaseLimit = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindInstances", 0);
-      }
-      else
-      {
-        databaseLimit = lock.GetConfiguration().GetUnsignedIntegerParameter("LimitFindResults", 0);
-      }
-    }      
-
     std::vector<std::string> resources, instances;
 
     {
@@ -846,6 +820,11 @@
 
     LOG(INFO) << "Number of candidate resources after fast DB filtering on main DICOM tags: " << resources.size();
 
+    /**
+     * "resources" contains the Orthanc ID of the resource at level
+     * "queryLevel", "instances" contains one the Orthanc ID of one
+     * sample instance from this resource.
+     **/
     assert(resources.size() == instances.size());
 
     size_t countResults = 0;
@@ -858,24 +837,59 @@
       // Optimization in Orthanc 1.5.1 - Don't read the full JSON from
       // the disk if only "main DICOM tags" are to be returned
 
-      std::auto_ptr<Json::Value> dicomAsJson;
+      std::unique_ptr<Json::Value> dicomAsJson;
 
       bool hasOnlyMainDicomTags;
       DicomMap dicom;
       
-      if (mode == LookupMode_DatabaseOnly ||
-          mode == LookupMode_DiskOnAnswer ||
+      if (findStorageAccessMode_ == FindStorageAccessMode_DatabaseOnly ||
+          findStorageAccessMode_ == FindStorageAccessMode_DiskOnAnswer ||
           lookup.HasOnlyMainDicomTags())
       {
         // Case (1): The main DICOM tags, as stored in the database,
         // are sufficient to look for match
 
-        if (!GetIndex().GetAllMainDicomTags(dicom, instances[i]))
+        DicomMap tmp;
+        if (!GetIndex().GetAllMainDicomTags(tmp, instances[i]))
         {
           // The instance has been removed during the execution of the
           // lookup, ignore it
           continue;
         }
+
+#if 1
+        // New in Orthanc 1.6.0: Only keep the main DICOM tags at the
+        // level of interest for the query
+        switch (queryLevel)
+        {
+          // WARNING: Don't reorder cases below, and don't add "break"
+          case ResourceType_Instance:
+            dicom.MergeMainDicomTags(tmp, ResourceType_Instance);
+
+          case ResourceType_Series:
+            dicom.MergeMainDicomTags(tmp, ResourceType_Series);
+
+          case ResourceType_Study:
+            dicom.MergeMainDicomTags(tmp, ResourceType_Study);
+            
+          case ResourceType_Patient:
+            dicom.MergeMainDicomTags(tmp, ResourceType_Patient);
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_InternalError);
+        }
+
+        // Special case of the "Modality" at the study level, in order
+        // to deal with C-FIND on "ModalitiesInStudy" (0008,0061).
+        // Check out integration test "test_rest_modalities_in_study".
+        if (queryLevel == ResourceType_Study)
+        {
+          dicom.CopyTagIfExists(tmp, DICOM_TAG_MODALITY);
+        }
+#else
+        dicom.Assign(tmp);  // This emulates Orthanc <= 1.5.8
+#endif
         
         hasOnlyMainDicomTags = true;
       }
@@ -907,8 +921,8 @@
         }
         else
         {
-          if ((mode == LookupMode_DiskOnLookupAndAnswer ||
-               mode == LookupMode_DiskOnAnswer) &&
+          if ((findStorageAccessMode_ == FindStorageAccessMode_DiskOnLookupAndAnswer ||
+               findStorageAccessMode_ == FindStorageAccessMode_DiskOnAnswer) &&
               dicomAsJson.get() == NULL &&
               isDicomAsJsonNeeded)
           {
@@ -1052,4 +1066,24 @@
     }
 #endif
   }
+
+
+  IStorageCommitmentFactory::ILookupHandler*
+  ServerContext::CreateStorageCommitment(const std::string& jobId,
+                                         const std::string& transactionUid,
+                                         const std::vector<std::string>& sopClassUids,
+                                         const std::vector<std::string>& sopInstanceUids,
+                                         const std::string& remoteAet,
+                                         const std::string& calledAet)
+  {
+#if ORTHANC_ENABLE_PLUGINS == 1
+    if (HasPlugins())
+    {
+      return GetPlugins().CreateStorageCommitment(
+        jobId, transactionUid, sopClassUids, sopInstanceUids, remoteAet, calledAet);
+    }
+#endif
+
+    return NULL;
+  }
 }
--- a/OrthancServer/ServerContext.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerContext.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -37,6 +37,7 @@
 #include "LuaScripting.h"
 #include "OrthancHttpHandler.h"
 #include "ServerIndex.h"
+#include "ServerJobs/IStorageCommitmentFactory.h"
 
 #include "../Core/Cache/MemoryCache.h"
 
@@ -53,6 +54,7 @@
   class SetOfInstancesJob;
   class SharedArchive;
   class SharedMessageQueue;
+  class StorageCommitmentReports;
   
   
   /**
@@ -60,7 +62,9 @@
    * filesystem (including compression), as well as the index of the
    * DICOM store. It implements the required locking mechanisms.
    **/
-  class ServerContext : private JobsRegistry::IObserver
+  class ServerContext :
+    public IStorageCommitmentFactory,
+    private JobsRegistry::IObserver
   {
   public:
     class ILookupVisitor : public boost::noncopyable
@@ -82,14 +86,6 @@
     
     
   private:
-    enum LookupMode
-    {
-      LookupMode_DatabaseOnly,
-      LookupMode_DiskOnAnswer,
-      LookupMode_DiskOnLookupAndAnswer
-    };
-
-    
     class LuaServerListener : public IServerListener
     {
     private:
@@ -120,7 +116,7 @@
       }
     };
     
-    class DicomCacheProvider : public ICachePageProvider
+    class DicomCacheProvider : public Deprecated::ICachePageProvider  // TODO
     {
     private:
       ServerContext& context_;
@@ -172,11 +168,11 @@
 
     void SaveJobsEngine();
 
-    virtual void SignalJobSubmitted(const std::string& jobId);
+    virtual void SignalJobSubmitted(const std::string& jobId) ORTHANC_OVERRIDE;
 
-    virtual void SignalJobSuccess(const std::string& jobId);
+    virtual void SignalJobSuccess(const std::string& jobId) ORTHANC_OVERRIDE;
 
-    virtual void SignalJobFailure(const std::string& jobId);
+    virtual void SignalJobFailure(const std::string& jobId) ORTHANC_OVERRIDE;
 
     ServerIndex index_;
     IStorageArea& area_;
@@ -186,12 +182,12 @@
     
     DicomCacheProvider provider_;
     boost::mutex dicomCacheMutex_;
-    MemoryCache dicomCache_;
+    Deprecated::MemoryCache dicomCache_;  // TODO
 
     LuaScripting mainLua_;
     LuaScripting filterLua_;
     LuaServerListener  luaListener_;
-    std::auto_ptr<SharedArchive>  mediaArchive_;
+    std::unique_ptr<SharedArchive>  mediaArchive_;
     
     // The "JobsEngine" must be *after* "LuaScripting", as
     // "LuaScripting" embeds "LuaJobManager" that registers as an
@@ -214,15 +210,20 @@
     boost::thread  changeThread_;
     boost::thread  saveJobsThread_;
         
-    std::auto_ptr<SharedArchive>  queryRetrieveArchive_;
+    std::unique_ptr<SharedArchive>  queryRetrieveArchive_;
     std::string defaultLocalAet_;
     OrthancHttpHandler  httpHandler_;
     bool saveJobs_;
+    FindStorageAccessMode findStorageAccessMode_;
+    unsigned int limitFindInstances_;
+    unsigned int limitFindResults_;
 
-    std::auto_ptr<MetricsRegistry>  metricsRegistry_;
+    std::unique_ptr<MetricsRegistry>  metricsRegistry_;
     bool isHttpServerSecure_;
     bool isExecuteLuaEnabled_;
 
+    std::unique_ptr<StorageCommitmentReports>  storageCommitmentReports_;
+
   public:
     class DicomCacheLocker : public boost::noncopyable
     {
@@ -424,5 +425,18 @@
     {
       return isExecuteLuaEnabled_;
     }
+
+    virtual IStorageCommitmentFactory::ILookupHandler*
+    CreateStorageCommitment(const std::string& jobId,
+                            const std::string& transactionUid,
+                            const std::vector<std::string>& sopClassUids,
+                            const std::vector<std::string>& sopInstanceUids,
+                            const std::string& remoteAet,
+                            const std::string& calledAet) ORTHANC_OVERRIDE;
+
+    StorageCommitmentReports& GetStorageCommitmentReports()
+    {
+      return *storageCommitmentReports_;
+    }
   };
 }
--- a/OrthancServer/ServerEnumerations.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerEnumerations.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -192,6 +192,30 @@
     return dictContentType_.Translate(str);
   }
 
+
+  FindStorageAccessMode StringToFindStorageAccessMode(const std::string& value)
+  {
+    if (value == "Always")
+    {
+      return FindStorageAccessMode_DiskOnLookupAndAnswer;
+    }
+    else if (value == "Never")
+    {
+      return FindStorageAccessMode_DatabaseOnly;
+    }
+    else if (value == "Answers")
+    {
+      return FindStorageAccessMode_DiskOnAnswer;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange,
+                             "Configuration option \"StorageAccessOnFind\" "
+                             "should be \"Always\", \"Never\" or \"Answers\": " + value);
+    }    
+  }
+  
+
   std::string GetBasePath(ResourceType type,
                           const std::string& publicId)
   {
--- a/OrthancServer/ServerEnumerations.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerEnumerations.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -83,6 +83,13 @@
     };
   }
 
+  enum FindStorageAccessMode
+  {
+    FindStorageAccessMode_DatabaseOnly,
+    FindStorageAccessMode_DiskOnAnswer,
+    FindStorageAccessMode_DiskOnLookupAndAnswer
+  };
+
 
   /**
    * WARNING: Do not change the explicit values in the enumerations
@@ -178,6 +185,8 @@
 
   FileContentType StringToContentType(const std::string& str);
 
+  FindStorageAccessMode StringToFindStorageAccessMode(const std::string& str);
+
   std::string EnumerationToString(FileContentType type);
 
   std::string GetFileContentMime(FileContentType type);
--- a/OrthancServer/ServerIndex.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerIndex.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -152,7 +152,15 @@
              it = pendingFilesToRemove_.begin();
            it != pendingFilesToRemove_.end(); ++it)
       {
-        context_.RemoveFile(it->GetUuid(), it->GetContentType());
+        try
+        {
+          context_.RemoveFile(it->GetUuid(), it->GetContentType());
+        }
+        catch (OrthancException& e)
+        {
+          LOG(ERROR) << "Unable to remove an attachment from the storage area: "
+                     << it->GetUuid() << " (type: " << EnumerationToString(it->GetContentType()) << ")";
+        }
       }
     }
 
@@ -233,7 +241,7 @@
   {
   private:
     ServerIndex& index_;
-    std::auto_ptr<IDatabaseWrapper::ITransaction> transaction_;
+    std::unique_ptr<IDatabaseWrapper::ITransaction> transaction_;
     bool isCommitted_;
 
   public:
@@ -356,34 +364,40 @@
       
     void LoadTags(ResourceType level)
     {
-      const DicomTag* tags = NULL;
-      size_t size;
+      {
+        const DicomTag* tags = NULL;
+        size_t size;
   
-      ServerToolbox::LoadIdentifiers(tags, size, level);
+        ServerToolbox::LoadIdentifiers(tags, size, level);
   
-      for (size_t i = 0; i < size; i++)
-      {
-        if (registry_.find(tags[i]) == registry_.end())
+        for (size_t i = 0; i < size; i++)
         {
-          registry_[tags[i]] = TagInfo(level, DicomTagType_Identifier);
-        }
-        else
-        {
-          // These patient-level tags are copied in the study level
-          assert(level == ResourceType_Study &&
-                 (tags[i] == DICOM_TAG_PATIENT_ID ||
-                  tags[i] == DICOM_TAG_PATIENT_NAME ||
-                  tags[i] == DICOM_TAG_PATIENT_BIRTH_DATE));
+          if (registry_.find(tags[i]) == registry_.end())
+          {
+            registry_[tags[i]] = TagInfo(level, DicomTagType_Identifier);
+          }
+          else
+          {
+            // These patient-level tags are copied in the study level
+            assert(level == ResourceType_Study &&
+                   (tags[i] == DICOM_TAG_PATIENT_ID ||
+                    tags[i] == DICOM_TAG_PATIENT_NAME ||
+                    tags[i] == DICOM_TAG_PATIENT_BIRTH_DATE));
+          }
         }
       }
-  
-      DicomMap::LoadMainDicomTags(tags, size, level);
-  
-      for (size_t i = 0; i < size; i++)
+
       {
-        if (registry_.find(tags[i]) == registry_.end())
+        std::set<DicomTag> tags;
+        DicomMap::GetMainDicomTags(tags, level);
+
+        for (std::set<DicomTag>::const_iterator
+               tag = tags.begin(); tag != tags.end(); ++tag)
         {
-          registry_[tags[i]] = TagInfo(level, DicomTagType_Main);
+          if (registry_.find(*tag) == registry_.end())
+          {
+            registry_[*tag] = TagInfo(level, DicomTagType_Main);
+          }
         }
       }
     }
@@ -494,7 +508,16 @@
       Logging::Flush();
 
       boost::mutex::scoped_lock lock(that->mutex_);
-      that->db_.FlushToDisk();
+
+      try
+      {
+        that->db_.FlushToDisk();
+      }
+      catch (OrthancException&)
+      {
+        LOG(ERROR) << "Cannot flush the SQLite database to the disk (is your filesystem full?)";
+      }
+          
       count = 0;
     }
 
--- a/OrthancServer/ServerIndex.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerIndex.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -65,14 +65,14 @@
     boost::thread flushThread_;
     boost::thread unstableResourcesMonitorThread_;
 
-    std::auto_ptr<Listener> listener_;
+    std::unique_ptr<Listener> listener_;
     IDatabaseWrapper& db_;
     LeastRecentlyUsedIndex<int64_t, UnstableResourcePayload>  unstableResources_;
 
     uint64_t     maximumStorageSize_;
     unsigned int maximumPatients_;
     bool         overwrite_;
-    std::auto_ptr<MainDicomTagsRegistry>  mainDicomTagsRegistry_;
+    std::unique_ptr<MainDicomTagsRegistry>  mainDicomTagsRegistry_;
 
     static void FlushThread(ServerIndex* that,
                             unsigned int threadSleep);
--- a/OrthancServer/ServerIndexChange.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerIndexChange.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/ArchiveJob.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/ArchiveJob.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -276,7 +276,7 @@
       else if (previous == resources_.end())
       {
         // This is the first time we meet this resource
-        std::auto_ptr<ArchiveIndex> child(new ArchiveIndex(GetChildResourceType(level_)));
+        std::unique_ptr<ArchiveIndex> child(new ArchiveIndex(GetChildResourceType(level_)));
         child->Add(index, resource);
         resources_[id] = child.release();
       }
@@ -308,7 +308,7 @@
           std::list<std::string> children;
           index.GetChildren(children, it->first);
 
-          std::auto_ptr<ArchiveIndex> child(new ArchiveIndex(GetChildResourceType(level_)));
+          std::unique_ptr<ArchiveIndex> child(new ArchiveIndex(GetChildResourceType(level_)));
 
           for (std::list<std::string>::const_iterator 
                  it2 = children.begin(); it2 != children.end(); ++it2)
@@ -695,12 +695,12 @@
   class ArchiveJob::ZipWriterIterator : public boost::noncopyable
   {
   private:
-    TemporaryFile&                        target_;
-    ServerContext&                        context_;
-    ZipCommands                           commands_;
-    std::auto_ptr<HierarchicalZipWriter>  zip_;
-    std::auto_ptr<DicomDirWriter>         dicomDir_;
-    bool                                  isMedia_;
+    TemporaryFile&                          target_;
+    ServerContext&                          context_;
+    ZipCommands                             commands_;
+    std::unique_ptr<HierarchicalZipWriter>  zip_;
+    std::unique_ptr<DicomDirWriter>         dicomDir_;
+    bool                                    isMedia_;
 
   public:
     ZipWriterIterator(TemporaryFile& target,
@@ -902,7 +902,7 @@
     class DynamicTemporaryFile : public IDynamicObject
     {
     private:
-      std::auto_ptr<TemporaryFile>   file_;
+      std::unique_ptr<TemporaryFile>   file_;
 
     public:
       DynamicTemporaryFile(TemporaryFile* f) : file_(f)
@@ -935,7 +935,7 @@
   }
     
 
-  JobStepResult ArchiveJob::Step()
+  JobStepResult ArchiveJob::Step(const std::string& jobId)
   {
     assert(writer_.get() != NULL);
 
--- a/OrthancServer/ServerJobs/ArchiveJob.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/ArchiveJob.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -33,6 +33,7 @@
 
 #pragma once
 
+#include "../../Core/Compatibility.h"
 #include "../../Core/JobsEngine/IJob.h"
 #include "../../Core/TemporaryFile.h"
 
@@ -55,7 +56,7 @@
     class ZipWriterIterator;
     
     boost::shared_ptr<TemporaryFile>      synchronousTarget_;
-    std::auto_ptr<TemporaryFile>          asynchronousTarget_;
+    std::unique_ptr<TemporaryFile>        asynchronousTarget_;
     ServerContext&                        context_;
     boost::shared_ptr<ArchiveIndex>       archive_;
     bool                                  isMedia_;
@@ -88,29 +89,29 @@
 
     void AddResource(const std::string& publicId);
 
-    virtual void Reset();
+    virtual void Reset() ORTHANC_OVERRIDE;
 
-    virtual void Start();
+    virtual void Start() ORTHANC_OVERRIDE;
 
-    virtual JobStepResult Step();
+    virtual JobStepResult Step(const std::string& jobId) ORTHANC_OVERRIDE;
 
-    virtual void Stop(JobStopReason reason)
+    virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE
     {
     }
 
-    virtual float GetProgress();
+    virtual float GetProgress() ORTHANC_OVERRIDE;
 
-    virtual void GetJobType(std::string& target);
+    virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE;
     
-    virtual void GetPublicContent(Json::Value& value);
+    virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE;
 
-    virtual bool Serialize(Json::Value& value)
+    virtual bool Serialize(Json::Value& value) ORTHANC_OVERRIDE
     {
       return false;  // Cannot serialize this kind of job
     }
 
     virtual bool GetOutput(std::string& output,
                            MimeType& mime,
-                           const std::string& key);
+                           const std::string& key) ORTHANC_OVERRIDE;
   };
 }
--- a/OrthancServer/ServerJobs/DicomModalityStoreJob.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/DicomModalityStoreJob.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -34,9 +34,11 @@
 #include "../PrecompiledHeadersServer.h"
 #include "DicomModalityStoreJob.h"
 
+#include "../../Core/Compatibility.h"
 #include "../../Core/Logging.h"
 #include "../../Core/SerializationToolbox.h"
 #include "../ServerContext.h"
+#include "../StorageCommitmentReports.h"
 
 
 namespace Orthanc
@@ -71,14 +73,47 @@
       LOG(WARNING) << "An instance was removed after the job was issued: " << instance;
       return false;
     }
+    
+    std::string sopClassUid, sopInstanceUid;
 
     if (HasMoveOriginator())
     {
-      connection_->Store(dicom, moveOriginatorAet_, moveOriginatorId_);
+      connection_->Store(sopClassUid, sopInstanceUid, dicom, moveOriginatorAet_, moveOriginatorId_);
     }
     else
     {
-      connection_->Store(dicom);
+      connection_->Store(sopClassUid, sopInstanceUid, dicom);
+    }
+
+    if (storageCommitment_)
+    {
+      sopClassUids_.push_back(sopClassUid);
+      sopInstanceUids_.push_back(sopInstanceUid);
+
+      if (sopClassUids_.size() != sopInstanceUids_.size() ||
+          sopClassUids_.size() > GetInstancesCount())
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+      
+      if (sopClassUids_.size() == GetInstancesCount())
+      {
+        const std::string& remoteAet = remote_.GetApplicationEntityTitle();
+        
+        LOG(INFO) << "Sending storage commitment request to modality: " << remoteAet;
+
+        // Create a "pending" storage commitment report BEFORE the
+        // actual SCU call in order to avoid race conditions
+        context_.GetStorageCommitmentReports().Store(
+          transactionUid_, new StorageCommitmentReports::Report(remoteAet));
+        
+        assert(IsStarted());
+        OpenConnection();
+
+        std::vector<std::string> a(sopClassUids_.begin(), sopClassUids_.end());
+        std::vector<std::string> b(sopInstanceUids_.begin(), sopInstanceUids_.end());
+        connection_->RequestStorageCommitment(transactionUid_, a, b);
+      }
     }
 
     //boost::this_thread::sleep(boost::posix_time::milliseconds(500));
@@ -96,8 +131,10 @@
   DicomModalityStoreJob::DicomModalityStoreJob(ServerContext& context) :
     context_(context),
     localAet_("ORTHANC"),
-    moveOriginatorId_(0)  // By default, not a C-MOVE
+    moveOriginatorId_(0),      // By default, not a C-MOVE
+    storageCommitment_(false)  // By default, no storage commitment
   {
+    ResetStorageCommitment();
   }
 
 
@@ -178,6 +215,38 @@
   }
 
 
+  void DicomModalityStoreJob::ResetStorageCommitment()
+  {
+    if (storageCommitment_)
+    {
+      transactionUid_ = Toolbox::GenerateDicomPrivateUniqueIdentifier();
+      sopClassUids_.clear();
+      sopInstanceUids_.clear();
+    }
+  }
+  
+
+  void DicomModalityStoreJob::Reset()
+  {
+    SetOfInstancesJob::Reset();
+
+    /**
+     * "After the N-EVENT-REPORT has been sent, the Transaction UID is
+     * no longer active and shall not be reused for other
+     * transactions." => Need to reset the transaction UID here
+     * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html
+     **/
+    ResetStorageCommitment();
+  }
+  
+
+  void DicomModalityStoreJob::EnableStorageCommitment(bool enabled)
+  {
+    storageCommitment_ = enabled;
+    ResetStorageCommitment();
+  }
+  
+
   void DicomModalityStoreJob::GetPublicContent(Json::Value& value)
   {
     SetOfInstancesJob::GetPublicContent(value);
@@ -190,6 +259,11 @@
       value["MoveOriginatorAET"] = GetMoveOriginatorAet();
       value["MoveOriginatorID"] = GetMoveOriginatorId();
     }
+
+    if (storageCommitment_)
+    {
+      value["StorageCommitmentTransactionUID"] = transactionUid_;
+    }
   }
 
 
@@ -197,6 +271,7 @@
   static const char* REMOTE = "Remote";
   static const char* MOVE_ORIGINATOR_AET = "MoveOriginatorAet";
   static const char* MOVE_ORIGINATOR_ID = "MoveOriginatorId";
+  static const char* STORAGE_COMMITMENT = "StorageCommitment";
   
 
   DicomModalityStoreJob::DicomModalityStoreJob(ServerContext& context,
@@ -209,6 +284,7 @@
     moveOriginatorAet_ = SerializationToolbox::ReadString(serialized, MOVE_ORIGINATOR_AET);
     moveOriginatorId_ = static_cast<uint16_t>
       (SerializationToolbox::ReadUnsignedInteger(serialized, MOVE_ORIGINATOR_ID));
+    EnableStorageCommitment(SerializationToolbox::ReadBoolean(serialized, STORAGE_COMMITMENT));
   }
 
 
@@ -224,6 +300,7 @@
       remote_.Serialize(target[REMOTE], true /* force advanced format */);
       target[MOVE_ORIGINATOR_AET] = moveOriginatorAet_;
       target[MOVE_ORIGINATOR_ID] = moveOriginatorId_;
+      target[STORAGE_COMMITMENT] = storageCommitment_;
       return true;
     }
   }  
--- a/OrthancServer/ServerJobs/DicomModalityStoreJob.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/DicomModalityStoreJob.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -33,6 +33,7 @@
 
 #pragma once
 
+#include "../../Core/Compatibility.h"
 #include "../../Core/JobsEngine/SetOfInstancesJob.h"
 #include "../../Core/DicomNetworking/DicomUserConnection.h"
 
@@ -43,19 +44,27 @@
   class DicomModalityStoreJob : public SetOfInstancesJob
   {
   private:
-    ServerContext&                      context_;
-    std::string                         localAet_;
-    RemoteModalityParameters            remote_;
-    std::string                         moveOriginatorAet_;
-    uint16_t                            moveOriginatorId_;
-    std::auto_ptr<DicomUserConnection>  connection_;
+    ServerContext&                        context_;
+    std::string                           localAet_;
+    RemoteModalityParameters              remote_;
+    std::string                           moveOriginatorAet_;
+    uint16_t                              moveOriginatorId_;
+    std::unique_ptr<DicomUserConnection>  connection_;
+    bool                                  storageCommitment_;
+
+    // For storage commitment
+    std::string             transactionUid_;
+    std::list<std::string>  sopInstanceUids_;
+    std::list<std::string>  sopClassUids_;
 
     void OpenConnection();
 
+    void ResetStorageCommitment();
+
   protected:
-    virtual bool HandleInstance(const std::string& instance);
+    virtual bool HandleInstance(const std::string& instance) ORTHANC_OVERRIDE;
     
-    virtual bool HandleTrailingStep();
+    virtual bool HandleTrailingStep() ORTHANC_OVERRIDE;
 
   public:
     DicomModalityStoreJob(ServerContext& context);
@@ -89,15 +98,19 @@
     void SetMoveOriginator(const std::string& aet,
                            int id);
 
-    virtual void Stop(JobStopReason reason);
+    virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE;
 
-    virtual void GetJobType(std::string& target)
+    virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE
     {
       target = "DicomModalityStore";
     }
 
-    virtual void GetPublicContent(Json::Value& value);
+    virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE;
+
+    virtual bool Serialize(Json::Value& target) ORTHANC_OVERRIDE;
 
-    virtual bool Serialize(Json::Value& target);
+    virtual void Reset() ORTHANC_OVERRIDE;
+
+    void EnableStorageCommitment(bool enabled);
   };
 }
--- a/OrthancServer/ServerJobs/DicomMoveScuJob.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/DicomMoveScuJob.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -46,8 +46,8 @@
   class DicomMoveScuJob::Command : public SetOfCommandsJob::ICommand
   {
   private:
-    DicomMoveScuJob&         that_;
-    std::auto_ptr<DicomMap>  findAnswer_;
+    DicomMoveScuJob&           that_;
+    std::unique_ptr<DicomMap>  findAnswer_;
 
   public:
     Command(DicomMoveScuJob& that,
@@ -57,13 +57,13 @@
     {
     }
 
-    virtual bool Execute()
+    virtual bool Execute(const std::string& jobId) ORTHANC_OVERRIDE
     {
       that_.Retrieve(*findAnswer_);
       return true;
     }
 
-    virtual void Serialize(Json::Value& target) const
+    virtual void Serialize(Json::Value& target) const ORTHANC_OVERRIDE
     {
       findAnswer_->Serialize(target);
     }
--- a/OrthancServer/ServerJobs/DicomMoveScuJob.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/DicomMoveScuJob.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -33,6 +33,7 @@
 
 #pragma once
 
+#include "../../Core/Compatibility.h"
 #include "../../Core/JobsEngine/SetOfCommandsJob.h"
 #include "../../Core/DicomNetworking/DicomUserConnection.h"
 
@@ -48,12 +49,12 @@
     class Command;
     class Unserializer;
     
-    ServerContext&                      context_;
-    std::string                         localAet_;
-    std::string                         targetAet_;
-    RemoteModalityParameters            remote_;
-    std::auto_ptr<DicomUserConnection>  connection_;
-    Json::Value                         query_;
+    ServerContext&                        context_;
+    std::string                           localAet_;
+    std::string                           targetAet_;
+    RemoteModalityParameters              remote_;
+    std::unique_ptr<DicomUserConnection>  connection_;
+    Json::Value                           query_;
 
     void Retrieve(const DicomMap& findAnswer);
     
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/ServerJobs/IStorageCommitmentFactory.h	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,66 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+namespace Orthanc
+{
+  class IStorageCommitmentFactory : public boost::noncopyable
+  {
+  public:
+    class ILookupHandler : public boost::noncopyable
+    {
+    public:
+      virtual ~ILookupHandler()
+      {
+      }
+
+      virtual StorageCommitmentFailureReason Lookup(const std::string& sopClassUid,
+                                                    const std::string& sopInstanceUid) = 0;
+    };
+
+    virtual ~IStorageCommitmentFactory()
+    {
+    }
+
+    virtual ILookupHandler* CreateStorageCommitment(const std::string& jobId,
+                                                    const std::string& transactionUid,
+                                                    const std::vector<std::string>& sopClassUids,
+                                                    const std::vector<std::string>& sopInstanceUids,
+                                                    const std::string& remoteAet,
+                                                    const std::string& calledAet) = 0;
+  };
+}
--- a/OrthancServer/ServerJobs/LuaJobManager.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/LuaJobManager.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/LuaJobManager.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/LuaJobManager.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -69,11 +69,11 @@
     class Lock : public boost::noncopyable
     {
     private:
-      LuaJobManager&                                that_;
-      boost::mutex::scoped_lock                     lock_;
-      JobsEngine&                                   engine_;
-      std::auto_ptr<SequenceOfOperationsJob::Lock>  jobLock_;
-      bool                                          isNewJob_;
+      LuaJobManager&                                  that_;
+      boost::mutex::scoped_lock                       lock_;
+      JobsEngine&                                     engine_;
+      std::unique_ptr<SequenceOfOperationsJob::Lock>  jobLock_;
+      bool                                            isNewJob_;
 
     public:
       Lock(LuaJobManager& that,
--- a/OrthancServer/ServerJobs/MergeStudyJob.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/MergeStudyJob.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -85,7 +85,7 @@
      * Retrieve the DICOM instance to be modified
      **/
     
-    std::auto_ptr<ParsedDicomFile> modified;
+    std::unique_ptr<ParsedDicomFile> modified;
 
     try
     {
--- a/OrthancServer/ServerJobs/MergeStudyJob.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/MergeStudyJob.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/Operations/DeleteResourceOperation.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/Operations/DeleteResourceOperation.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/Operations/DeleteResourceOperation.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/Operations/DeleteResourceOperation.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/Operations/DicomInstanceOperationValue.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/Operations/DicomInstanceOperationValue.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/Operations/DicomInstanceOperationValue.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/Operations/DicomInstanceOperationValue.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -94,7 +94,7 @@
 
     LOG(INFO) << "Lua: Modifying instance " << instance.GetId();
 
-    std::auto_ptr<ParsedDicomFile> modified;
+    std::unique_ptr<ParsedDicomFile> modified;
     
     {
       ServerContext::DicomCacheLocker lock(context_, instance.GetId());
--- a/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/Operations/ModifyInstanceOperation.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -33,8 +33,9 @@
 
 #pragma once
 
+#include "../../../Core/Compatibility.h"
+#include "../../../Core/DicomParsing/DicomModification.h"
 #include "../../../Core/JobsEngine/Operations/IJobOperation.h"
-#include "../../../Core/DicomParsing/DicomModification.h"
 
 namespace Orthanc
 {
@@ -43,9 +44,9 @@
   class ModifyInstanceOperation : public IJobOperation
   {
   private:
-    ServerContext&                    context_;
-    RequestOrigin                     origin_;
-    std::auto_ptr<DicomModification>  modification_;
+    ServerContext&                      context_;
+    RequestOrigin                       origin_;
+    std::unique_ptr<DicomModification>  modification_;
     
   public:
     ModifyInstanceOperation(ServerContext& context,
--- a/OrthancServer/ServerJobs/Operations/StorePeerOperation.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/Operations/StorePeerOperation.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/Operations/StorePeerOperation.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/Operations/StorePeerOperation.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -46,7 +46,7 @@
                                 const JobOperationValue& input,
                                 IDicomConnectionManager& connectionManager)
   {
-    std::auto_ptr<IDicomConnectionManager::IResource> resource
+    std::unique_ptr<IDicomConnectionManager::IResource> resource
       (connectionManager.AcquireConnection(localAet_, modality_));
 
     if (resource.get() == NULL)
@@ -70,7 +70,9 @@
     {
       std::string dicom;
       instance.ReadDicom(dicom);
-      resource->GetConnection().Store(dicom);
+
+      std::string sopClassUid, sopInstanceUid;  // Unused
+      resource->GetConnection().Store(sopClassUid, sopInstanceUid, dicom);
     }
     catch (OrthancException& e)
     {
--- a/OrthancServer/ServerJobs/Operations/StoreScuOperation.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/Operations/StoreScuOperation.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/Operations/SystemCallOperation.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/Operations/SystemCallOperation.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -81,7 +81,7 @@
 
     arguments.reserve(arguments.size() + postArguments_.size() + 1);
 
-    std::auto_ptr<TemporaryFile> tmp;
+    std::unique_ptr<TemporaryFile> tmp;
     
     switch (input.GetType())
     {
--- a/OrthancServer/ServerJobs/Operations/SystemCallOperation.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/Operations/SystemCallOperation.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -49,10 +49,11 @@
 
 #include "DicomModalityStoreJob.h"
 #include "DicomMoveScuJob.h"
+#include "MergeStudyJob.h"
 #include "OrthancPeerStoreJob.h"
 #include "ResourceModificationJob.h"
-#include "MergeStudyJob.h"
 #include "SplitStudyJob.h"
+#include "StorageCommitmentScpJob.h"
 
 
 namespace Orthanc
@@ -64,7 +65,7 @@
 #if ORTHANC_ENABLE_PLUGINS == 1
     if (context_.HasPlugins())
     {
-      std::auto_ptr<IJob> job(context_.GetPlugins().UnserializeJob(type, source));
+      std::unique_ptr<IJob> job(context_.GetPlugins().UnserializeJob(type, source));
       if (job.get() != NULL)
       {
         return job.release();
@@ -96,6 +97,10 @@
     {
       return new DicomMoveScuJob(context_, source);
     }
+    else if (type == "StorageCommitmentScp")
+    {
+      return new StorageCommitmentScpJob(context_, source);
+    }
     else
     {
       return GenericJobUnserializer::UnserializeJob(source);
--- a/OrthancServer/ServerJobs/OrthancJobUnserializer.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/OrthancJobUnserializer.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/OrthancPeerStoreJob.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/OrthancPeerStoreJob.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerJobs/OrthancPeerStoreJob.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/OrthancPeerStoreJob.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -33,6 +33,7 @@
 
 #pragma once
 
+#include "../../Core/Compatibility.h"
 #include "../../Core/JobsEngine/SetOfInstancesJob.h"
 #include "../../Core/HttpClient.h"
 
@@ -44,9 +45,9 @@
   class OrthancPeerStoreJob : public SetOfInstancesJob
   {
   private:
-    ServerContext&             context_;
-    WebServiceParameters       peer_;
-    std::auto_ptr<HttpClient>  client_;
+    ServerContext&               context_;
+    WebServiceParameters         peer_;
+    std::unique_ptr<HttpClient>  client_;
 
   protected:
     virtual bool HandleInstance(const std::string& instance);
--- a/OrthancServer/ServerJobs/ResourceModificationJob.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/ResourceModificationJob.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -147,8 +147,8 @@
      * Retrieve the original instance from the DICOM cache.
      **/
     
-    std::auto_ptr<DicomInstanceHasher> originalHasher;
-    std::auto_ptr<ParsedDicomFile> modified;
+    std::unique_ptr<DicomInstanceHasher> originalHasher;
+    std::unique_ptr<ParsedDicomFile> modified;
 
     try
     {
--- a/OrthancServer/ServerJobs/ResourceModificationJob.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/ResourceModificationJob.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -46,11 +46,11 @@
   private:
     class Output;
     
-    ServerContext&                    context_;
-    std::auto_ptr<DicomModification>  modification_;
-    boost::shared_ptr<Output>         output_;
-    bool                              isAnonymization_;
-    DicomInstanceOrigin               origin_;
+    ServerContext&                      context_;
+    std::unique_ptr<DicomModification>  modification_;
+    boost::shared_ptr<Output>           output_;
+    bool                                isAnonymization_;
+    DicomInstanceOrigin                 origin_;
 
   protected:
     virtual bool HandleInstance(const std::string& instance);
--- a/OrthancServer/ServerJobs/SplitStudyJob.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/SplitStudyJob.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -71,7 +71,7 @@
      * Retrieve the DICOM instance to be modified
      **/
     
-    std::auto_ptr<ParsedDicomFile> modified;
+    std::unique_ptr<ParsedDicomFile> modified;
 
     try
     {
--- a/OrthancServer/ServerJobs/SplitStudyJob.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerJobs/SplitStudyJob.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/ServerJobs/StorageCommitmentScpJob.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,454 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../PrecompiledHeadersServer.h"
+#include "StorageCommitmentScpJob.h"
+
+#include "../../Core/DicomNetworking/DicomUserConnection.h"
+#include "../../Core/Logging.h"
+#include "../../Core/OrthancException.h"
+#include "../../Core/SerializationToolbox.h"
+#include "../OrthancConfiguration.h"
+#include "../ServerContext.h"
+
+
+static const char* ANSWER = "Answer";
+static const char* CALLED_AET = "CalledAet";
+static const char* INDEX = "Index";
+static const char* LOOKUP = "Lookup";
+static const char* REMOTE_MODALITY = "RemoteModality";
+static const char* SETUP = "Setup";
+static const char* SOP_CLASS_UIDS = "SopClassUids";
+static const char* SOP_INSTANCE_UIDS = "SopInstanceUids";
+static const char* TRANSACTION_UID = "TransactionUid";
+static const char* TYPE = "Type";
+
+
+
+namespace Orthanc
+{
+  class StorageCommitmentScpJob::StorageCommitmentCommand : public SetOfCommandsJob::ICommand
+  {
+  public:
+    virtual CommandType GetType() const = 0;
+  };
+
+  
+  class StorageCommitmentScpJob::SetupCommand : public StorageCommitmentCommand
+  {
+  private:
+    StorageCommitmentScpJob&  that_;
+
+  public:
+    SetupCommand(StorageCommitmentScpJob& that) :
+      that_(that)
+    {
+    }
+
+    virtual CommandType GetType() const ORTHANC_OVERRIDE
+    {
+      return CommandType_Setup;
+    }
+    
+    virtual bool Execute(const std::string& jobId) ORTHANC_OVERRIDE
+    {
+      that_.Setup(jobId);
+      return true;
+    }
+
+    virtual void Serialize(Json::Value& target) const ORTHANC_OVERRIDE
+    {
+      target = Json::objectValue;
+      target[TYPE] = SETUP;
+    }
+  };
+
+
+  class StorageCommitmentScpJob::LookupCommand : public StorageCommitmentCommand
+  {
+  private:
+    StorageCommitmentScpJob&        that_;
+    size_t                          index_;
+    bool                            hasFailureReason_;
+    StorageCommitmentFailureReason  failureReason_;
+
+  public:
+    LookupCommand(StorageCommitmentScpJob&  that,
+                  size_t index) :
+      that_(that),
+      index_(index),
+      hasFailureReason_(false)
+    {
+    }
+
+    virtual CommandType GetType() const ORTHANC_OVERRIDE
+    {
+      return CommandType_Lookup;
+    }
+    
+    virtual bool Execute(const std::string& jobId) ORTHANC_OVERRIDE
+    {
+      failureReason_ = that_.Lookup(index_);
+      hasFailureReason_ = true;
+      return true;
+    }
+
+    size_t GetIndex() const
+    {
+      return index_;
+    }
+
+    StorageCommitmentFailureReason GetFailureReason() const
+    {
+      if (hasFailureReason_)
+      {
+        return failureReason_;
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_BadSequenceOfCalls);
+      }
+    }
+
+    virtual void Serialize(Json::Value& target) const ORTHANC_OVERRIDE
+    {
+      target = Json::objectValue;
+      target[TYPE] = LOOKUP;
+      target[INDEX] = static_cast<unsigned int>(index_);
+    }
+  };
+
+  
+  class StorageCommitmentScpJob::AnswerCommand : public StorageCommitmentCommand
+  {
+  private:
+    StorageCommitmentScpJob&  that_;
+
+  public:
+    AnswerCommand(StorageCommitmentScpJob& that) :
+      that_(that)
+    {
+      if (that_.ready_)
+      {
+        throw OrthancException(ErrorCode_BadSequenceOfCalls);
+      }
+      else
+      {
+        that_.ready_ = true;
+      }
+    }
+
+    virtual CommandType GetType() const ORTHANC_OVERRIDE
+    {
+      return CommandType_Answer;
+    }
+    
+    virtual bool Execute(const std::string& jobId) ORTHANC_OVERRIDE
+    {
+      that_.Answer();
+      return true;
+    }
+
+    virtual void Serialize(Json::Value& target) const ORTHANC_OVERRIDE
+    {
+      target = Json::objectValue;
+      target[TYPE] = ANSWER;
+    }
+  };
+    
+
+  class StorageCommitmentScpJob::Unserializer : public SetOfCommandsJob::ICommandUnserializer
+  {
+  private:
+    StorageCommitmentScpJob&  that_;
+
+  public:
+    Unserializer(StorageCommitmentScpJob& that) :
+      that_(that)
+    {
+      that_.ready_ = false;
+    }
+
+    virtual ICommand* Unserialize(const Json::Value& source) const
+    {
+      const std::string type = SerializationToolbox::ReadString(source, TYPE);
+
+      if (type == SETUP)
+      {
+        return new SetupCommand(that_);
+      }
+      else if (type == LOOKUP)
+      {
+        return new LookupCommand(that_, SerializationToolbox::ReadUnsignedInteger(source, INDEX));
+      }
+      else if (type == ANSWER)
+      {
+        return new AnswerCommand(that_);
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+    }
+  };
+
+
+  void StorageCommitmentScpJob::CheckInvariants()
+  {
+    const size_t n = GetCommandsCount();
+
+    if (n <= 1)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    
+    for (size_t i = 0; i < n; i++)
+    {
+      const CommandType type = dynamic_cast<const StorageCommitmentCommand&>(GetCommand(i)).GetType();
+      
+      if ((i == 0 && type != CommandType_Setup) ||
+          (i >= 1 && i < n - 1 && type != CommandType_Lookup) ||
+          (i == n - 1 && type != CommandType_Answer))
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      if (type == CommandType_Lookup)
+      {
+        const LookupCommand& lookup = dynamic_cast<const LookupCommand&>(GetCommand(i));
+        if (lookup.GetIndex() != i - 1)
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+      }
+    }
+  }
+    
+
+  void StorageCommitmentScpJob::Setup(const std::string& jobId)
+  {
+    CheckInvariants();
+
+    const std::string& remoteAet = remoteModality_.GetApplicationEntityTitle();
+    lookupHandler_.reset(context_.CreateStorageCommitment(jobId, transactionUid_, sopClassUids_,
+                                                          sopInstanceUids_, remoteAet, calledAet_));
+  }
+
+
+  StorageCommitmentFailureReason StorageCommitmentScpJob::Lookup(size_t index)
+  {
+#ifndef NDEBUG
+    CheckInvariants();
+#endif
+
+    if (index >= sopClassUids_.size())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    else if (lookupHandler_.get() != NULL)
+    {
+      return lookupHandler_->Lookup(sopClassUids_[index], sopInstanceUids_[index]);
+    }
+    else
+    {
+      // This is the default implementation of Orthanc (if no storage
+      // commitment plugin is installed)
+      bool success = false;
+      StorageCommitmentFailureReason reason =
+        StorageCommitmentFailureReason_NoSuchObjectInstance /* 0x0112 == 274 */;
+      
+      try
+      {
+        std::vector<std::string> orthancId;
+        context_.GetIndex().LookupIdentifierExact(orthancId, ResourceType_Instance, DICOM_TAG_SOP_INSTANCE_UID, sopInstanceUids_[index]);
+
+        if (orthancId.size() == 1)
+        {
+          std::string a, b;
+
+          // Make sure that the DICOM file can be re-read by DCMTK
+          // from the file storage, and that the actual SOP
+          // class/instance UIDs do match
+          ServerContext::DicomCacheLocker locker(context_, orthancId[0]);
+          if (locker.GetDicom().GetTagValue(a, DICOM_TAG_SOP_CLASS_UID) &&
+              locker.GetDicom().GetTagValue(b, DICOM_TAG_SOP_INSTANCE_UID) &&
+              b == sopInstanceUids_[index])
+          {
+            if (a == sopClassUids_[index])
+            {
+              success = true;
+              reason = StorageCommitmentFailureReason_Success;
+            }
+            else
+            {
+              // Mismatch in the SOP class UID
+              reason = StorageCommitmentFailureReason_ClassInstanceConflict /* 0x0119 */;
+            }
+          }
+        }
+      }
+      catch (OrthancException&)
+      {
+      }
+
+      LOG(INFO) << "  Storage commitment SCP job: " << (success ? "Success" : "Failure")
+                << " while looking for " << sopClassUids_[index] << " / " << sopInstanceUids_[index];
+
+      return reason;
+    }
+  }
+  
+  
+  void StorageCommitmentScpJob::Answer()
+  {   
+    CheckInvariants();
+    LOG(INFO) << "  Storage commitment SCP job: Sending answer";
+
+    std::vector<StorageCommitmentFailureReason> failureReasons;
+    failureReasons.reserve(sopClassUids_.size());
+
+    for (size_t i = 1; i < GetCommandsCount() - 1; i++)
+    {
+      const LookupCommand& lookup = dynamic_cast<const LookupCommand&>(GetCommand(i));
+      failureReasons.push_back(lookup.GetFailureReason());
+    }
+
+    if (failureReasons.size() != sopClassUids_.size())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+      
+    DicomUserConnection scu(calledAet_, remoteModality_);
+    scu.ReportStorageCommitment(transactionUid_, sopClassUids_, sopInstanceUids_, failureReasons);
+  }
+    
+
+  StorageCommitmentScpJob::StorageCommitmentScpJob(ServerContext& context,
+                                                   const std::string& transactionUid,
+                                                   const std::string& remoteAet,
+                                                   const std::string& calledAet) :
+    context_(context),
+    ready_(false),
+    transactionUid_(transactionUid),
+    calledAet_(calledAet)
+  {
+    {
+      OrthancConfiguration::ReaderLock lock;
+      if (!lock.GetConfiguration().LookupDicomModalityUsingAETitle(remoteModality_, remoteAet))
+      {
+        throw OrthancException(ErrorCode_InexistentItem,
+                               "Unknown remote modality for storage commitment SCP: " + remoteAet);
+      }
+    }
+
+    AddCommand(new SetupCommand(*this));
+  }
+    
+
+  void StorageCommitmentScpJob::Reserve(size_t size)
+  {
+    if (ready_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      sopClassUids_.reserve(size);
+      sopInstanceUids_.reserve(size);
+    }
+  }
+    
+
+  void StorageCommitmentScpJob::AddInstance(const std::string& sopClassUid,
+                                            const std::string& sopInstanceUid)
+  {
+    if (ready_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      assert(sopClassUids_.size() == sopInstanceUids_.size());
+      AddCommand(new LookupCommand(*this, sopClassUids_.size()));
+      sopClassUids_.push_back(sopClassUid);
+      sopInstanceUids_.push_back(sopInstanceUid);
+    }
+  }
+    
+
+  void StorageCommitmentScpJob::MarkAsReady()
+  {
+    AddCommand(new AnswerCommand(*this));
+  }
+
+
+  void StorageCommitmentScpJob::GetPublicContent(Json::Value& value)
+  {
+    SetOfCommandsJob::GetPublicContent(value);
+      
+    value["CalledAet"] = calledAet_;
+    value["RemoteAet"] = remoteModality_.GetApplicationEntityTitle();
+    value["TransactionUid"] = transactionUid_;
+  }
+
+
+  StorageCommitmentScpJob::StorageCommitmentScpJob(ServerContext& context,
+                                                   const Json::Value& serialized) :
+    SetOfCommandsJob(new Unserializer(*this), serialized),
+    context_(context)
+  {
+    transactionUid_ = SerializationToolbox::ReadString(serialized, TRANSACTION_UID);
+    remoteModality_ = RemoteModalityParameters(serialized[REMOTE_MODALITY]);
+    calledAet_ = SerializationToolbox::ReadString(serialized, CALLED_AET);
+    SerializationToolbox::ReadArrayOfStrings(sopClassUids_, serialized, SOP_CLASS_UIDS);
+    SerializationToolbox::ReadArrayOfStrings(sopInstanceUids_, serialized, SOP_INSTANCE_UIDS);
+  }
+  
+
+  bool StorageCommitmentScpJob::Serialize(Json::Value& target)
+  {
+    if (!SetOfCommandsJob::Serialize(target))
+    {
+      return false;
+    }
+    else
+    {
+      target[TRANSACTION_UID] = transactionUid_;
+      remoteModality_.Serialize(target[REMOTE_MODALITY], true /* force advanced format */);
+      target[CALLED_AET] = calledAet_;
+      SerializationToolbox::WriteArrayOfStrings(target, sopClassUids_, SOP_CLASS_UIDS);
+      SerializationToolbox::WriteArrayOfStrings(target, sopInstanceUids_, SOP_INSTANCE_UIDS);
+      return true;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/ServerJobs/StorageCommitmentScpJob.h	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,111 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../../Core/Compatibility.h"
+#include "../../Core/DicomNetworking/RemoteModalityParameters.h"
+#include "../../Core/JobsEngine/SetOfCommandsJob.h"
+#include "IStorageCommitmentFactory.h"
+
+#include <memory>
+#include <vector>
+
+namespace Orthanc
+{
+  class ServerContext;
+  
+  class StorageCommitmentScpJob : public SetOfCommandsJob
+  {
+  private:
+    enum CommandType
+    {
+      CommandType_Setup,
+      CommandType_Lookup,
+      CommandType_Answer
+    };
+    
+    class StorageCommitmentCommand;
+    class SetupCommand;
+    class LookupCommand;
+    class AnswerCommand;
+    class Unserializer;
+
+    ServerContext&            context_;
+    bool                      ready_;
+    std::string               transactionUid_;
+    RemoteModalityParameters  remoteModality_;
+    std::string               calledAet_;
+    std::vector<std::string>  sopClassUids_;
+    std::vector<std::string>  sopInstanceUids_;
+
+    std::unique_ptr<IStorageCommitmentFactory::ILookupHandler>  lookupHandler_;
+
+    void CheckInvariants();
+    
+    void Setup(const std::string& jobId);
+    
+    StorageCommitmentFailureReason Lookup(size_t index);
+    
+    void Answer();
+    
+  public:
+    StorageCommitmentScpJob(ServerContext& context,
+                            const std::string& transactionUid,
+                            const std::string& remoteAet,
+                            const std::string& calledAet);
+
+    StorageCommitmentScpJob(ServerContext& context,
+                            const Json::Value& serialized);
+
+    void Reserve(size_t size);
+    
+    void AddInstance(const std::string& sopClassUid,
+                     const std::string& sopInstanceUid);
+
+    void MarkAsReady();
+
+    virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE
+    {
+    }
+
+    virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE
+    {
+      target = "StorageCommitmentScp";
+    }
+
+    virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE;
+
+    virtual bool Serialize(Json::Value& target) ORTHANC_OVERRIDE;
+  };
+}
--- a/OrthancServer/ServerToolbox.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerToolbox.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/ServerToolbox.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/ServerToolbox.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/SliceOrdering.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/SliceOrdering.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/OrthancServer/SliceOrdering.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/SliceOrdering.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/StorageCommitmentReports.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,272 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "PrecompiledHeadersServer.h"
+#include "StorageCommitmentReports.h"
+
+#include "../Core/OrthancException.h"
+
+namespace Orthanc
+{
+  void StorageCommitmentReports::Report::MarkAsComplete()
+  {
+    if (isComplete_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      isComplete_ = true;
+    }
+  }
+
+  void StorageCommitmentReports::Report::AddSuccess(const std::string& sopClassUid,
+                                                    const std::string& sopInstanceUid)
+  {
+    if (isComplete_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      Success success;
+      success.sopClassUid_ = sopClassUid;
+      success.sopInstanceUid_ = sopInstanceUid;
+      success_.push_back(success);
+    }
+  }
+
+  void StorageCommitmentReports::Report::AddFailure(const std::string& sopClassUid,
+                                                    const std::string& sopInstanceUid,
+                                                    StorageCommitmentFailureReason reason)
+  {
+    if (isComplete_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      Failure failure;
+      failure.sopClassUid_ = sopClassUid;
+      failure.sopInstanceUid_ = sopInstanceUid;
+      failure.reason_ = reason;
+      failures_.push_back(failure);
+    }
+  }
+
+  
+  StorageCommitmentReports::Report::Status StorageCommitmentReports::Report::GetStatus() const
+  {
+    if (!isComplete_)
+    {
+      return Status_Pending;
+    }
+    else if (failures_.empty())
+    {
+      return Status_Success;
+    }
+    else
+    {
+      return Status_Failure;
+    }
+  }
+
+
+  void StorageCommitmentReports::Report::Format(Json::Value& json) const
+  {
+    static const char* const FIELD_STATUS = "Status";
+    static const char* const FIELD_SOP_CLASS_UID = "SOPClassUID";
+    static const char* const FIELD_SOP_INSTANCE_UID = "SOPInstanceUID";
+    static const char* const FIELD_FAILURE_REASON = "FailureReason";
+    static const char* const FIELD_DESCRIPTION = "Description";
+    static const char* const FIELD_REMOTE_AET = "RemoteAET";
+    static const char* const FIELD_SUCCESS = "Success";
+    static const char* const FIELD_FAILURES = "Failures";
+
+    
+    json = Json::objectValue;
+    json[FIELD_REMOTE_AET] = remoteAet_;
+
+    bool pending;
+    
+    switch (GetStatus())
+    {
+      case Status_Pending:
+        json[FIELD_STATUS] = "Pending";
+        pending = true;
+        break;
+
+      case Status_Success:
+        json[FIELD_STATUS] = "Success";
+        pending = false;
+        break;
+
+      case Status_Failure:
+        json[FIELD_STATUS] = "Failure";
+        pending = false;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+
+    if (!pending)
+    {
+      {
+        Json::Value success = Json::arrayValue;
+        for (std::list<Success>::const_iterator
+               it = success_.begin(); it != success_.end(); ++it)
+        {
+          Json::Value item = Json::objectValue;
+          item[FIELD_SOP_CLASS_UID] = it->sopClassUid_;
+          item[FIELD_SOP_INSTANCE_UID] = it->sopInstanceUid_;
+          success.append(item);
+        }
+
+        json[FIELD_SUCCESS] = success;
+      }
+
+      {
+        Json::Value failures = Json::arrayValue;
+        for (std::list<Failure>::const_iterator
+               it = failures_.begin(); it != failures_.end(); ++it)
+        {
+          Json::Value item = Json::objectValue;
+          item[FIELD_SOP_CLASS_UID] = it->sopClassUid_;
+          item[FIELD_SOP_INSTANCE_UID] = it->sopInstanceUid_;
+          item[FIELD_FAILURE_REASON] = it->reason_;
+          item[FIELD_DESCRIPTION] = EnumerationToString(it->reason_);
+          failures.append(item);
+        }
+
+        json[FIELD_FAILURES] = failures;
+      }
+    }
+  }
+
+
+  void StorageCommitmentReports::Report::GetSuccessSopInstanceUids(
+    std::vector<std::string>& target) const
+  {
+    target.clear();
+    target.reserve(success_.size());
+
+    for (std::list<Success>::const_iterator
+           it = success_.begin(); it != success_.end(); ++it)
+    {
+      target.push_back(it->sopInstanceUid_);
+    }
+  }
+
+
+  StorageCommitmentReports::~StorageCommitmentReports()
+  {
+    while (!content_.IsEmpty())
+    {
+      Report* report = NULL;
+      content_.RemoveOldest(report);
+
+      assert(report != NULL);
+      delete report;
+    }
+  }
+
+  
+  void StorageCommitmentReports::Store(const std::string& transactionUid,
+                                       Report* report)
+  {
+    std::unique_ptr<Report> protection(report);
+    
+    boost::mutex::scoped_lock lock(mutex_);
+
+    {
+      Report* previous = NULL;
+      if (content_.Contains(transactionUid, previous))
+      {
+        assert(previous != NULL);
+        delete previous;
+
+        content_.Invalidate(transactionUid);
+      }
+    }
+
+    assert(maxSize_ == 0 ||
+           content_.GetSize() <= maxSize_);
+
+    if (maxSize_ != 0 &&
+        content_.GetSize() == maxSize_)
+    {
+      assert(!content_.IsEmpty());
+      
+      Report* oldest = NULL;
+      content_.RemoveOldest(oldest);
+
+      assert(oldest != NULL);
+      delete oldest;
+    }
+
+    assert(maxSize_ == 0 ||
+           content_.GetSize() < maxSize_);
+
+    content_.Add(transactionUid, protection.release());
+  }
+
+
+  StorageCommitmentReports::Accessor::Accessor(StorageCommitmentReports& that,
+                                               const std::string& transactionUid) :
+    lock_(that.mutex_),
+    transactionUid_(transactionUid)
+  {
+    if (that.content_.Contains(transactionUid, report_))
+    {
+      that.content_.MakeMostRecent(transactionUid);
+    }
+    else
+    {
+      report_ = NULL;
+    }
+  }
+
+  const StorageCommitmentReports::Report&
+  StorageCommitmentReports::Accessor::GetReport() const
+  {
+    if (report_ == NULL)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      return *report_;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/StorageCommitmentReports.h	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,147 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Core/Cache/LeastRecentlyUsedIndex.h"
+
+namespace Orthanc
+{
+  class StorageCommitmentReports
+  {
+  public:
+    class Report : public boost::noncopyable
+    {
+    public:
+      enum Status
+      {
+        Status_Success,
+        Status_Failure,
+        Status_Pending
+      };
+      
+    private:
+      struct Success
+      {
+        std::string  sopClassUid_;
+        std::string  sopInstanceUid_;
+      };
+      
+      struct Failure
+      {
+        std::string  sopClassUid_;
+        std::string  sopInstanceUid_;
+        StorageCommitmentFailureReason  reason_;
+      };
+      
+      bool                isComplete_;
+      std::list<Success>  success_;
+      std::list<Failure>  failures_;
+      std::string         remoteAet_;
+
+    public:
+      Report(const std::string& remoteAet) :
+        isComplete_(false),
+        remoteAet_(remoteAet)
+      {
+      }
+
+      const std::string& GetRemoteAet() const
+      {
+        return remoteAet_;
+      }
+
+      void MarkAsComplete();
+
+      void AddSuccess(const std::string& sopClassUid,
+                      const std::string& sopInstanceUid);
+
+      void AddFailure(const std::string& sopClassUid,
+                      const std::string& sopInstanceUid,
+                      StorageCommitmentFailureReason reason);
+
+      Status GetStatus() const;
+
+      void Format(Json::Value& json) const;
+
+      void GetSuccessSopInstanceUids(std::vector<std::string>& target) const;
+    };
+
+  private:
+    typedef LeastRecentlyUsedIndex<std::string, Report*>  Content;
+    
+    boost::mutex   mutex_;
+    Content        content_;
+    size_t         maxSize_;
+
+  public:
+    StorageCommitmentReports(size_t maxSize) :
+      maxSize_(maxSize)
+    {
+    }
+
+    ~StorageCommitmentReports();
+
+    size_t GetMaxSize() const
+    {
+      return maxSize_;
+    }
+
+    void Store(const std::string& transactionUid,
+               Report* report); // Takes ownership
+
+    class Accessor : public boost::noncopyable
+    {
+    private:
+      boost::mutex::scoped_lock  lock_;
+      std::string                transactionUid_;
+      Report                    *report_;
+
+    public:
+      Accessor(StorageCommitmentReports& that,
+               const std::string& transactionUid);
+
+      const std::string& GetTransactionUid() const
+      {
+        return transactionUid_;
+      }
+
+      bool IsValid() const
+      {
+        return report_ != NULL;
+      }
+
+      const Report& GetReport() const;
+    };
+  };
+}
--- a/OrthancServer/main.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/OrthancServer/main.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -36,6 +36,7 @@
 
 #include <boost/algorithm/string/predicate.hpp>
 
+#include "../Core/Compatibility.h"
 #include "../Core/DicomFormat/DicomArray.h"
 #include "../Core/DicomNetworking/DicomServer.h"
 #include "../Core/DicomParsing/FromDcmtkBridge.h"
@@ -50,7 +51,9 @@
 #include "OrthancInitialization.h"
 #include "OrthancMoveRequestHandler.h"
 #include "ServerContext.h"
+#include "ServerJobs/StorageCommitmentScpJob.h"
 #include "ServerToolbox.h"
+#include "StorageCommitmentReports.h"
 
 using namespace Orthanc;
 
@@ -58,11 +61,11 @@
 class OrthancStoreRequestHandler : public IStoreRequestHandler
 {
 private:
-  ServerContext& server_;
+  ServerContext& context_;
 
 public:
   OrthancStoreRequestHandler(ServerContext& context) :
-    server_(context)
+    context_(context)
   {
   }
 
@@ -84,8 +87,82 @@
       toStore.SetJson(dicomJson);
 
       std::string id;
-      server_.Store(id, toStore);
+      context_.Store(id, toStore);
+    }
+  }
+};
+
+
+
+class OrthancStorageCommitmentRequestHandler : public IStorageCommitmentRequestHandler
+{
+private:
+  ServerContext& context_;
+  
+public:
+  OrthancStorageCommitmentRequestHandler(ServerContext& context) :
+    context_(context)
+  {
+  }
+
+  virtual void HandleRequest(const std::string& transactionUid,
+                             const std::vector<std::string>& referencedSopClassUids,
+                             const std::vector<std::string>& referencedSopInstanceUids,
+                             const std::string& remoteIp,
+                             const std::string& remoteAet,
+                             const std::string& calledAet)
+  {
+    if (referencedSopClassUids.size() != referencedSopInstanceUids.size())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    
+    std::unique_ptr<StorageCommitmentScpJob> job(
+      new StorageCommitmentScpJob(context_, transactionUid, remoteAet, calledAet));
+
+    for (size_t i = 0; i < referencedSopClassUids.size(); i++)
+    {
+      job->AddInstance(referencedSopClassUids[i], referencedSopInstanceUids[i]);
     }
+
+    job->MarkAsReady();
+
+    context_.GetJobsEngine().GetRegistry().Submit(job.release(), 0 /* default priority */);
+  }
+
+  virtual void HandleReport(const std::string& transactionUid,
+                            const std::vector<std::string>& successSopClassUids,
+                            const std::vector<std::string>& successSopInstanceUids,
+                            const std::vector<std::string>& failedSopClassUids,
+                            const std::vector<std::string>& failedSopInstanceUids,
+                            const std::vector<StorageCommitmentFailureReason>& failureReasons,
+                            const std::string& remoteIp,
+                            const std::string& remoteAet,
+                            const std::string& calledAet)
+  {
+    if (successSopClassUids.size() != successSopInstanceUids.size() ||
+        failedSopClassUids.size() != failedSopInstanceUids.size() ||
+        failedSopClassUids.size() != failureReasons.size())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+    
+    std::unique_ptr<StorageCommitmentReports::Report> report(
+      new StorageCommitmentReports::Report(remoteAet));
+
+    for (size_t i = 0; i < successSopClassUids.size(); i++)
+    {
+      report->AddSuccess(successSopClassUids[i], successSopInstanceUids[i]);
+    }
+
+    for (size_t i = 0; i < failedSopClassUids.size(); i++)
+    {
+      report->AddFailure(failedSopClassUids[i], failedSopInstanceUids[i], failureReasons[i]);
+    }
+
+    report->MarkAsComplete();
+
+    context_.GetStorageCommitmentReports().Store(transactionUid, report.release());
   }
 };
 
@@ -113,7 +190,8 @@
 class MyDicomServerFactory : 
   public IStoreRequestHandlerFactory,
   public IFindRequestHandlerFactory, 
-  public IMoveRequestHandlerFactory
+  public IMoveRequestHandlerFactory, 
+  public IStorageCommitmentRequestHandlerFactory
 {
 private:
   ServerContext& context_;
@@ -130,7 +208,7 @@
 
   virtual IFindRequestHandler* ConstructFindRequestHandler()
   {
-    std::auto_ptr<OrthancFindRequestHandler> result(new OrthancFindRequestHandler(context_));
+    std::unique_ptr<OrthancFindRequestHandler> result(new OrthancFindRequestHandler(context_));
 
     {
       OrthancConfiguration::ReaderLock lock;
@@ -166,6 +244,11 @@
     return new OrthancMoveRequestHandler(context_);
   }
 
+  virtual IStorageCommitmentRequestHandler* ConstructStorageCommitmentRequestHandler()
+  {
+    return new OrthancStorageCommitmentRequestHandler(context_);
+  }
+
   void Done()
   {
   }
@@ -276,6 +359,10 @@
         configuration = "Mpeg2TransferSyntaxAccepted";
         break;
 
+      case TransferSyntax_Mpeg4:
+        configuration = "Mpeg4TransferSyntaxAccepted";
+        break;
+
       case TransferSyntax_Rle:
         configuration = "RleTransferSyntaxAccepted";
         break;
@@ -543,7 +630,7 @@
   std::cout
     << path << " " << ORTHANC_VERSION << std::endl
     << "Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics Department, University Hospital of Liege (Belgium)" << std::endl
-    << "Copyright (C) 2017-2019 Osimis S.A. (Belgium)" << std::endl
+    << "Copyright (C) 2017-2020 Osimis S.A. (Belgium)" << std::endl
     << "Licensing GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>, with OpenSSL exception." << std::endl
     << "This is free software: you are free to change and redistribute it." << std::endl
     << "There is NO WARRANTY, to the extent permitted by law." << std::endl
@@ -672,6 +759,7 @@
     PrintErrorCode(ErrorCode_CannotOrderSlices, "Unable to order the slices of the series");
     PrintErrorCode(ErrorCode_NoWorklistHandler, "No request handler factory for DICOM C-Find Modality SCP");
     PrintErrorCode(ErrorCode_AlreadyExistingTag, "Cannot override the value of a tag that already exists");
+    PrintErrorCode(ErrorCode_NoStorageCommitmentHandler, "No request handler factory for DICOM N-ACTION SCP (storage commitment)");
     PrintErrorCode(ErrorCode_UnsupportedMediaType, "Unsupported media type");
   }
 
@@ -966,6 +1054,7 @@
     dicomServer.SetStoreRequestHandlerFactory(serverFactory);
     dicomServer.SetMoveRequestHandlerFactory(serverFactory);
     dicomServer.SetFindRequestHandlerFactory(serverFactory);
+    dicomServer.SetStorageCommitmentRequestHandlerFactory(serverFactory);
 
     {
       OrthancConfiguration::ReaderLock lock;
@@ -1290,8 +1379,8 @@
                              bool upgradeDatabase,
                              bool loadJobsFromDatabase)
 {
-  std::auto_ptr<IDatabaseWrapper>  databasePtr;
-  std::auto_ptr<IStorageArea>  storage;
+  std::unique_ptr<IDatabaseWrapper>  databasePtr;
+  std::unique_ptr<IStorageArea>  storage;
 
 #if ORTHANC_ENABLE_PLUGINS == 1
   OrthancPlugins plugins;
--- a/Plugins/Engine/IPluginServiceProvider.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Engine/IPluginServiceProvider.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Engine/OrthancPluginDatabase.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Engine/OrthancPluginDatabase.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Engine/OrthancPluginDatabase.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Engine/OrthancPluginDatabase.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Engine/OrthancPlugins.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Engine/OrthancPlugins.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -367,7 +367,7 @@
       
     public:
       DicomWebBinaryFormatter(const _OrthancPluginEncodeDicomWeb& parameters) :
-      callback_(parameters.callback)
+        callback_(parameters.callback)
       {
       }
       
@@ -435,7 +435,7 @@
       };
 
       HttpOutput&                 output_;
-      std::auto_ptr<std::string>  errorDetails_;
+      std::unique_ptr<std::string>  errorDetails_;
       bool                        logDetails_;
       MultipartState              multipartState_;
       std::string                 multipartSubType_;
@@ -665,8 +665,8 @@
 
     public:
       ChunkedRestCallback(_OrthancPluginChunkedRestCallback parameters) :
-      parameters_(parameters),
-      regex_(parameters.pathRegularExpression)
+        parameters_(parameters),
+        regex_(parameters.pathRegularExpression)
       {
       }
 
@@ -682,6 +682,110 @@
     };
 
 
+
+    class StorageCommitmentScp : public IStorageCommitmentFactory
+    {
+    private:
+      class Handler : public IStorageCommitmentFactory::ILookupHandler
+      {
+      private:
+        _OrthancPluginRegisterStorageCommitmentScpCallback  parameters_;
+        void*    handler_;
+
+      public:
+        Handler(_OrthancPluginRegisterStorageCommitmentScpCallback  parameters,
+                void* handler) :
+          parameters_(parameters),
+          handler_(handler)
+        {
+          if (handler == NULL)
+          {
+            throw OrthancException(ErrorCode_NullPointer);
+          }
+        }
+
+        virtual ~Handler()
+        {
+          assert(handler_ != NULL);
+          parameters_.destructor(handler_);
+          handler_ = NULL;
+        }
+
+        virtual StorageCommitmentFailureReason Lookup(const std::string& sopClassUid,
+                                                      const std::string& sopInstanceUid)
+        {
+          assert(handler_ != NULL);
+          OrthancPluginStorageCommitmentFailureReason reason =
+            OrthancPluginStorageCommitmentFailureReason_Success;
+          OrthancPluginErrorCode error = parameters_.lookup(
+            &reason, handler_, sopClassUid.c_str(), sopInstanceUid.c_str());
+          if (error == OrthancPluginErrorCode_Success)
+          {
+            return Plugins::Convert(reason);
+          }
+          else
+          {
+            throw OrthancException(static_cast<ErrorCode>(error));
+          }
+        }
+      };
+      
+      _OrthancPluginRegisterStorageCommitmentScpCallback  parameters_;
+
+    public:
+      StorageCommitmentScp(_OrthancPluginRegisterStorageCommitmentScpCallback parameters) :
+        parameters_(parameters)
+      {
+      }
+
+      virtual ILookupHandler* CreateStorageCommitment(
+        const std::string& jobId,
+        const std::string& transactionUid,
+        const std::vector<std::string>& sopClassUids,
+        const std::vector<std::string>& sopInstanceUids,
+        const std::string& remoteAet,
+        const std::string& calledAet) ORTHANC_OVERRIDE
+      {
+        const size_t n = sopClassUids.size();
+        
+        if (sopInstanceUids.size() != n)
+        {
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+        }
+        
+        std::vector<const char*> a, b;
+        a.resize(n);
+        b.resize(n);
+
+        for (size_t i = 0; i < n; i++)
+        {
+          a[i] = sopClassUids[i].c_str();
+          b[i] = sopInstanceUids[i].c_str();
+        }
+
+        void* handler = NULL;
+        OrthancPluginErrorCode error = parameters_.factory(
+          &handler, jobId.c_str(), transactionUid.c_str(),
+          a.empty() ? NULL : &a[0], b.empty() ? NULL : &b[0], static_cast<uint32_t>(n),
+          remoteAet.c_str(), calledAet.c_str());
+
+        if (error != OrthancPluginErrorCode_Success)
+        {
+          throw OrthancException(static_cast<ErrorCode>(error));          
+        }
+        else if (handler == NULL)
+        {
+          // This plugin won't handle this storage commitment request
+          return NULL;
+        }
+        else
+        {
+          return new Handler(parameters_, handler);
+        }
+      }
+    };
+
+
     class ServerContextLock
     {
     private:
@@ -690,8 +794,8 @@
 
     public:
       ServerContextLock(PImpl& that) : 
-      lock_(that.contextMutex_),
-      context_(that.context_)
+        lock_(that.contextMutex_),
+        context_(that.context_)
       {
         if (context_ == NULL)
         {
@@ -724,6 +828,7 @@
     typedef std::list<OrthancPluginDecodeImageCallback>  DecodeImageCallbacks;
     typedef std::list<OrthancPluginJobsUnserializer>  JobsUnserializers;
     typedef std::list<OrthancPluginRefreshMetricsCallback>  RefreshMetricsCallbacks;
+    typedef std::list<StorageCommitmentScp*>  StorageCommitmentScpCallbacks;
     typedef std::map<Property, std::string>  Properties;
 
     PluginsManager manager_;
@@ -740,7 +845,8 @@
     IncomingHttpRequestFilters  incomingHttpRequestFilters_;
     IncomingHttpRequestFilters2 incomingHttpRequestFilters2_;
     RefreshMetricsCallbacks refreshMetricsCallbacks_;
-    std::auto_ptr<StorageAreaFactory>  storageArea_;
+    StorageCommitmentScpCallbacks storageCommitmentScpCallbacks_;
+    std::unique_ptr<StorageAreaFactory>  storageArea_;
 
     boost::recursive_mutex restCallbackMutex_;
     boost::recursive_mutex storedCallbackMutex_;
@@ -750,12 +856,13 @@
     boost::mutex decodeImageCallbackMutex_;
     boost::mutex jobsUnserializersMutex_;
     boost::mutex refreshMetricsMutex_;
+    boost::mutex storageCommitmentScpMutex_;
     boost::recursive_mutex invokeServiceMutex_;
 
     Properties properties_;
     int argc_;
     char** argv_;
-    std::auto_ptr<OrthancPluginDatabase>  database_;
+    std::unique_ptr<OrthancPluginDatabase>  database_;
     PluginsErrorDictionary  dictionary_;
 
     PImpl() : 
@@ -775,8 +882,8 @@
   {
   private:
     OrthancPlugins&  that_;
-    std::auto_ptr<HierarchicalMatcher> matcher_;
-    std::auto_ptr<ParsedDicomFile>     filtered_;
+    std::unique_ptr<HierarchicalMatcher> matcher_;
+    std::unique_ptr<ParsedDicomFile>     filtered_;
     ParsedDicomFile* currentQuery_;
 
     void Reset()
@@ -824,7 +931,8 @@
           Json::Value target;
           call.ExecuteToJson(target, true);
           
-          filtered_.reset(ParsedDicomFile::CreateFromJson(target, DicomFromJsonFlags_None));
+          filtered_.reset(ParsedDicomFile::CreateFromJson(target, DicomFromJsonFlags_None,
+                                                          "" /* no private creator */));
           currentQuery_ = filtered_.get();
         }
       }
@@ -888,7 +996,7 @@
       }
 
       ParsedDicomFile f(dicom, size);
-      std::auto_ptr<ParsedDicomFile> summary(matcher_->Extract(f));
+      std::unique_ptr<ParsedDicomFile> summary(matcher_->Extract(f));
       reinterpret_cast<DicomFindAnswers*>(answers)->Add(*summary);
     }
   };
@@ -898,7 +1006,7 @@
   {
   private:
     OrthancPlugins&            that_;
-    std::auto_ptr<DicomArray>  currentQuery_;
+    std::unique_ptr<DicomArray>  currentQuery_;
 
     void Reset()
     {
@@ -1260,6 +1368,7 @@
         sizeof(int32_t) != sizeof(OrthancPluginConstraintType) ||
         sizeof(int32_t) != sizeof(OrthancPluginMetricsType) ||
         sizeof(int32_t) != sizeof(OrthancPluginDicomWebBinaryMode) ||
+        sizeof(int32_t) != sizeof(OrthancPluginStorageCommitmentFailureReason) ||
         static_cast<int>(OrthancPluginDicomToJsonFlags_IncludeBinary) != static_cast<int>(DicomToJsonFlags_IncludeBinary) ||
         static_cast<int>(OrthancPluginDicomToJsonFlags_IncludePrivateTags) != static_cast<int>(DicomToJsonFlags_IncludePrivateTags) ||
         static_cast<int>(OrthancPluginDicomToJsonFlags_IncludeUnknownTags) != static_cast<int>(DicomToJsonFlags_IncludeUnknownTags) ||
@@ -1303,6 +1412,13 @@
     {
       delete *it;
     }
+
+    for (PImpl::StorageCommitmentScpCallbacks::iterator
+           it = pimpl_->storageCommitmentScpCallbacks_.begin(); 
+         it != pimpl_->storageCommitmentScpCallbacks_.end(); ++it)
+    {
+      delete *it;
+    } 
   }
 
 
@@ -1350,7 +1466,7 @@
       
     public:
       RestCallbackMatcher(const UriComponents& uri) :
-      flatUri_(Toolbox::FlattenUri(uri))
+        flatUri_(Toolbox::FlattenUri(uri))
       {
       }
 
@@ -1863,6 +1979,18 @@
   }
 
 
+  void OrthancPlugins::RegisterStorageCommitmentScpCallback(const void* parameters)
+  {
+    const _OrthancPluginRegisterStorageCommitmentScpCallback& p = 
+      *reinterpret_cast<const _OrthancPluginRegisterStorageCommitmentScpCallback*>(parameters);
+
+    boost::mutex::scoped_lock lock(pimpl_->storageCommitmentScpMutex_);
+    LOG(INFO) << "Plugin has registered a storage commitment callback";
+
+    pimpl_->storageCommitmentScpCallbacks_.push_back(new PImpl::StorageCommitmentScp(p));
+  }
+
+
   void OrthancPlugins::AnswerBuffer(const void* parameters)
   {
     const _OrthancPluginAnswerBuffer& p = 
@@ -2355,7 +2483,7 @@
     std::string result;
 
     {
-      std::auto_ptr<DeflateBaseCompressor> compressor;
+      std::unique_ptr<DeflateBaseCompressor> compressor;
 
       switch (p.compression)
       {
@@ -2405,14 +2533,14 @@
   }
 
 
-  static OrthancPluginImage* ReturnImage(std::auto_ptr<ImageAccessor>& image)
+  static OrthancPluginImage* ReturnImage(std::unique_ptr<ImageAccessor>& image)
   {
     // Images returned to plugins are assumed to be writeable. If the
     // input image is read-only, we return a copy so that it can be modified.
 
     if (image->IsReadOnly())
     {
-      std::auto_ptr<Image> copy(new Image(image->GetFormat(), image->GetWidth(), image->GetHeight(), false));
+      std::unique_ptr<Image> copy(new Image(image->GetFormat(), image->GetWidth(), image->GetHeight(), false));
       ImageProcessing::Copy(*copy, *image);
       image.reset(NULL);
       return reinterpret_cast<OrthancPluginImage*>(copy.release());
@@ -2428,7 +2556,7 @@
   {
     const _OrthancPluginUncompressImage& p = *reinterpret_cast<const _OrthancPluginUncompressImage*>(parameters);
 
-    std::auto_ptr<ImageAccessor> image;
+    std::unique_ptr<ImageAccessor> image;
 
     switch (p.format)
     {
@@ -2812,7 +2940,7 @@
     const _OrthancPluginConvertPixelFormat& p = *reinterpret_cast<const _OrthancPluginConvertPixelFormat*>(parameters);
     const ImageAccessor& source = *reinterpret_cast<const ImageAccessor*>(p.source);
 
-    std::auto_ptr<ImageAccessor> target(new Image(Plugins::Convert(p.targetFormat), source.GetWidth(), source.GetHeight(), false));
+    std::unique_ptr<ImageAccessor> target(new Image(Plugins::Convert(p.targetFormat), source.GetWidth(), source.GetHeight(), false));
     ImageProcessing::Convert(*target, source);
 
     *(p.target) = ReturnImage(target);
@@ -2865,7 +2993,7 @@
     const _OrthancPluginDicomToJson& p =
       *reinterpret_cast<const _OrthancPluginDicomToJson*>(parameters);
 
-    std::auto_ptr<ParsedDicomFile> dicom;
+    std::unique_ptr<ParsedDicomFile> dicom;
 
     if (service == _OrthancPluginService_DicomBufferToJson)
     {
@@ -2921,8 +3049,18 @@
     std::string dicom;
 
     {
-      std::auto_ptr<ParsedDicomFile> file
-        (ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(p.flags)));
+      // Fix issue 168 (Plugins can't read private tags from the
+      // configuration file)
+      // https://bitbucket.org/sjodogne/orthanc/issues/168/
+      std::string privateCreator;
+      {
+        OrthancConfiguration::ReaderLock lock;
+        privateCreator = lock.GetConfiguration().GetDefaultPrivateCreator();
+      }
+      
+      std::unique_ptr<ParsedDicomFile> file
+        (ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(p.flags),
+                                         privateCreator));
 
       if (p.pixelData)
       {
@@ -2984,7 +3122,7 @@
     const _OrthancPluginCreateImage& p =
       *reinterpret_cast<const _OrthancPluginCreateImage*>(parameters);
 
-    std::auto_ptr<ImageAccessor> result;
+    std::unique_ptr<ImageAccessor> result;
 
     switch (service)
     {
@@ -3095,7 +3233,25 @@
     DcmTagKey tag2(tag.GetGroup(), tag.GetElement());
 
     DictionaryReadLocker locker;
-    const DcmDictEntry* entry = locker->findEntry(tag2, NULL);
+    const DcmDictEntry* entry = NULL;
+
+    if (tag.IsPrivate())
+    {
+      // Fix issue 168 (Plugins can't read private tags from the
+      // configuration file)
+      // https://bitbucket.org/sjodogne/orthanc/issues/168/
+      std::string privateCreator;
+      {
+        OrthancConfiguration::ReaderLock lock;
+        privateCreator = lock.GetConfiguration().GetDefaultPrivateCreator();
+      }
+
+      entry = locker->findEntry(tag2, privateCreator.c_str());
+    }
+    else
+    {
+      entry = locker->findEntry(tag2, NULL);
+    }
 
     if (entry == NULL)
     {
@@ -3882,6 +4038,10 @@
         RegisterRefreshMetricsCallback(parameters);
         return true;
 
+      case _OrthancPluginService_RegisterStorageCommitmentScpCallback:
+        RegisterStorageCommitmentScpCallback(parameters);
+        return true;
+
       case _OrthancPluginService_RegisterStorageArea:
       {
         LOG(INFO) << "Plugin has registered a custom storage area";
@@ -4452,7 +4612,7 @@
   };
 
 
-  bool OrthancPlugins::CreateChunkedRequestReader(std::auto_ptr<IChunkedRequestReader>& target,
+  bool OrthancPlugins::CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader>& target,
                                                   RequestOrigin origin,
                                                   const char* remoteIp,
                                                   const char* username,
@@ -4539,4 +4699,32 @@
       }
     }
   }
+
+
+  IStorageCommitmentFactory::ILookupHandler* OrthancPlugins::CreateStorageCommitment(
+    const std::string& jobId,
+    const std::string& transactionUid,
+    const std::vector<std::string>& sopClassUids,
+    const std::vector<std::string>& sopInstanceUids,
+    const std::string& remoteAet,
+    const std::string& calledAet)
+  {
+    boost::mutex::scoped_lock lock(pimpl_->storageCommitmentScpMutex_);
+
+    for (PImpl::StorageCommitmentScpCallbacks::iterator
+           it = pimpl_->storageCommitmentScpCallbacks_.begin(); 
+         it != pimpl_->storageCommitmentScpCallbacks_.end(); ++it)
+    {
+      assert(*it != NULL);
+      IStorageCommitmentFactory::ILookupHandler* handler = (*it)->CreateStorageCommitment
+        (jobId, transactionUid, sopClassUids, sopInstanceUids, remoteAet, calledAet);
+
+      if (handler != NULL)
+      {
+        return handler;
+      }
+    } 
+    
+    return NULL;
+  }
 }
--- a/Plugins/Engine/OrthancPlugins.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Engine/OrthancPlugins.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -62,6 +62,7 @@
 #include "../../Core/JobsEngine/IJob.h"
 #include "../../OrthancServer/IDicomImageDecoder.h"
 #include "../../OrthancServer/IServerListener.h"
+#include "../../OrthancServer/ServerJobs/IStorageCommitmentFactory.h"
 #include "OrthancPluginDatabase.h"
 #include "PluginsManager.h"
 
@@ -80,7 +81,8 @@
     public IDicomImageDecoder,
     public IIncomingHttpRequestFilter,
     public IFindRequestHandlerFactory,
-    public IMoveRequestHandlerFactory
+    public IMoveRequestHandlerFactory,
+    public IStorageCommitmentFactory
   {
   private:
     class PImpl;
@@ -124,6 +126,8 @@
 
     void RegisterRefreshMetricsCallback(const void* parameters);
 
+    void RegisterStorageCommitmentScpCallback(const void* parameters);
+
     void AnswerBuffer(const void* parameters);
 
     void Redirect(const void* parameters);
@@ -235,20 +239,20 @@
                         const Arguments& headers,
                         const GetArguments& getArguments,
                         const void* bodyData,
-                        size_t bodySize);
+                        size_t bodySize) ORTHANC_OVERRIDE;
 
     virtual bool InvokeService(SharedLibrary& plugin,
                                _OrthancPluginService service,
-                               const void* parameters);
+                               const void* parameters) ORTHANC_OVERRIDE;
 
-    virtual void SignalChange(const ServerIndexChange& change);
-
+    virtual void SignalChange(const ServerIndexChange& change) ORTHANC_OVERRIDE;
+    
     virtual void SignalStoredInstance(const std::string& instanceId,
                                       DicomInstanceToStore& instance,
-                                      const Json::Value& simplifiedTags);
+                                      const Json::Value& simplifiedTags) ORTHANC_OVERRIDE;
 
     virtual bool FilterIncomingInstance(const DicomInstanceToStore& instance,
-                                        const Json::Value& simplified)
+                                        const Json::Value& simplified) ORTHANC_OVERRIDE
     {
       return true; // TODO Enable filtering of instances from plugins
     }
@@ -298,7 +302,7 @@
 
     bool HasWorklistHandler();
 
-    virtual IWorklistRequestHandler* ConstructWorklistRequestHandler();
+    virtual IWorklistRequestHandler* ConstructWorklistRequestHandler() ORTHANC_OVERRIDE;
 
     bool HasCustomImageDecoder();
 
@@ -311,22 +315,22 @@
 
     virtual ImageAccessor* Decode(const void* dicom,
                                   size_t size,
-                                  unsigned int frame);
+                                  unsigned int frame) ORTHANC_OVERRIDE;
 
     virtual bool IsAllowed(HttpMethod method,
                            const char* uri,
                            const char* ip,
                            const char* username,
                            const IHttpHandler::Arguments& httpHeaders,
-                           const IHttpHandler::GetArguments& getArguments);
+                           const IHttpHandler::GetArguments& getArguments) ORTHANC_OVERRIDE;
 
     bool HasFindHandler();
 
-    virtual IFindRequestHandler* ConstructFindRequestHandler();
+    virtual IFindRequestHandler* ConstructFindRequestHandler() ORTHANC_OVERRIDE;
 
     bool HasMoveHandler();
 
-    virtual IMoveRequestHandler* ConstructMoveRequestHandler();
+    virtual IMoveRequestHandler* ConstructMoveRequestHandler() ORTHANC_OVERRIDE;
 
     IJob* UnserializeJob(const std::string& type,
                          const Json::Value& value);
@@ -334,13 +338,22 @@
     void RefreshMetrics();
 
     // New in Orthanc 1.5.7
-    virtual bool CreateChunkedRequestReader(std::auto_ptr<IChunkedRequestReader>& target,
+    virtual bool CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader>& target,
                                             RequestOrigin origin,
                                             const char* remoteIp,
                                             const char* username,
                                             HttpMethod method,
                                             const UriComponents& uri,
-                                            const Arguments& headers);
+                                            const Arguments& headers) ORTHANC_OVERRIDE;
+
+    // New in Orthanc 1.6.0
+    IStorageCommitmentFactory::ILookupHandler* CreateStorageCommitment(
+      const std::string& jobId,
+      const std::string& transactionUid,
+      const std::vector<std::string>& sopClassUids,
+      const std::vector<std::string>& sopInstanceUids,
+      const std::string& remoteAet,
+      const std::string& calledAet) ORTHANC_OVERRIDE;
   };
 }
 
--- a/Plugins/Engine/PluginsEnumerations.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Engine/PluginsEnumerations.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -549,5 +549,36 @@
           throw OrthancException(ErrorCode_ParameterOutOfRange);
       }
     }
+
+
+    StorageCommitmentFailureReason Convert(OrthancPluginStorageCommitmentFailureReason reason)
+    {
+      switch (reason)
+      {
+        case OrthancPluginStorageCommitmentFailureReason_Success:
+          return StorageCommitmentFailureReason_Success;
+          
+        case OrthancPluginStorageCommitmentFailureReason_ProcessingFailure:
+          return StorageCommitmentFailureReason_ProcessingFailure;
+
+        case OrthancPluginStorageCommitmentFailureReason_NoSuchObjectInstance:
+          return StorageCommitmentFailureReason_NoSuchObjectInstance;
+
+        case OrthancPluginStorageCommitmentFailureReason_ResourceLimitation:
+          return StorageCommitmentFailureReason_ResourceLimitation;
+
+        case OrthancPluginStorageCommitmentFailureReason_ReferencedSOPClassNotSupported:
+          return StorageCommitmentFailureReason_ReferencedSOPClassNotSupported;
+
+        case OrthancPluginStorageCommitmentFailureReason_ClassInstanceConflict:
+          return StorageCommitmentFailureReason_ClassInstanceConflict;
+
+        case OrthancPluginStorageCommitmentFailureReason_DuplicateTransactionUID:
+          return StorageCommitmentFailureReason_DuplicateTransactionUID;
+             
+        default:
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+    }
   }
 }
--- a/Plugins/Engine/PluginsEnumerations.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Engine/PluginsEnumerations.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -79,6 +79,8 @@
     OrthancPluginJobStepStatus Convert(JobStepCode step);
 
     JobStepCode Convert(OrthancPluginJobStepStatus step);
+
+    StorageCommitmentFailureReason Convert(OrthancPluginStorageCommitmentFailureReason reason);
   }
 }
 
--- a/Plugins/Engine/PluginsErrorDictionary.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Engine/PluginsErrorDictionary.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -69,7 +69,7 @@
                                                           uint16_t httpStatus,
                                                           const char* message)
   {
-    std::auto_ptr<Error> error(new Error);
+    std::unique_ptr<Error> error(new Error);
 
     error->pluginName_ = PluginsManager::GetPluginName(library);
     error->pluginCode_ = pluginCode;
--- a/Plugins/Engine/PluginsErrorDictionary.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Engine/PluginsErrorDictionary.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Engine/PluginsJob.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Engine/PluginsJob.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -78,7 +78,7 @@
     parameters_.finalize(parameters_.job);
   }
 
-  JobStepResult PluginsJob::Step()
+  JobStepResult PluginsJob::Step(const std::string& jobId)
   {
     OrthancPluginJobStepStatus status = parameters_.step(parameters_.job);
 
--- a/Plugins/Engine/PluginsJob.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Engine/PluginsJob.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -51,30 +51,30 @@
 
     virtual ~PluginsJob();
 
-    virtual void Start()
+    virtual void Start() ORTHANC_OVERRIDE
     {
     }
     
-    virtual JobStepResult Step();
+    virtual JobStepResult Step(const std::string& jobId) ORTHANC_OVERRIDE;
 
-    virtual void Reset();
+    virtual void Reset() ORTHANC_OVERRIDE;
 
-    virtual void Stop(JobStopReason reason);
+    virtual void Stop(JobStopReason reason) ORTHANC_OVERRIDE;
 
-    virtual float GetProgress();
+    virtual float GetProgress() ORTHANC_OVERRIDE;
 
-    virtual void GetJobType(std::string& target)
+    virtual void GetJobType(std::string& target) ORTHANC_OVERRIDE
     {
       target = type_;
     }
     
-    virtual void GetPublicContent(Json::Value& value);
+    virtual void GetPublicContent(Json::Value& value) ORTHANC_OVERRIDE;
 
-    virtual bool Serialize(Json::Value& value);
+    virtual bool Serialize(Json::Value& value) ORTHANC_OVERRIDE;
 
     virtual bool GetOutput(std::string& output,
                            MimeType& mime,
-                           const std::string& key)
+                           const std::string& key) ORTHANC_OVERRIDE
     {
       // TODO
       return false;
--- a/Plugins/Engine/PluginsManager.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Engine/PluginsManager.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -249,7 +249,7 @@
       return;
     }
 
-    std::auto_ptr<Plugin> plugin(new Plugin(*this, path));
+    std::unique_ptr<Plugin> plugin(new Plugin(*this, path));
 
     if (!IsOrthancPlugin(plugin->GetSharedLibrary()))
     {
--- a/Plugins/Engine/PluginsManager.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Engine/PluginsManager.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Include/orthanc/OrthancCDatabasePlugin.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Include/orthanc/OrthancCDatabasePlugin.h	Thu Mar 19 11:48:30 2020 +0100
@@ -6,7 +6,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Include/orthanc/OrthancCPlugin.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Thu Mar 19 11:48:30 2020 +0100
@@ -26,6 +26,7 @@
  *    - Possibly register a callback to unserialize jobs using OrthancPluginRegisterJobsUnserializer().
  *    - Possibly register a callback to refresh its metrics using OrthancPluginRegisterRefreshMetricsCallback().
  *    - Possibly register a callback to answer chunked HTTP transfers using ::OrthancPluginRegisterChunkedRestCallback().
+ *    - Possibly register a callback for Storage Commitment SCP using ::OrthancPluginRegisterStorageCommitmentScpCallback().
  * -# <tt>void OrthancPluginFinalize()</tt>:
  *    This function is invoked by Orthanc during its shutdown. The plugin
  *    must free all its memory.
@@ -58,7 +59,7 @@
  * @brief Functions to register and manage callbacks by the plugins.
  *
  * @defgroup DicomCallbacks DicomCallbacks
- * @brief Functions to register and manage DICOM callbacks (worklists, C-Find, C-MOVE).
+ * @brief Functions to register and manage DICOM callbacks (worklists, C-FIND, C-MOVE, storage commitment).
  *
  * @defgroup Orthanc Orthanc
  * @brief Functions to access the content of the Orthanc server.
@@ -77,7 +78,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -122,16 +123,16 @@
 #endif
 
 #define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
-#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     5
-#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  7
+#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     6
+#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  0
 
 
 #if !defined(ORTHANC_PLUGINS_VERSION_IS_ABOVE)
-#define ORTHANC_PLUGINS_VERSION_IS_ABOVE(major, minor, revision) \
-  (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER > major ||               \
-   (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER == major &&             \
-    (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER > minor ||             \
-     (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER == minor &&           \
+#define ORTHANC_PLUGINS_VERSION_IS_ABOVE(major, minor, revision)        \
+  (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER > major ||                      \
+   (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER == major &&                    \
+    (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER > minor ||                    \
+     (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER == minor &&                  \
       ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER >= revision))))
 #endif
 
@@ -301,6 +302,7 @@
     OrthancPluginErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
     OrthancPluginErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
     OrthancPluginErrorCode_AlreadyExistingTag = 2042    /*!< Cannot override the value of a tag that already exists */,
+    OrthancPluginErrorCode_NoStorageCommitmentHandler = 2043    /*!< No request handler factory for DICOM N-ACTION SCP (storage commitment) */,
     OrthancPluginErrorCode_UnsupportedMediaType = 3000    /*!< Unsupported media type */,
 
     _OrthancPluginErrorCode_INTERNAL = 0x7fffffff
@@ -450,6 +452,7 @@
     _OrthancPluginService_RegisterIncomingHttpRequestFilter2 = 1010,
     _OrthancPluginService_RegisterRefreshMetricsCallback = 1011,
     _OrthancPluginService_RegisterChunkedRestCallback = 1012,  /* New in Orthanc 1.5.7 */
+    _OrthancPluginService_RegisterStorageCommitmentScpCallback = 1013,
 
     /* Sending answers to REST calls */
     _OrthancPluginService_AnswerBuffer = 2000,
@@ -909,14 +912,14 @@
    **/
   typedef enum
   {
-    OrthancPluginMetricsType_Default,   /*!< Default metrics */
+    OrthancPluginMetricsType_Default = 0,   /*!< Default metrics */
 
     /**
      * This metrics represents a time duration. Orthanc will keep the
      * maximum value of the metrics over a sliding window of ten
      * seconds, which is useful if the metrics is sampled frequently.
      **/
-    OrthancPluginMetricsType_Timer
+    OrthancPluginMetricsType_Timer = 1
   } OrthancPluginMetricsType;
   
 
@@ -926,11 +929,47 @@
    **/
   typedef enum
   {
-    OrthancPluginDicomWebBinaryMode_Ignore,        /*!< Don't include binary tags */
-    OrthancPluginDicomWebBinaryMode_InlineBinary,  /*!< Inline encoding using Base64 */
-    OrthancPluginDicomWebBinaryMode_BulkDataUri    /*!< Use a bulk data URI field */
+    OrthancPluginDicomWebBinaryMode_Ignore = 0,        /*!< Don't include binary tags */
+    OrthancPluginDicomWebBinaryMode_InlineBinary = 1,  /*!< Inline encoding using Base64 */
+    OrthancPluginDicomWebBinaryMode_BulkDataUri = 2    /*!< Use a bulk data URI field */
   } OrthancPluginDicomWebBinaryMode;
 
+
+  /**
+   * The available values for the Failure Reason (0008,1197) during
+   * storage commitment.
+   * http://dicom.nema.org/medical/dicom/2019e/output/chtml/part03/sect_C.14.html#sect_C.14.1.1
+   **/
+  typedef enum
+  {
+    OrthancPluginStorageCommitmentFailureReason_Success = 0,
+    /*!< Success: The DICOM instance is properly stored in the SCP */
+
+    OrthancPluginStorageCommitmentFailureReason_ProcessingFailure = 1,
+    /*!< 0110H: A general failure in processing the operation was encountered */
+
+    OrthancPluginStorageCommitmentFailureReason_NoSuchObjectInstance = 2,
+    /*!< 0112H: One or more of the elements in the Referenced SOP
+      Instance Sequence was not available */
+
+    OrthancPluginStorageCommitmentFailureReason_ResourceLimitation = 3,
+    /*!< 0213H: The SCP does not currently have enough resources to
+      store the requested SOP Instance(s) */
+
+    OrthancPluginStorageCommitmentFailureReason_ReferencedSOPClassNotSupported = 4,
+    /*!< 0122H: Storage Commitment has been requested for a SOP
+      Instance with a SOP Class that is not supported by the SCP */
+
+    OrthancPluginStorageCommitmentFailureReason_ClassInstanceConflict = 5,
+    /*!< 0119H: The SOP Class of an element in the Referenced SOP
+      Instance Sequence did not correspond to the SOP class registered
+      for this SOP Instance at the SCP */
+
+    OrthancPluginStorageCommitmentFailureReason_DuplicateTransactionUID = 6
+    /*!< 0131H: The Transaction UID of the Storage Commitment Request
+      is already in use */
+  } OrthancPluginStorageCommitmentFailureReason;
+
   
 
   /**
@@ -1148,6 +1187,12 @@
    * @param type The content type corresponding to this file. 
    * @return 0 if success, other value if error.
    * @ingroup Callbacks
+   * 
+   * @warning The "content" buffer *must* have been allocated using
+   * the "malloc()" function of your C standard library (i.e. nor
+   * "new[]", neither a pointer to a buffer). The "free()" function of
+   * your C standard library will automatically be invoked on the
+   * "content" pointer.
    **/
   typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) (
     void** content,
@@ -1539,7 +1584,7 @@
    * "levelTagElement", and "levelIndex" arrays.
    * @param levelTagGroup The group of the parent DICOM tags in the hierarchy.
    * @param levelTagElement The element of the parent DICOM tags in the hierarchy.
-   * @param levelIndex The index of the node in the parent sequences of the hiearchy.
+   * @param levelIndex The index of the node in the parent sequences of the hierarchy.
    * @param tagGroup The group of the DICOM tag of interest.
    * @param tagElement The element of the DICOM tag of interest.
    * @param vr The value representation of the binary DICOM node.
@@ -1652,7 +1697,8 @@
         sizeof(int32_t) != sizeof(OrthancPluginJobStepStatus) ||
         sizeof(int32_t) != sizeof(OrthancPluginConstraintType) ||
         sizeof(int32_t) != sizeof(OrthancPluginMetricsType) ||
-        sizeof(int32_t) != sizeof(OrthancPluginDicomWebBinaryMode))
+        sizeof(int32_t) != sizeof(OrthancPluginDicomWebBinaryMode) ||
+        sizeof(int32_t) != sizeof(OrthancPluginStorageCommitmentFailureReason))
     {
       /* Mismatch in the size of the enumerations */
       return 0;
@@ -2880,7 +2926,10 @@
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
    * @param instance The instance of interest.
    * @param metadata The metadata of interest.
-   * @return The metadata value if success, NULL if error.
+   * @return The metadata value if success, NULL if error. Please note that the 
+   *         returned string belongs to the instance object and must NOT be 
+   *         deallocated. Please make a copy of the string if you wish to access 
+   *         it later.
    * @ingroup Callbacks
    **/
   ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata(
@@ -7250,6 +7299,117 @@
   }
 
 
+
+  /**
+   * @brief Callback executed by the storage commitment SCP.
+   *
+   * Signature of a factory function that creates an object to handle
+   * one incoming storage commitment request.
+   *
+   * @remark The factory receives the list of the SOP class/instance
+   * UIDs of interest to the remote storage commitment SCU. This gives
+   * the factory the possibility to start some prefetch process
+   * upfront in the background, before the handler object is actually
+   * queried about the status of these DICOM instances.
+   *
+   * @param handler Output variable where the factory puts the handler object it created.
+   * @param jobId ID of the Orthanc job that is responsible for handling 
+   * the storage commitment request. This job will successively look for the
+   * status of all the individual queried DICOM instances.
+   * @param transactionUid UID of the storage commitment transaction
+   * provided by the storage commitment SCU. It contains the value of the
+   * (0008,1195) DICOM tag.
+   * @param sopClassUids Array of the SOP class UIDs (0008,0016) that are queried by the SCU.
+   * @param sopInstanceUids Array of the SOP instance UIDs (0008,0018) that are queried by the SCU.
+   * @param countInstances Number of DICOM instances that are queried. This is the size
+   * of the `sopClassUids` and `sopInstanceUids` arrays.
+   * @param remoteAet The AET of the storage commitment SCU.
+   * @param calledAet The AET used by the SCU to contact the storage commitment SCP (i.e. Orthanc).
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageCommitmentFactory) (
+    void**              handler /* out */,
+    const char*         jobId,
+    const char*         transactionUid,
+    const char* const*  sopClassUids,
+    const char* const*  sopInstanceUids,
+    uint32_t            countInstances,
+    const char*         remoteAet,
+    const char*         calledAet);
+
+  
+  /**
+   * @brief Callback to free one storage commitment SCP handler.
+   * 
+   * Signature of a callback function that releases the resources
+   * allocated by the factory of the storage commitment SCP. The
+   * handler is the return value of a previous call to the
+   * OrthancPluginStorageCommitmentFactory() callback.
+   *
+   * @param handler The handler object to be destructed.
+   * @ingroup DicomCallbacks
+   **/
+  typedef void (*OrthancPluginStorageCommitmentDestructor) (void* handler);
+
+
+  /**
+   * @brief Callback to get the status of one DICOM instance in the
+   * storage commitment SCP.
+   *
+   * Signature of a callback function that is successively invoked for
+   * each DICOM instance that is queried by the remote storage
+   * commitment SCU.  The function must be tought of as a method of
+   * the handler object that was created by a previous call to the
+   * OrthancPluginStorageCommitmentFactory() callback. After each call
+   * to this method, the progress of the associated Orthanc job is
+   * updated.
+   * 
+   * @param target Output variable where to put the status for the queried instance.
+   * @param handler The handler object associated with this storage commitment request.
+   * @param sopClassUid The SOP class UID (0008,0016) of interest.
+   * @param sopInstanceUid The SOP instance UID (0008,0018) of interest.
+   * @ingroup DicomCallbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageCommitmentLookup) (
+    OrthancPluginStorageCommitmentFailureReason* target,
+    void* handler,
+    const char* sopClassUid,
+    const char* sopInstanceUid);
+    
+    
+  typedef struct
+  {
+    OrthancPluginStorageCommitmentFactory     factory;
+    OrthancPluginStorageCommitmentDestructor  destructor;
+    OrthancPluginStorageCommitmentLookup      lookup;
+  } _OrthancPluginRegisterStorageCommitmentScpCallback;
+
+  /**
+   * @brief Register a callback to handle incoming requests to the storage commitment SCP.
+   *
+   * This function registers a callback to handle storage commitment SCP requests.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param factory Factory function that creates the handler object
+   * for incoming storage commitment requests.
+   * @param destructor Destructor function to destroy the handler object.
+   * @param lookup Callback method to get the status of one DICOM instance.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageCommitmentScpCallback(
+    OrthancPluginContext*                     context,
+    OrthancPluginStorageCommitmentFactory     factory,
+    OrthancPluginStorageCommitmentDestructor  destructor,
+    OrthancPluginStorageCommitmentLookup      lookup)
+  {
+    _OrthancPluginRegisterStorageCommitmentScpCallback params;
+    params.factory = factory;
+    params.destructor = destructor;
+    params.lookup = lookup;
+    context->InvokeService(context, _OrthancPluginService_RegisterStorageCommitmentScpCallback, &params);
+  }
   
 #ifdef  __cplusplus
 }
--- a/Plugins/Samples/AutomatedJpeg2kCompression/Plugin.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/AutomatedJpeg2kCompression/Plugin.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Basic/Plugin.c	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/Basic/Plugin.c	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -47,7 +47,7 @@
     return OrthancPluginErrorCode_ParameterOutOfRange;
   }
   
-  sprintf(buffer, "Callback on URL [%s] with body [%s]\n", url, request->body);
+  sprintf(buffer, "Callback on URL [%s] with body [%s]\n", url, (const char*) request->body);
   OrthancPluginLogWarning(context, buffer);
 
   OrthancPluginSetCookie(context, output, "hello", "world");
--- a/Plugins/Samples/Common/DicomDatasetReader.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/Common/DicomDatasetReader.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/DicomDatasetReader.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/Common/DicomDatasetReader.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/DicomPath.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/Common/DicomPath.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -35,6 +35,8 @@
 
 #include "OrthancPluginException.h"
 
+#include <boost/lexical_cast.hpp>
+
 namespace OrthancPlugins
 {
   const DicomPath::Prefix& DicomPath::GetPrefixItem(size_t depth) const
@@ -97,4 +99,18 @@
     AddToPrefix(sequence2, index2);
     AddToPrefix(sequence3, index3);
   }
+
+
+  std::string DicomPath::Format() const
+  {
+    std::string s;
+      
+    for (size_t i = 0; i < GetPrefixLength(); i++)
+    {
+      s += (GetPrefixTag(i).FormatHexadecimal() + " / " +
+            boost::lexical_cast<std::string>(i) + " / ");
+    }
+
+    return s + GetFinalTag().FormatHexadecimal();
+  }
 }
--- a/Plugins/Samples/Common/DicomPath.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/Common/DicomPath.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -112,5 +112,7 @@
     {
       finalTag_ = tag;
     }
+
+    std::string Format() const;
   };
 }
--- a/Plugins/Samples/Common/DicomTag.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/Common/DicomTag.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -108,4 +108,12 @@
       ORTHANC_PLUGINS_THROW_EXCEPTION(NotImplemented);
     }
   }
+
+
+  std::string DicomTag::FormatHexadecimal() const
+  {
+    char buf[16];
+    sprintf(buf, "(%04x,%04x)", group_, element_);
+    return buf;
+  }
 }
--- a/Plugins/Samples/Common/DicomTag.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/Common/DicomTag.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -34,6 +34,7 @@
 #pragma once
 
 #include <stdint.h>
+#include <string>
 
 namespace OrthancPlugins
 {
@@ -74,6 +75,8 @@
     {
       return !(*this == other);
     }
+
+    std::string FormatHexadecimal() const;
   };
 
 
--- a/Plugins/Samples/Common/FullOrthancDataset.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/Common/FullOrthancDataset.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/FullOrthancDataset.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/Common/FullOrthancDataset.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/IDicomDataset.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/Common/IDicomDataset.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/IOrthancConnection.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/Common/IOrthancConnection.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/IOrthancConnection.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/Common/IOrthancConnection.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/OrthancHttpConnection.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/Common/OrthancHttpConnection.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/OrthancHttpConnection.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/Common/OrthancHttpConnection.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/OrthancPluginConnection.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/Common/OrthancPluginConnection.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/OrthancPluginConnection.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/Common/OrthancPluginConnection.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -33,8 +33,9 @@
 
 #include "OrthancPluginCppWrapper.h"
 
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/move/unique_ptr.hpp>
 #include <boost/thread.hpp>
-#include <boost/algorithm/string/predicate.hpp>
 #include <json/reader.h>
 #include <json/writer.h>
 
@@ -2159,6 +2160,109 @@
     }
   }
 
+
+  void OrthancJob::SubmitFromRestApiPost(OrthancPluginRestOutput* output,
+                                         const Json::Value& body,
+                                         OrthancJob* job)
+  {
+    static const char* KEY_SYNCHRONOUS = "Synchronous";
+    static const char* KEY_ASYNCHRONOUS = "Asynchronous";
+    static const char* KEY_PRIORITY = "Priority";
+
+    boost::movelib::unique_ptr<OrthancJob> protection(job);
+  
+    if (body.type() != Json::objectValue)
+    {
+#if HAS_ORTHANC_EXCEPTION == 1
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                      "Expected a JSON object in the body");
+#else
+      LogError("Expected a JSON object in the body");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+#endif
+    }
+
+    bool synchronous = true;
+  
+    if (body.isMember(KEY_SYNCHRONOUS))
+    {
+      if (body[KEY_SYNCHRONOUS].type() != Json::booleanValue)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                        "Option \"" + std::string(KEY_SYNCHRONOUS) +
+                                        "\" must be Boolean");
+#else
+        LogError("Option \"" + std::string(KEY_SYNCHRONOUS) + "\" must be Boolean");
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+#endif
+      }
+      else
+      {
+        synchronous = body[KEY_SYNCHRONOUS].asBool();
+      }
+    }
+
+    if (body.isMember(KEY_ASYNCHRONOUS))
+    {
+      if (body[KEY_ASYNCHRONOUS].type() != Json::booleanValue)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                        "Option \"" + std::string(KEY_ASYNCHRONOUS) +
+                                        "\" must be Boolean");
+#else
+        LogError("Option \"" + std::string(KEY_ASYNCHRONOUS) + "\" must be Boolean");
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+#endif
+      }
+      else
+      {
+        synchronous = !body[KEY_ASYNCHRONOUS].asBool();
+      }
+    }
+
+    int priority = 0;
+
+    if (body.isMember(KEY_PRIORITY))
+    {
+      if (body[KEY_PRIORITY].type() != Json::booleanValue)
+      {
+#if HAS_ORTHANC_EXCEPTION == 1
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
+                                        "Option \"" + std::string(KEY_PRIORITY) +
+                                        "\" must be an integer");
+#else
+        LogError("Option \"" + std::string(KEY_PRIORITY) + "\" must be an integer");
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+#endif
+      }
+      else
+      {
+        priority = !body[KEY_PRIORITY].asInt();
+      }
+    }
+  
+    Json::Value result;
+
+    if (synchronous)
+    {
+      OrthancPlugins::OrthancJob::SubmitAndWait(result, protection.release(), priority);
+    }
+    else
+    {
+      std::string id = OrthancPlugins::OrthancJob::Submit(protection.release(), priority);
+
+      result = Json::objectValue;
+      result["ID"] = id;
+      result["Path"] = "/jobs/" + id;
+    }
+
+    std::string s = result.toStyledString();
+    OrthancPluginAnswerBuffer(OrthancPlugins::GetGlobalContext(), output, s.c_str(),
+                              s.size(), "application/json");
+  }
+
 #endif
 
 
@@ -2956,7 +3060,7 @@
             }
             else
             {
-              std::auto_ptr<IChunkedRequestReader> reader(PostHandler(url, request));
+              boost::movelib::unique_ptr<IChunkedRequestReader> reader(PostHandler(url, request));
               if (reader.get() == NULL)
               {
                 ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
@@ -2989,7 +3093,7 @@
             }
             else
             {
-              std::auto_ptr<IChunkedRequestReader> reader(PutHandler(url, request));
+              boost::movelib::unique_ptr<IChunkedRequestReader> reader(PutHandler(url, request));
               if (reader.get() == NULL)
               {
                 ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
@@ -3036,4 +3140,41 @@
     }
 #endif
   }
+
+
+#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
+  OrthancPluginErrorCode IStorageCommitmentScpHandler::Lookup(
+    OrthancPluginStorageCommitmentFailureReason* target,
+    void* rawHandler,
+    const char* sopClassUid,
+    const char* sopInstanceUid)
+  {
+    assert(target != NULL &&
+           rawHandler != NULL);
+      
+    try
+    {
+      IStorageCommitmentScpHandler& handler = *reinterpret_cast<IStorageCommitmentScpHandler*>(rawHandler);
+      *target = handler.Lookup(sopClassUid, sopInstanceUid);
+      return OrthancPluginErrorCode_Success;
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+    {
+      return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+    }
+    catch (...)
+    {
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
+  void IStorageCommitmentScpHandler::Destructor(void* rawHandler)
+  {
+    assert(rawHandler != NULL);
+    delete reinterpret_cast<IStorageCommitmentScpHandler*>(rawHandler);
+  }
+#endif
 }
--- a/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -103,6 +103,12 @@
 #  define HAS_ORTHANC_PLUGIN_CHUNKED_HTTP_SERVER  0
 #endif
 
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 6, 0)
+#  define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP  1
+#else
+#  define HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP  0
+#endif
+
 
 
 namespace OrthancPlugins
@@ -778,6 +784,13 @@
     static void SubmitAndWait(Json::Value& result,
                               OrthancJob* job /* takes ownership */,
                               int priority);
+
+    // Submit a job from a POST on the REST API with the same
+    // conventions as in the Orthanc core (according to the
+    // "Synchronous" and "Priority" options)
+    static void SubmitFromRestApiPost(OrthancPluginRestOutput* output,
+                                      const Json::Value& body,
+                                      OrthancJob* job);
   };
 #endif
 
@@ -1093,4 +1106,26 @@
 #endif
     }
   };
+
+  
+
+#if HAS_ORTHANC_PLUGIN_STORAGE_COMMITMENT_SCP == 1
+  class IStorageCommitmentScpHandler : public boost::noncopyable
+  {
+  public:
+    virtual ~IStorageCommitmentScpHandler()
+    {
+    }
+    
+    virtual OrthancPluginStorageCommitmentFailureReason Lookup(const std::string& sopClassUid,
+                                                               const std::string& sopInstanceUid) = 0;
+    
+    static OrthancPluginErrorCode Lookup(OrthancPluginStorageCommitmentFailureReason* target,
+                                         void* rawHandler,
+                                         const char* sopClassUid,
+                                         const char* sopInstanceUid);
+
+    static void Destructor(void* rawHandler);
+  };
+#endif
 }
--- a/Plugins/Samples/Common/OrthancPluginException.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/Common/OrthancPluginException.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/OrthancPlugins.cmake	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/Common/OrthancPlugins.cmake	Thu Mar 19 11:48:30 2020 +0100
@@ -20,13 +20,18 @@
   link_libraries(dl rt pthread)
 endif()
 
-
 include_directories(${SAMPLES_ROOT}/../Include/)
 
-
 if (MSVC)
-  include_directories(${SAMPLES_ROOT}/../../Resources/ThirdParty/VisualStudio/)
+  if (MSVC_VERSION LESS 1600)
+  # Starting with Visual Studio >= 2010 (i.e. macro _MSC_VER >=
+  # 1600), Microsoft ships a standard-compliant <stdint.h>
+  # header. For earlier versions of Visual Studio, give access to a
+  # compatibility header.
+  # http://stackoverflow.com/a/70630/881731
+  # https://en.wikibooks.org/wiki/C_Programming/C_Reference/stdint.h#External_links
+    include_directories(${SAMPLES_ROOT}/../../Resources/ThirdParty/VisualStudio/)
+  endif()
 endif()
 
-
 add_definitions(-DHAS_ORTHANC_EXCEPTION=0)
--- a/Plugins/Samples/Common/SimplifiedOrthancDataset.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/Common/SimplifiedOrthancDataset.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/Common/SimplifiedOrthancDataset.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/Common/SimplifiedOrthancDataset.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/ConnectivityChecks/CMakeLists.txt	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,65 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(ConnectivityChecks)
+
+SET(PLUGIN_NAME "connectivity-checks" CACHE STRING "Name of the plugin")
+SET(PLUGIN_VERSION "mainline" CACHE STRING "Version of the plugin")
+
+include(${CMAKE_CURRENT_SOURCE_DIR}/../../../Resources/CMake/OrthancFrameworkParameters.cmake)
+include(${CMAKE_CURRENT_SOURCE_DIR}/../../../Resources/CMake/OrthancFrameworkConfiguration.cmake)
+
+include(JavaScriptLibraries.cmake)
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  execute_process(
+    COMMAND 
+    ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py
+    ${PLUGIN_VERSION} ConnectivityChecks ConnectivityChecks.dll "Orthanc plugin to serve additional folders"
+    ERROR_VARIABLE Failure
+    OUTPUT_FILE ${AUTOGENERATED_DIR}/ConnectivityChecks.rc
+    )
+
+  if (Failure)
+    message(FATAL_ERROR "Error while computing the version information: ${Failure}")
+  endif()
+
+  list(APPEND ADDITIONAL_RESOURCES ${AUTOGENERATED_DIR}/ConnectivityChecks.rc)
+endif()  
+
+EmbedResources(
+  WEB_RESOURCES  ${CMAKE_CURRENT_SOURCE_DIR}/WebResources
+  LIBRARIES      ${JAVASCRIPT_LIBS_DIR}
+  )
+
+add_definitions(
+  -DHAS_ORTHANC_EXCEPTION=1
+  -DORTHANC_ENABLE_LOGGING_PLUGIN=1
+  -DORTHANC_PLUGIN_NAME="${PLUGIN_NAME}"
+  -DORTHANC_PLUGIN_VERSION="${PLUGIN_VERSION}"
+  )
+
+include_directories(
+  ${ORTHANC_ROOT}/Plugins/Include/
+  )
+
+add_library(ConnectivityChecks SHARED
+  ${ADDITIONAL_RESOURCES}
+  ${AUTOGENERATED_SOURCES}
+  ${ORTHANC_CORE_SOURCES_DEPENDENCIES}
+  ${ORTHANC_ROOT}/Core/Enumerations.cpp
+  ${ORTHANC_ROOT}/Core/Logging.cpp
+  ${ORTHANC_ROOT}/Core/SystemToolbox.cpp
+  ${ORTHANC_ROOT}/Core/Toolbox.cpp
+  Plugin.cpp
+  )
+
+set_target_properties(
+  ConnectivityChecks PROPERTIES 
+  VERSION ${PLUGIN_VERSION} 
+  SOVERSION ${PLUGIN_VERSION}
+  )
+
+install(
+  TARGETS ConnectivityChecks
+  DESTINATION .
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/ConnectivityChecks/JavaScriptLibraries.cmake	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,42 @@
+set(BASE_URL "http://orthanc.osimis.io/ThirdPartyDownloads")
+
+DownloadPackage(
+  "da0189f7c33bf9f652ea65401e0a3dc9"
+  "${BASE_URL}/dicom-web/bootstrap-4.3.1.zip"
+  "${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1")
+
+DownloadPackage(
+  "8242afdc5bd44105d9dc9e6535315484"
+  "${BASE_URL}/dicom-web/vuejs-2.6.10.tar.gz"
+  "${CMAKE_CURRENT_BINARY_DIR}/vue-2.6.10")
+
+DownloadPackage(
+  "3e2b4e1522661f7fcf8ad49cb933296c"
+  "${BASE_URL}/dicom-web/axios-0.19.0.tar.gz"
+  "${CMAKE_CURRENT_BINARY_DIR}/axios-0.19.0")
+
+DownloadFile(
+  "220afd743d9e9643852e31a135a9f3ae"
+  "${BASE_URL}/jquery-3.4.1.min.js")
+
+
+set(JAVASCRIPT_LIBS_DIR  ${CMAKE_CURRENT_BINARY_DIR}/javascript-libs)
+file(MAKE_DIRECTORY ${JAVASCRIPT_LIBS_DIR})
+
+file(COPY
+  ${CMAKE_CURRENT_BINARY_DIR}/axios-0.19.0/dist/axios.min.js
+  ${CMAKE_CURRENT_BINARY_DIR}/axios-0.19.0/dist/axios.min.map
+  ${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1/dist/js/bootstrap.min.js
+  ${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1/dist/js/bootstrap.min.js.map
+  ${CMAKE_CURRENT_BINARY_DIR}/vue-2.6.10/dist/vue.min.js
+  ${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/jquery-3.4.1.min.js
+  DESTINATION
+  ${JAVASCRIPT_LIBS_DIR}/js
+  )
+
+file(COPY
+  ${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1/dist/css/bootstrap.min.css
+  ${CMAKE_CURRENT_BINARY_DIR}/bootstrap-4.3.1/dist/css/bootstrap.min.css.map
+  DESTINATION
+  ${JAVASCRIPT_LIBS_DIR}/css
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/ConnectivityChecks/Plugin.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,124 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include <EmbeddedResources.h>
+#include <orthanc/OrthancCPlugin.h>
+
+#include "../../../Core/OrthancException.h"
+#include "../../../Core/SystemToolbox.h"
+
+#define ROOT_URI "/connectivity-checks"
+
+
+static OrthancPluginContext* context_ = NULL;
+
+
+template <Orthanc::EmbeddedResources::DirectoryResourceId DIRECTORY>
+static OrthancPluginErrorCode ServeStaticResource(OrthancPluginRestOutput* output,
+                                                  const char* url,
+                                                  const OrthancPluginHttpRequest* request)
+{
+  if (request->method != OrthancPluginHttpMethod_Get)
+  {
+    OrthancPluginSendMethodNotAllowed(context_, output, "GET");
+    return OrthancPluginErrorCode_Success;
+  }
+
+  std::string path = "/" + std::string(request->groups[0]);
+  std::string mime = Orthanc::EnumerationToString(Orthanc::SystemToolbox::AutodetectMimeType(path));
+
+  try
+  {
+    std::string s;
+    Orthanc::EmbeddedResources::GetDirectoryResource(s, DIRECTORY, path.c_str());
+
+    const char* resource = s.size() ? s.c_str() : NULL;
+    OrthancPluginAnswerBuffer(context_, output, resource, s.size(), mime.c_str());
+  }
+  catch (Orthanc::OrthancException&)
+  {
+    std::string s = "Unknown static resource in plugin: " + std::string(request->groups[0]);
+    OrthancPluginLogError(context_, s.c_str());
+    OrthancPluginSendHttpStatusCode(context_, output, 404);
+  }
+
+  return OrthancPluginErrorCode_Success;
+}
+
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c)
+  {
+    context_ = c;
+    
+    /* Check the version of the Orthanc core */
+    if (OrthancPluginCheckVersion(c) == 0)
+    {
+      char info[256];
+      sprintf(info, "Your version of Orthanc (%s) must be above %d.%d.%d to run this plugin",
+              c->orthancVersion,
+              ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
+              ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
+      OrthancPluginLogError(context_, info);
+      return -1;
+    }
+
+    /* Register the callbacks */
+    OrthancPluginSetDescription(context_, "Utilities to check connectivity to DICOM modalities, DICOMweb servers and Orthanc peers.");
+    OrthancPluginSetRootUri(context_, ROOT_URI "/app/index.html");
+    OrthancPluginRegisterRestCallback(context_, ROOT_URI "/libs/(.*)", ServeStaticResource<Orthanc::EmbeddedResources::LIBRARIES>);
+    OrthancPluginRegisterRestCallback(context_, ROOT_URI "/app/(.*)", ServeStaticResource<Orthanc::EmbeddedResources::WEB_RESOURCES>);
+ 
+    return 0;
+  }
+
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return ORTHANC_PLUGIN_NAME;
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return ORTHANC_PLUGIN_VERSION;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/ConnectivityChecks/WebResources/app.js	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,145 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+new Vue({
+  el: '#app',
+  data: {
+    dicomNodes: {},
+    peers: [],
+    canTestPeers: false,
+    dicomWebServers: []
+  },
+  methods: {
+    toggle: function (todo) {
+      todo.done = !todo.done
+    },
+
+    testDicomModalities: function () {
+      console.log('testing DICOM modalities');
+      axios
+        .get('../../modalities?expand')
+        .then(response => {
+          this.dicomNodes = response.data;
+          for (let alias of Object.keys(this.dicomNodes)) {
+            this.dicomNodes[alias]['alias'] = alias;
+            this.dicomNodes[alias]['status'] = 'testing';
+            axios
+              .post('../../modalities/' + alias + '/echo')
+              .then(response => {
+                this.dicomNodes[alias]['status'] = 'ok';
+                this.$forceUpdate();
+              })
+              .catch(response => {
+                this.dicomNodes[alias]['status'] = 'ko';
+                this.$forceUpdate();
+              })
+                }
+        })
+    },
+
+    testOrthancPeers: function () {
+      console.log('testing Orthanc peers');
+      axios
+        .get('../../peers?expand')
+        .then(response => {
+          this.peers = response.data;
+          for (let alias of Object.keys(this.peers)) {
+            this.peers[alias]['alias'] = alias;
+
+            if (this.canTestPeers) {
+              this.peers[alias]['status'] = 'testing';
+              axios
+                .get('../../peers/' + alias + '/system') // introduced in ApiVersion 5 only !
+                .then(response => {
+                  this.peers[alias]['status'] = 'ok';
+                  this.$forceUpdate();
+                })
+                .catch(response => {
+                  this.peers[alias]['status'] = 'ko';
+                  this.$forceUpdate();
+                })
+                  }
+            else {
+              this.peers[alias]['status'] = 'unknown';
+              this.$forceUpdate();
+            }
+          }
+        })
+    },
+
+    testDicomWebServers: function () {
+      console.log('testing Dicom-web servers');
+      axios
+        .get('../../dicom-web/servers?expand')
+        .then(response => {
+          this.dicomWebServers = response.data;
+          for (let alias of Object.keys(this.dicomWebServers)) {
+            this.dicomWebServers[alias]['alias'] = alias;
+            this.dicomWebServers[alias]['status'] = 'testing';
+
+            // perform a dummy qido-rs to test the connectivity
+            axios
+              .post('../../dicom-web/servers/' + alias + '/qido', {
+                'Uri' : '/studies',
+                'Arguments' : {
+                  '00100010' : 'CONNECTIVITY^CHECKS'
+                }
+              })
+              .then(response => {
+                this.dicomWebServers[alias]['status'] = 'ok';
+                this.$forceUpdate();
+              })
+              .catch(response => {
+                this.dicomWebServers[alias]['status'] = 'ko';
+                this.$forceUpdate();
+              })
+                }
+        })
+    },
+
+  },
+  computed: {
+  },
+  mounted() {
+    axios
+      .get('../../system')
+      .then(response => {
+        this.canTestPeers = response.data.ApiVersion >= 5;
+        this.testDicomModalities();
+        if (this.canTestPeers) {
+          this.testOrthancPeers();
+        }
+        this.testDicomWebServers();
+      })
+  }
+})
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/ConnectivityChecks/WebResources/index.html	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="UTF-8">
+
+    <link rel="stylesheet" href="../libs/css/bootstrap.min.css">
+
+    <title>Orthanc Connectivity checks</title>
+    <link rel="stylesheet" href="style.css" type="text/css">
+  </head>
+
+  <body>
+    <div id="app" class="container-fluid">
+      <h2>DICOM nodes</h2>
+      <table class="table">
+        <thead>
+          <tr>
+            <th scope="col">Alias</th>
+            <th scope="col">AET</th>
+            <th scope="col">Host</th>
+            <th scope="col">Port</th>
+            <th scope="col">Status</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-for="node in dicomNodes">
+            <th scope="row">{{node.alias}}</th>
+            <td>{{node.AET}}</td>
+            <td>{{node.Host}}</td>
+            <td>{{node.Port}}</td>
+            <td v-if="node.status=='ok'" class="connected">Connected</td>
+            <td v-if="node.status=='ko'" class="disconnected">Disconnected</td>
+            <td v-if="node.status=='testing'">
+              <div class="spinner-border" role="status">
+                <span class="sr-only">Testing...</span>
+              </div>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+
+      <h2>Orthanc peers</h2>
+      <table class="table" v-if="canTestPeers">
+        <thead>
+          <tr>
+            <th scope="col">Alias</th>
+            <th scope="col">Url</th>
+            <th scope="col">Status</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-for="node in peers">
+            <th scope="row">{{node.alias}}</th>
+            <td>{{node.Url}}</td>
+            <td v-if="node.status=='ok'" class="connected">Connected</td>
+            <td v-if="node.status=='ko'" class="disconnected">Disconnected</td>
+            <td v-if="node.status=='unknown'" class="unknown">
+              Can not test the peers connectivity with this version of Orthanc
+            </td>
+            <td v-if="node.status=='testing'">
+              <div class="spinner-border" role="status">
+                <span class="sr-only">Testing...</span>
+              </div>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+
+      <h2>DicomWeb servers</h2>
+      <table class="table">
+        <thead>
+          <tr>
+            <th scope="col">Alias</th>
+            <th scope="col">Url</th>
+            <th scope="col">Status</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr v-for="node in dicomWebServers">
+            <th scope="row">{{node.alias}}</th>
+            <td>{{node.Url}}</td>
+            <td v-if="node.status=='ok'" class="connected">Connected</td>
+            <td v-if="node.status=='ko'" class="disconnected">Disconnected</td>
+            <td v-if="node.status=='testing'">
+              <div class="spinner-border" role="status">
+                <span class="sr-only">Testing...</span>
+              </div>
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+
+    <script src="../libs/js/jquery-3.4.1.min.js" type="text/javascript"></script>
+    <script src="../libs/js/bootstrap.min.js" type="text/javascript"></script>
+    <script src="../libs/js/axios.min.js" type="text/javascript"></script>
+    <script src="../libs/js/vue.min.js" type="text/javascript"></script>
+    <script src="app.js" type="text/javascript"></script>
+  </body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/ConnectivityChecks/WebResources/style.css	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,13 @@
+.connected {
+    background-color: darkgreen;
+    color: white;
+}
+
+.disconnected {
+  background-color: darkred;
+    color: white;
+}
+
+.unknown {
+  background-color: gold;
+}
--- a/Plugins/Samples/CustomImageDecoder/Plugin.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/CustomImageDecoder/Plugin.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/GdcmDecoder/GdcmDecoderCache.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/GdcmDecoder/GdcmDecoderCache.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -21,6 +21,7 @@
 
 #include "GdcmDecoderCache.h"
 
+#include "../../../Core/Compatibility.h"
 #include "OrthancImageWrapper.h"
 
 namespace OrthancPlugins
@@ -83,13 +84,13 @@
     }
 
     // This is not the same image
-    std::auto_ptr<GdcmImageDecoder> decoder(new GdcmImageDecoder(dicom, size));
-    std::auto_ptr<OrthancImageWrapper> image(new OrthancImageWrapper(context, decoder->Decode(context, frameIndex)));
+    std::unique_ptr<GdcmImageDecoder> decoder(new GdcmImageDecoder(dicom, size));
+    std::unique_ptr<OrthancImageWrapper> image(new OrthancImageWrapper(context, decoder->Decode(context, frameIndex)));
 
     {
       // Cache the newly created decoder for further use
       boost::mutex::scoped_lock lock(mutex_);
-      decoder_ = decoder;
+      decoder_.reset(decoder.release());
       size_ = size;
       md5_ = md5;
     }
--- a/Plugins/Samples/GdcmDecoder/GdcmDecoderCache.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/GdcmDecoder/GdcmDecoderCache.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -21,6 +21,7 @@
 
 #pragma once
 
+#include "../../../Core/Compatibility.h"
 #include "GdcmImageDecoder.h"
 #include "OrthancImageWrapper.h"
 
@@ -33,7 +34,7 @@
   {
   private:
     boost::mutex   mutex_;
-    std::auto_ptr<OrthancPlugins::GdcmImageDecoder>  decoder_;
+    std::unique_ptr<OrthancPlugins::GdcmImageDecoder>  decoder_;
     size_t       size_;
     std::string  md5_;
 
--- a/Plugins/Samples/GdcmDecoder/GdcmImageDecoder.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/GdcmDecoder/GdcmImageDecoder.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -21,6 +21,7 @@
 
 #include "GdcmImageDecoder.h"
 
+#include "../../../Core/Compatibility.h"
 #include "OrthancImageWrapper.h"
 
 #include <gdcmImageReader.h>
@@ -40,9 +41,9 @@
     size_t                size_;
 
     gdcm::ImageReader reader_;
-    std::auto_ptr<gdcm::ImageApplyLookupTable> lut_;
-    std::auto_ptr<gdcm::ImageChangePhotometricInterpretation> photometric_;
-    std::auto_ptr<gdcm::ImageChangePlanarConfiguration> interleaved_;
+    std::unique_ptr<gdcm::ImageApplyLookupTable> lut_;
+    std::unique_ptr<gdcm::ImageChangePhotometricInterpretation> photometric_;
+    std::unique_ptr<gdcm::ImageChangePlanarConfiguration> interleaved_;
     std::string decoded_;
 
     PImpl(const void* dicom,
--- a/Plugins/Samples/GdcmDecoder/GdcmImageDecoder.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/GdcmDecoder/GdcmImageDecoder.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/GdcmDecoder/OrthancImageWrapper.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/GdcmDecoder/OrthancImageWrapper.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/GdcmDecoder/OrthancImageWrapper.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/GdcmDecoder/OrthancImageWrapper.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/GdcmDecoder/Plugin.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/GdcmDecoder/Plugin.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -19,6 +19,7 @@
  **/
 
 
+#include "../../../Core/Compatibility.h"
 #include "GdcmDecoderCache.h"
 #include "OrthancImageWrapper.h"
 
@@ -35,7 +36,7 @@
 {
   try
   {
-    std::auto_ptr<OrthancPlugins::OrthancImageWrapper> image;
+    std::unique_ptr<OrthancPlugins::OrthancImageWrapper> image;
 
 #if 0
     // Do not use the cache
--- a/Plugins/Samples/ModalityWorklists/Plugin.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/ModalityWorklists/Plugin.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -19,6 +19,7 @@
  **/
 
 
+#include "../../../Core/Compatibility.h"
 #include "../Common/OrthancPluginCppWrapper.h"
 
 #include <boost/filesystem.hpp>
@@ -142,7 +143,7 @@
   try
   {
     // Construct an object to match the worklists in the database against the C-Find query
-    std::auto_ptr<OrthancPlugins::FindMatcher> matcher(CreateMatcher(query, issuerAet));
+    std::unique_ptr<OrthancPlugins::FindMatcher> matcher(CreateMatcher(query, issuerAet));
 
     // Loop over the regular files in the database folder
     namespace fs = boost::filesystem;
--- a/Plugins/Samples/ModalityWorklists/WorklistsDatabase/Generate.py	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/ModalityWorklists/WorklistsDatabase/Generate.py	Thu Mar 19 11:48:30 2020 +0100
@@ -6,7 +6,7 @@
 SOURCE = '/home/jodogne/Downloads/dcmtk-3.6.0/dcmwlm/data/wlistdb/OFFIS/'
 TARGET = os.path.abspath(os.path.dirname(__file__))
 
-for f in os.listdir(SOURCE):
+for f in sorted(os.listdir(SOURCE)):
     ext = os.path.splitext(f)
 
     if ext[1].lower() == '.dump':
--- a/Plugins/Samples/ServeFolders/Plugin.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/ServeFolders/Plugin.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/StorageArea/Plugin.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/StorageArea/Plugin.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/StorageCommitmentScp/CMakeLists.txt	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,37 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(StorageCommitmentScp)
+
+SET(PLUGIN_VERSION "0.0" CACHE STRING "Version of the plugin")
+SET(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)")
+SET(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages")
+
+SET(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp")
+SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of boost")
+
+set(SAMPLES_ROOT ${CMAKE_SOURCE_DIR}/..)
+include(${SAMPLES_ROOT}/Common/OrthancPlugins.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake)
+
+add_library(StorageCommitmentScp SHARED 
+  Plugin.cpp
+  ../Common/OrthancPluginCppWrapper.cpp
+  ${JSONCPP_SOURCES}
+  ${BOOST_SOURCES}
+  )
+
+message("Setting the version of the plugin to ${PLUGIN_VERSION}")
+add_definitions(
+  -DPLUGIN_VERSION="${PLUGIN_VERSION}"
+  )
+
+set_target_properties(StorageCommitmentScp PROPERTIES 
+  VERSION ${PLUGIN_VERSION} 
+  SOVERSION ${PLUGIN_VERSION})
+
+install(
+  TARGETS StorageCommitmentScp
+  RUNTIME DESTINATION lib    # Destination for Windows
+  LIBRARY DESTINATION share/orthanc/plugins    # Destination for Linux
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/StorageCommitmentScp/Plugin.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,116 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "../Common/OrthancPluginCppWrapper.h"
+
+#include <json/value.h>
+#include <json/reader.h>
+
+
+
+class StorageCommitmentSample : public OrthancPlugins::IStorageCommitmentScpHandler
+{
+private:
+  int count_;
+  
+public:
+  StorageCommitmentSample() : count_(0)
+  {
+  }
+  
+  virtual OrthancPluginStorageCommitmentFailureReason Lookup(const std::string& sopClassUid,
+                                                             const std::string& sopInstanceUid)
+  {
+    printf("?? [%s] [%s]\n", sopClassUid.c_str(), sopInstanceUid.c_str());
+    if (count_++ % 2 == 0)
+      return OrthancPluginStorageCommitmentFailureReason_Success;
+    else
+      return OrthancPluginStorageCommitmentFailureReason_NoSuchObjectInstance;
+  }
+};
+
+
+static OrthancPluginErrorCode StorageCommitmentScp(void**              handler /* out */,
+                                                   const char*         jobId,
+                                                   const char*         transactionUid,
+                                                   const char* const*  sopClassUids,
+                                                   const char* const*  sopInstanceUids,
+                                                   uint32_t            countInstances,
+                                                   const char*         remoteAet,
+                                                   const char*         calledAet)
+{
+  /*std::string s;
+    OrthancPlugins::RestApiPost(s, "/jobs/" + std::string(jobId) + "/pause", NULL, 0, false);*/
+  
+  printf("[%s] [%s] [%s] [%s]\n", jobId, transactionUid, remoteAet, calledAet);
+
+  for (uint32_t i = 0; i < countInstances; i++)
+  {
+    printf("++ [%s] [%s]\n", sopClassUids[i], sopInstanceUids[i]);
+  }
+
+  *handler = new StorageCommitmentSample;
+  return OrthancPluginErrorCode_Success;
+}
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c)
+  {
+    OrthancPlugins::SetGlobalContext(c);
+
+    /* Check the version of the Orthanc core */
+    if (OrthancPluginCheckVersion(c) == 0)
+    {
+      OrthancPlugins::ReportMinimalOrthancVersion(ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
+                                                  ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
+                                                  ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
+      return -1;
+    }
+
+    OrthancPluginSetDescription(c, "Sample storage commitment SCP plugin.");
+
+    OrthancPluginRegisterStorageCommitmentScpCallback(
+      c, StorageCommitmentScp,
+      OrthancPlugins::IStorageCommitmentScpHandler::Destructor,
+      OrthancPlugins::IStorageCommitmentScpHandler::Lookup);
+    
+    return 0;
+  }
+
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return "storage-commitment-scp";
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return PLUGIN_VERSION;
+  }
+}
--- a/Plugins/Samples/WebSkeleton/Configuration.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/WebSkeleton/Configuration.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/WebSkeleton/Framework/EmbedResources.py	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/WebSkeleton/Framework/EmbedResources.py	Thu Mar 19 11:48:30 2020 +0100
@@ -1,7 +1,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
@@ -74,6 +74,8 @@
         # The resource is a directory: Recursively explore its files
         content = {}
         for root, dirs, files in os.walk(pathName):
+            dirs.sort()
+            files.sort()
             base = os.path.relpath(root, pathName)
             for f in files:
                 if f.find('~') == -1:  # Ignore Emacs backup files
--- a/Plugins/Samples/WebSkeleton/Framework/Framework.cmake	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/WebSkeleton/Framework/Framework.cmake	Thu Mar 19 11:48:30 2020 +0100
@@ -1,7 +1,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
--- a/Plugins/Samples/WebSkeleton/Framework/Plugin.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Plugins/Samples/WebSkeleton/Framework/Plugin.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/CMake/BoostConfiguration.cmake	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/CMake/BoostConfiguration.cmake	Thu Mar 19 11:48:30 2020 +0100
@@ -12,7 +12,7 @@
   endif()
 
   list(APPEND ORTHANC_BOOST_COMPONENTS filesystem thread system date_time regex)
-  find_package(Boost COMPONENTS "${ORTHANC_BOOST_COMPONENTS}")
+  find_package(Boost COMPONENTS ${ORTHANC_BOOST_COMPONENTS})
 
   if (NOT Boost_FOUND)
     foreach (item ${ORTHANC_BOOST_COMPONENTS})
@@ -30,9 +30,20 @@
     message(FATAL_ERROR "Unable to locate Boost on this system")
   endif()
 
+  
+  # Patch by xnox to fix issue #166 (CMake find_boost version is now
+  # broken with newer boost/cmake)
+  # https://bitbucket.org/sjodogne/orthanc/issues/166/
+  if (POLICY CMP0093)
+    set(BOOST144 1.44)
+  else()
+    set(BOOST144 104400)
+  endif()
+  
+  
   # Boost releases 1.44 through 1.47 supply both V2 and V3 filesystem
   # http://www.boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/index.htm
-  if (${Boost_VERSION} LESS 104400)
+  if (${Boost_VERSION} LESS ${BOOST144})
     add_definitions(
       -DBOOST_HAS_FILESYSTEM_V3=0
       )
@@ -107,6 +118,13 @@
     -DBOOST_REGEX_NO_LIB
     -DBOOST_SYSTEM_NO_LIB
     -DBOOST_LOCALE_NO_LIB
+
+    # In static builds, explicitly prevent Boost from using the system
+    # locale in lexical casts. This is notably important if
+    # "boost::lexical_cast<double>()" is applied to strings containing
+    # "," instead of "." as decimal separators. Check out function
+    # "OrthancStone::LinearAlgebra::ParseVector()".
+    -DBOOST_LEXICAL_CAST_ASSUME_C_LOCALE
     )
 
   set(BOOST_SOURCES
--- a/Resources/CMake/CivetwebConfiguration.cmake	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/CMake/CivetwebConfiguration.cmake	Thu Mar 19 11:48:30 2020 +0100
@@ -30,6 +30,12 @@
     ${CIVETWEB_SOURCES_DIR}/src/civetweb.c
     )
 
+  # New in Orthanc 1.6.0: Enable support of compression in civetweb
+  set_source_files_properties(
+    ${CIVETWEB_SOURCES}
+    PROPERTIES COMPILE_DEFINITIONS
+    "USE_ZLIB=1")
+  
   if (ENABLE_SSL)
     add_definitions(
       -DNO_SSL_DL=1
--- a/Resources/CMake/Compiler.cmake	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/CMake/Compiler.cmake	Thu Mar 19 11:48:30 2020 +0100
@@ -7,6 +7,15 @@
   SET(STANDALONE_BUILD ON)
 endif()
 
+
+if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+  # Cache the environment variables "LSB_CC" and "LSB_CXX" for further
+  # use by "ExternalProject" in CMake
+  SET(CMAKE_LSB_CC $ENV{LSB_CC} CACHE STRING "")
+  SET(CMAKE_LSB_CXX $ENV{LSB_CXX} CACHE STRING "")
+endif()
+
+
 if (CMAKE_COMPILER_IS_GNUCXX)
   set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wno-long-long")
 
@@ -163,15 +172,21 @@
     SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${MINGW_NO_WARNINGS} -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast")
     SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MINGW_NO_WARNINGS}")
 
-    # This is a patch for MinGW64
-    SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++")
-    SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++")
+    if (DYNAMIC_MINGW_STDLIB)
+    else()
+      # This is a patch for MinGW64
+      SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++")
+      SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++")
+    endif()
 
     CHECK_LIBRARY_EXISTS(winpthread pthread_create "" HAVE_WIN_PTHREAD)
     if (HAVE_WIN_PTHREAD)
-      # This line is necessary to compile with recent versions of MinGW,
-      # otherwise "libwinpthread-1.dll" is not statically linked.
-      SET(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic")
+      if (DYNAMIC_MINGW_STDLIB)
+      else()
+        # This line is necessary to compile with recent versions of MinGW,
+        # otherwise "libwinpthread-1.dll" is not statically linked.
+        SET(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic")
+      endif()
       add_definitions(-DHAVE_WIN_PTHREAD=1)
     else()
       add_definitions(-DHAVE_WIN_PTHREAD=0)
@@ -193,12 +208,15 @@
   # zero (and similar conditions like integer overflows) are
   # encountered: The "clamp" mode avoids throwing errors, as they
   # cannot be properly catched by "try {} catch (...)" constructions.
-  if (EMSCRIPTEN_SET_LLVM_WASM_BACKEND)
-    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'")
-  else()
-    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]' -s BINARYEN_TRAP_MODE='\"clamp\"'")
+  # Setting this option to "ON" fixes error: "shared:ERROR:
+  # BINARYEN_TRAP_MODE is not supported by the LLVM wasm backend" if
+  # using the "upstream" backend of Emscripten.
+  if (NOT EMSCRIPTEN_SET_LLVM_WASM_BACKEND)
+    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s BINARYEN_TRAP_MODE='\"clamp\"'")
   endif()
 
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'")
+  
 elseif (CMAKE_SYSTEM_NAME STREQUAL "Android")
 
 else()
--- a/Resources/CMake/DcmtkConfiguration.cmake	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/CMake/DcmtkConfiguration.cmake	Thu Mar 19 11:48:30 2020 +0100
@@ -9,6 +9,8 @@
     include(${CMAKE_CURRENT_LIST_DIR}/DcmtkConfigurationStatic-3.6.2.cmake)
   elseif (DCMTK_STATIC_VERSION STREQUAL "3.6.4")
     include(${CMAKE_CURRENT_LIST_DIR}/DcmtkConfigurationStatic-3.6.4.cmake)
+  elseif (DCMTK_STATIC_VERSION STREQUAL "3.6.5")
+    include(${CMAKE_CURRENT_LIST_DIR}/DcmtkConfigurationStatic-3.6.5.cmake)
   else()
     message(FATAL_ERROR "Unsupported version of DCMTK: ${DCMTK_STATIC_VERSION}")
   endif()
@@ -24,6 +26,7 @@
   LIST(REMOVE_ITEM DCMTK_SOURCES 
     ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdictbi.cc
     ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdeftag.cc
+    ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdict_orthanc.cc
     )
 
   if (ENABLE_DCMTK_NETWORKING)
@@ -48,6 +51,12 @@
     list(REMOVE_ITEM DCMTK_SOURCES 
       ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/ddpiimpl.cc
 
+      # Solves linking problem in WebAssembly: "wasm-ld: error:
+      # duplicate symbol: jaritab" (modification in Orthanc 1.5.9)
+      ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg8/jaricom.c
+      ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg12/jaricom.c
+      ${DCMTK_SOURCES_DIR}/dcmjpeg/libijg24/jaricom.c
+
       # Disable support for encoding JPEG (modification in Orthanc 1.0.1)
       ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djcodece.cc
       ${DCMTK_SOURCES_DIR}/dcmjpeg/libsrc/djencsv1.cc
--- a/Resources/CMake/DcmtkConfigurationStatic-3.6.2.cmake	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/CMake/DcmtkConfigurationStatic-3.6.2.cmake	Thu Mar 19 11:48:30 2020 +0100
@@ -12,7 +12,12 @@
 
 set(DCMTK_BINARY_DIR ${DCMTK_SOURCES_DIR}/)
 set(DCMTK_CMAKE_INCLUDE ${DCMTK_SOURCES_DIR}/)
-set(DCMTK_WITH_THREADS ON)
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
+  set(DCMTK_WITH_THREADS OFF)  # Disable thread support in wasm/asm.js
+else()
+  set(DCMTK_WITH_THREADS ON)
+endif()
 
 add_definitions(-DDCMTK_INSIDE_LOG4CPLUS=1)
 
@@ -31,7 +36,7 @@
   message("Applying patch to detect mathematic primitives in DCMTK 3.6.2 with C++11")
   execute_process(
     COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
-    ${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.2-cmath.patch
+    ${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.2.patch
     WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
     RESULT_VARIABLE Failure
     )
@@ -39,6 +44,11 @@
   if (Failure)
     message(FATAL_ERROR "Error while patching a file")
   endif()
+
+  configure_file(
+    ${ORTHANC_ROOT}/Resources/Patches/dcmtk-dcdict_orthanc.cc
+    ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdict_orthanc.cc
+    COPYONLY)
 else()
   message("The patches for DCMTK have already been applied")
 endif()
@@ -72,18 +82,21 @@
   SET(DCMTK_ENABLE_CHARSET_CONVERSION "iconv" CACHE STRING "")
   SET(HAVE_SYS_GETTID 0 CACHE INTERNAL "")
 
-  execute_process(
-    COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
-    ${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.2-linux-standard-base.patch
-    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-    RESULT_VARIABLE Failure
-    )
+  if (FirstRun)
+    execute_process(
+      COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
+      ${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.2-linux-standard-base.patch
+      WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+      RESULT_VARIABLE Failure
+      )
 
-  if (FirstRun AND Failure)
-    message(FATAL_ERROR "Error while patching a file")
+    if (Failure)
+      message(FATAL_ERROR "Error while patching a file")
+    endif()
   endif()
 endif()
 
+
 SET(DCMTK_SOURCE_DIR ${DCMTK_SOURCES_DIR})
 include(${DCMTK_SOURCES_DIR}/CMake/CheckFunctionWithHeaderExists.cmake)
 include(${DCMTK_SOURCES_DIR}/CMake/GenerateDCMTKConfigure.cmake)
@@ -163,19 +176,6 @@
 endif()
 
 
-if (ORTHANC_SANDBOXED)
-  configure_file(
-    ${ORTHANC_ROOT}/Resources/WebAssembly/dcdict.h
-    ${DCMTK_SOURCES_DIR}/dcmdata/include/dcmtk/dcmdata/dcdict.h
-    COPYONLY)
-  
-  configure_file(
-    ${ORTHANC_ROOT}/Resources/WebAssembly/dcdict.cc
-    ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdict.cc
-    COPYONLY)
-endif()
-
-
 #set_source_files_properties(${DCMTK_SOURCES}
 #  PROPERTIES COMPILE_DEFINITIONS
 #  "PACKAGE_VERSION=\"${DCMTK_PACKAGE_VERSION}\";PACKAGE_VERSION_NUMBER=\"${DCMTK_VERSION_NUMBER}\"")
--- a/Resources/CMake/DcmtkConfigurationStatic-3.6.4.cmake	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/CMake/DcmtkConfigurationStatic-3.6.4.cmake	Thu Mar 19 11:48:30 2020 +0100
@@ -12,7 +12,12 @@
 
 set(DCMTK_BINARY_DIR ${DCMTK_SOURCES_DIR}/)
 set(DCMTK_CMAKE_INCLUDE ${DCMTK_SOURCES_DIR}/)
-set(DCMTK_WITH_THREADS ON)
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
+  set(DCMTK_WITH_THREADS OFF)  # Disable thread support in wasm/asm.js
+else()
+  set(DCMTK_WITH_THREADS ON)
+endif()
 
 add_definitions(-DDCMTK_INSIDE_LOG4CPLUS=1)
 
@@ -25,16 +30,25 @@
 DownloadPackage(${DCMTK_MD5} ${DCMTK_URL} "${DCMTK_SOURCES_DIR}")
 
 
-# Apply the patches
-execute_process(
-  COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
-  ${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.4.patch
-  WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-  RESULT_VARIABLE Failure
-  )
+if (FirstRun)
+  # Apply the patches
+  execute_process(
+    COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
+    ${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.4.patch
+    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+    RESULT_VARIABLE Failure
+    )
 
-if (FirstRun AND Failure)
-  message(FATAL_ERROR "Error while patching files")
+  if (Failure)
+    message(FATAL_ERROR "Error while patching a file")
+  endif()
+
+  configure_file(
+    ${ORTHANC_ROOT}/Resources/Patches/dcmtk-dcdict_orthanc.cc
+    ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdict_orthanc.cc
+    COPYONLY)
+else()
+  message("The patches for DCMTK have already been applied")
 endif()
 
 
@@ -152,19 +166,6 @@
 endif()
 
 
-if (ORTHANC_SANDBOXED)
-  configure_file(
-    ${ORTHANC_ROOT}/Resources/WebAssembly/dcdict.h
-    ${DCMTK_SOURCES_DIR}/dcmdata/include/dcmtk/dcmdata/dcdict.h
-    COPYONLY)
-  
-  configure_file(
-    ${ORTHANC_ROOT}/Resources/WebAssembly/dcdict.cc
-    ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdict.cc
-    COPYONLY)
-endif()
-
-
 list(REMOVE_ITEM DCMTK_SOURCES 
   ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdictbi.cc
   ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdeftag.cc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/DcmtkConfigurationStatic-3.6.5.cmake	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,210 @@
+SET(DCMTK_VERSION_NUMBER 365)
+SET(DCMTK_PACKAGE_VERSION "3.6.5")
+SET(DCMTK_SOURCES_DIR ${CMAKE_BINARY_DIR}/dcmtk-3.6.5)
+SET(DCMTK_URL "http://orthanc.osimis.io/ThirdPartyDownloads/dcmtk-3.6.5.tar.gz")
+SET(DCMTK_MD5 "e19707f64ee5695c496b9c1e48e39d07")
+
+macro(DCMTK_UNSET)
+endmacro()
+
+macro(DCMTK_UNSET_CACHE)
+endmacro()
+
+set(DCMTK_BINARY_DIR ${DCMTK_SOURCES_DIR}/)
+set(DCMTK_CMAKE_INCLUDE ${DCMTK_SOURCES_DIR}/)
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
+  set(DCMTK_WITH_THREADS OFF)  # Disable thread support in wasm/asm.js
+else()
+  set(DCMTK_WITH_THREADS ON)
+endif()
+
+add_definitions(-DDCMTK_INSIDE_LOG4CPLUS=1)
+
+if (IS_DIRECTORY "${DCMTK_SOURCES_DIR}")
+  set(FirstRun OFF)
+else()
+  set(FirstRun ON)
+endif()
+
+DownloadPackage(${DCMTK_MD5} ${DCMTK_URL} "${DCMTK_SOURCES_DIR}")
+
+
+if (FirstRun)
+  # Apply the patches
+  execute_process(
+    COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
+    ${ORTHANC_ROOT}/Resources/Patches/dcmtk-3.6.5.patch
+    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+    RESULT_VARIABLE Failure
+    )
+
+  if (Failure)
+    message(FATAL_ERROR "Error while patching a file")
+  endif()
+
+  configure_file(
+    ${ORTHANC_ROOT}/Resources/Patches/dcmtk-dcdict_orthanc.cc
+    ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/dcdict_orthanc.cc
+    COPYONLY)
+else()
+  message("The patches for DCMTK have already been applied")
+endif()
+
+
+include_directories(
+  ${DCMTK_SOURCES_DIR}/dcmiod/include
+  )
+
+
+# C_CHAR_UNSIGNED *must* be set before calling "GenerateDCMTKConfigure.cmake"
+IF (CMAKE_CROSSCOMPILING)
+  if (CMAKE_COMPILER_IS_GNUCXX AND
+      CMAKE_SYSTEM_NAME STREQUAL "Windows")  # MinGW
+    SET(C_CHAR_UNSIGNED 1 CACHE INTERNAL "Whether char is unsigned.")
+
+  elseif(CMAKE_SYSTEM_NAME STREQUAL "Emscripten")  # WebAssembly or asm.js
+
+    # Check out "../WebAssembly/ArithmeticTests/" to regenerate the
+    # "arith.h" file
+    configure_file(
+      ${ORTHANC_ROOT}/Resources/WebAssembly/arith.h
+      ${DCMTK_SOURCES_DIR}/config/include/dcmtk/config/arith.h
+      COPYONLY)
+
+    UNSET(C_CHAR_UNSIGNED CACHE)
+    SET(C_CHAR_UNSIGNED 0 CACHE INTERNAL "")
+
+  else()
+    message(FATAL_ERROR "Support your platform here")
+  endif()
+ENDIF()
+
+
+if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+  SET(DCMTK_ENABLE_CHARSET_CONVERSION "iconv" CACHE STRING "")
+  SET(HAVE_SYS_GETTID 0 CACHE INTERNAL "")
+endif()
+
+
+SET(DCMTK_SOURCE_DIR ${DCMTK_SOURCES_DIR})
+include(${DCMTK_SOURCES_DIR}/CMake/CheckFunctionWithHeaderExists.cmake)
+include(${DCMTK_SOURCES_DIR}/CMake/GenerateDCMTKConfigure.cmake)
+
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")  # WebAssembly or
+  # asm.js The macros below are not properly discovered by DCMTK
+  # when using WebAssembly. Check out "../WebAssembly/arith.h" for
+  # how we produced these values. This step MUST be after
+  # "GenerateDCMTKConfigure" and before the generation of
+  # "osconfig.h".
+  UNSET(SIZEOF_VOID_P   CACHE)
+  UNSET(SIZEOF_CHAR     CACHE)
+  UNSET(SIZEOF_DOUBLE   CACHE)
+  UNSET(SIZEOF_FLOAT    CACHE)
+  UNSET(SIZEOF_INT      CACHE)
+  UNSET(SIZEOF_LONG     CACHE)
+  UNSET(SIZEOF_SHORT    CACHE)
+  UNSET(SIZEOF_VOID_P   CACHE)
+
+  SET(SIZEOF_VOID_P 4   CACHE INTERNAL "")
+  SET(SIZEOF_CHAR 1     CACHE INTERNAL "")
+  SET(SIZEOF_DOUBLE 8   CACHE INTERNAL "")
+  SET(SIZEOF_FLOAT 4    CACHE INTERNAL "")
+  SET(SIZEOF_INT 4      CACHE INTERNAL "")
+  SET(SIZEOF_LONG 4     CACHE INTERNAL "")
+  SET(SIZEOF_SHORT 2    CACHE INTERNAL "")
+  SET(SIZEOF_VOID_P 4   CACHE INTERNAL "")
+endif()
+
+
+set(DCMTK_PACKAGE_VERSION_SUFFIX "")
+set(DCMTK_PACKAGE_VERSION_NUMBER ${DCMTK_VERSION_NUMBER})
+
+CONFIGURE_FILE(
+  ${DCMTK_SOURCES_DIR}/CMake/osconfig.h.in
+  ${DCMTK_SOURCES_DIR}/config/include/dcmtk/config/osconfig.h)
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  link_libraries(netapi32)  # For NetWkstaUserGetInfo@12
+  link_libraries(iphlpapi)  # For GetAdaptersInfo@8
+
+  # Configure Wine if cross-compiling for Windows
+  if (CMAKE_COMPILER_IS_GNUCXX)
+    include(${DCMTK_SOURCES_DIR}/CMake/dcmtkUseWine.cmake)
+    FIND_PROGRAM(WINE_WINE_PROGRAM wine)
+    FIND_PROGRAM(WINE_WINEPATH_PROGRAM winepath)
+    list(APPEND DCMTK_TRY_COMPILE_REQUIRED_CMAKE_FLAGS "-DCMAKE_EXE_LINKER_FLAGS=-static")
+  endif()
+endif()
+
+# This step must be after the generation of "osconfig.h"
+if (NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
+  INSPECT_FUNDAMENTAL_ARITHMETIC_TYPES()
+endif()
+
+
+# Source for the logging facility of DCMTK
+AUX_SOURCE_DIRECTORY(${DCMTK_SOURCES_DIR}/oflog/libsrc DCMTK_SOURCES)
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten")
+  list(REMOVE_ITEM DCMTK_SOURCES 
+    ${DCMTK_SOURCES_DIR}/oflog/libsrc/clfsap.cc
+    ${DCMTK_SOURCES_DIR}/oflog/libsrc/windebap.cc
+    ${DCMTK_SOURCES_DIR}/oflog/libsrc/winsock.cc
+    )
+
+elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  list(REMOVE_ITEM DCMTK_SOURCES 
+    ${DCMTK_SOURCES_DIR}/oflog/libsrc/unixsock.cc
+    ${DCMTK_SOURCES_DIR}/oflog/libsrc/clfsap.cc
+    )
+endif()
+
+
+list(REMOVE_ITEM DCMTK_SOURCES 
+  ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdictbi.cc
+  ${DCMTK_SOURCES_DIR}/dcmdata/libsrc/mkdeftag.cc
+  )
+
+
+# Starting with DCMTK 3.6.2, the Nagle algorithm is not disabled by
+# default since this does not seem to be appropriate (anymore) for
+# most modern operating systems. In order to change this default, the
+# environment variable NO_TCPDELAY can be set to "1" (see envvars.txt
+# for details). Alternatively, the macro DISABLE_NAGLE_ALGORITHM can
+# be defined to change this setting at compilation time (see
+# macros.txt for details).
+# https://forum.dcmtk.org/viewtopic.php?t=4632
+add_definitions(
+  -DDISABLE_NAGLE_ALGORITHM=1
+  )
+
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  # For compatibility with Windows XP, avoid using fiber-local-storage
+  # in log4cplus, but use thread-local-storage instead. Otherwise,
+  # Windows XP complains about missing "FlsGetValue()" in KERNEL32.dll
+  add_definitions(
+    -DDCMTK_LOG4CPLUS_AVOID_WIN32_FLS
+    )
+
+  if (CMAKE_COMPILER_IS_GNUCXX OR             # MinGW
+      "${CMAKE_SIZEOF_VOID_P}" STREQUAL "4")  # MSVC for 32bit (*)
+
+    # (*) With multithreaded logging enabled, Visual Studio 2008 fails
+    # with error: ".\dcmtk-3.6.5\oflog\libsrc\globinit.cc(422) : error
+    # C2664: 'dcmtk::log4cplus::thread::impl::tls_init' : cannot
+    # convert parameter 1 from 'void (__stdcall *)(void *)' to
+    # 'dcmtk::log4cplus::thread::impl::tls_init_cleanup_func_type'"
+    #   None of the functions with this name in scope match the target type
+
+    add_definitions(
+      -DDCMTK_LOG4CPLUS_SINGLE_THREADED
+      )
+  endif()
+endif()
--- a/Resources/CMake/JsonCppConfiguration.cmake	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/CMake/JsonCppConfiguration.cmake	Thu Mar 19 11:48:30 2020 +0100
@@ -2,9 +2,9 @@
 
 if (STATIC_BUILD OR NOT USE_SYSTEM_JSONCPP)
   if (USE_LEGACY_JSONCPP)
-    set(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-0.10.6)
-    set(JSONCPP_URL "http://orthanc.osimis.io/ThirdPartyDownloads/jsoncpp-0.10.6.tar.gz")
-    set(JSONCPP_MD5 "13d1991d79697df8cadbc25c93e37c83")
+    set(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-0.10.7)
+    set(JSONCPP_URL "http://orthanc.osimis.io/ThirdPartyDownloads/jsoncpp-0.10.7.tar.gz")
+    set(JSONCPP_MD5 "3a8072ca6a1fa9cbaf7715ae625f134f")
     add_definitions(-DORTHANC_LEGACY_JSONCPP=1)
   else()
     set(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-1.8.4)
@@ -78,9 +78,11 @@
   # https://gitlab.kitware.com/third-party/jsoncpp/commit/56df2068470241f9043b676bfae415ed62a0c172
   add_definitions(-DJSONCPP_DEPRECATED_STACK_LIMIT=5000)
 
-  if (CMAKE_COMPILER_IS_GNUCXX OR
-      "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
-    message("Switching to C++11 standard in gcc/clang, as version of JsonCpp is >= 1.0.0")
+  if (CMAKE_COMPILER_IS_GNUCXX)
+    message("Switching to C++11 standard in gcc, as version of JsonCpp is >= 1.0.0")
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11 -Wno-deprecated-declarations")
+  elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
+    message("Switching to C++11 standard in clang, as version of JsonCpp is >= 1.0.0")
     set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wno-deprecated-declarations")
   endif()
 endif()
--- a/Resources/CMake/LibCurlConfiguration.cmake	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/CMake/LibCurlConfiguration.cmake	Thu Mar 19 11:48:30 2020 +0100
@@ -60,7 +60,7 @@
       )
   endif()
 
-  if (NOT EXISTS "${CURL_SOURCES_DIR}/lib/curl_config.h")
+  if (NOT EXISTS "${CURL_SOURCES_DIR}/lib/vauth/vauth/vauth.h")
     #file(WRITE ${CURL_SOURCES_DIR}/lib/curl_config.h "")
 
     file(WRITE ${CURL_SOURCES_DIR}/lib/vauth/vauth/vauth.h "#include \"../vauth.h\"\n")
--- a/Resources/CMake/OpenSslConfiguration.cmake	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/CMake/OpenSslConfiguration.cmake	Thu Mar 19 11:48:30 2020 +0100
@@ -1,335 +1,10 @@
 if (STATIC_BUILD OR NOT USE_SYSTEM_OPENSSL)
-  SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.0.2p)
-  SET(OPENSSL_URL "http://orthanc.osimis.io/ThirdPartyDownloads/openssl-1.0.2p.tar.gz")
-  SET(OPENSSL_MD5 "ac5eb30bf5798aa14b1ae6d0e7da58df")
-
-  if (IS_DIRECTORY "${OPENSSL_SOURCES_DIR}")
-    set(FirstRun OFF)
+  if (OPENSSL_STATIC_VERSION STREQUAL "1.0.2")
+    include(${CMAKE_CURRENT_LIST_DIR}/OpenSslConfigurationStatic-1.0.2.cmake)
+  elseif (OPENSSL_STATIC_VERSION STREQUAL "1.1.1")
+    include(${CMAKE_CURRENT_LIST_DIR}/OpenSslConfigurationStatic-1.1.1.cmake)
   else()
-    set(FirstRun ON)
-  endif()
-
-  DownloadPackage(${OPENSSL_MD5} ${OPENSSL_URL} "${OPENSSL_SOURCES_DIR}")
-
-  if (FirstRun)
-    file(MAKE_DIRECTORY ${OPENSSL_SOURCES_DIR}/include/openssl)
-
-    foreach(header
-      ${OPENSSL_SOURCES_DIR}/crypto/aes/aes.h
-      ${OPENSSL_SOURCES_DIR}/crypto/asn1/asn1.h
-      ${OPENSSL_SOURCES_DIR}/crypto/asn1/asn1_mac.h
-      ${OPENSSL_SOURCES_DIR}/crypto/asn1/asn1t.h
-      ${OPENSSL_SOURCES_DIR}/crypto/bf/blowfish.h
-      ${OPENSSL_SOURCES_DIR}/crypto/bio/bio.h
-      ${OPENSSL_SOURCES_DIR}/crypto/bn/bn.h
-      ${OPENSSL_SOURCES_DIR}/crypto/buffer/buffer.h
-      ${OPENSSL_SOURCES_DIR}/crypto/camellia/camellia.h
-      ${OPENSSL_SOURCES_DIR}/crypto/cast/cast.h
-      ${OPENSSL_SOURCES_DIR}/crypto/cmac/cmac.h
-      ${OPENSSL_SOURCES_DIR}/crypto/cms/cms.h
-      ${OPENSSL_SOURCES_DIR}/crypto/comp/comp.h
-      ${OPENSSL_SOURCES_DIR}/crypto/conf/conf.h
-      ${OPENSSL_SOURCES_DIR}/crypto/conf/conf_api.h
-      ${OPENSSL_SOURCES_DIR}/crypto/crypto.h
-      ${OPENSSL_SOURCES_DIR}/crypto/des/des.h
-      ${OPENSSL_SOURCES_DIR}/crypto/des/des_old.h
-      ${OPENSSL_SOURCES_DIR}/crypto/dh/dh.h
-      ${OPENSSL_SOURCES_DIR}/crypto/dsa/dsa.h
-      ${OPENSSL_SOURCES_DIR}/crypto/dso/dso.h
-      ${OPENSSL_SOURCES_DIR}/crypto/ebcdic.h
-      ${OPENSSL_SOURCES_DIR}/crypto/ec/ec.h
-      ${OPENSSL_SOURCES_DIR}/crypto/ecdh/ecdh.h
-      ${OPENSSL_SOURCES_DIR}/crypto/ecdsa/ecdsa.h
-      ${OPENSSL_SOURCES_DIR}/crypto/engine/engine.h
-      ${OPENSSL_SOURCES_DIR}/crypto/err/err.h
-      ${OPENSSL_SOURCES_DIR}/crypto/evp/evp.h
-      ${OPENSSL_SOURCES_DIR}/crypto/hmac/hmac.h
-      ${OPENSSL_SOURCES_DIR}/crypto/idea/idea.h
-      ${OPENSSL_SOURCES_DIR}/crypto/jpake/jpake.h
-      ${OPENSSL_SOURCES_DIR}/crypto/krb5/krb5_asn.h
-      ${OPENSSL_SOURCES_DIR}/crypto/lhash/lhash.h
-      ${OPENSSL_SOURCES_DIR}/crypto/md2/md2.h
-      ${OPENSSL_SOURCES_DIR}/crypto/md4/md4.h
-      ${OPENSSL_SOURCES_DIR}/crypto/md5/md5.h
-      ${OPENSSL_SOURCES_DIR}/crypto/mdc2/mdc2.h
-      ${OPENSSL_SOURCES_DIR}/crypto/modes/modes.h
-      ${OPENSSL_SOURCES_DIR}/crypto/objects/obj_mac.h
-      ${OPENSSL_SOURCES_DIR}/crypto/objects/objects.h
-      ${OPENSSL_SOURCES_DIR}/crypto/ocsp/ocsp.h
-      ${OPENSSL_SOURCES_DIR}/crypto/opensslconf.h
-      ${OPENSSL_SOURCES_DIR}/crypto/opensslv.h
-      ${OPENSSL_SOURCES_DIR}/crypto/ossl_typ.h
-      ${OPENSSL_SOURCES_DIR}/crypto/pem/pem.h
-      ${OPENSSL_SOURCES_DIR}/crypto/pem/pem2.h
-      ${OPENSSL_SOURCES_DIR}/crypto/pkcs12/pkcs12.h
-      ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/pkcs7.h
-      ${OPENSSL_SOURCES_DIR}/crypto/pqueue/pqueue.h
-      ${OPENSSL_SOURCES_DIR}/crypto/rand/rand.h
-      ${OPENSSL_SOURCES_DIR}/crypto/rc2/rc2.h
-      ${OPENSSL_SOURCES_DIR}/crypto/rc4/rc4.h
-      ${OPENSSL_SOURCES_DIR}/crypto/rc5/rc5.h
-      ${OPENSSL_SOURCES_DIR}/crypto/ripemd/ripemd.h
-      ${OPENSSL_SOURCES_DIR}/crypto/rsa/rsa.h
-      ${OPENSSL_SOURCES_DIR}/crypto/seed/seed.h
-      ${OPENSSL_SOURCES_DIR}/crypto/sha/sha.h
-      ${OPENSSL_SOURCES_DIR}/crypto/srp/srp.h
-      ${OPENSSL_SOURCES_DIR}/crypto/stack/safestack.h
-      ${OPENSSL_SOURCES_DIR}/crypto/stack/stack.h
-      ${OPENSSL_SOURCES_DIR}/crypto/store/store.h
-      ${OPENSSL_SOURCES_DIR}/crypto/symhacks.h
-      ${OPENSSL_SOURCES_DIR}/crypto/ts/ts.h
-      ${OPENSSL_SOURCES_DIR}/crypto/txt_db/txt_db.h
-      ${OPENSSL_SOURCES_DIR}/crypto/ui/ui.h
-      ${OPENSSL_SOURCES_DIR}/crypto/ui/ui_compat.h
-      ${OPENSSL_SOURCES_DIR}/crypto/whrlpool/whrlpool.h
-      ${OPENSSL_SOURCES_DIR}/crypto/x509/x509.h
-      ${OPENSSL_SOURCES_DIR}/crypto/x509/x509_vfy.h
-      ${OPENSSL_SOURCES_DIR}/crypto/x509v3/x509v3.h
-      ${OPENSSL_SOURCES_DIR}/e_os2.h
-      ${OPENSSL_SOURCES_DIR}/ssl/dtls1.h
-      ${OPENSSL_SOURCES_DIR}/ssl/kssl.h
-      ${OPENSSL_SOURCES_DIR}/ssl/srtp.h
-      ${OPENSSL_SOURCES_DIR}/ssl/ssl.h
-      ${OPENSSL_SOURCES_DIR}/ssl/ssl2.h
-      ${OPENSSL_SOURCES_DIR}/ssl/ssl23.h
-      ${OPENSSL_SOURCES_DIR}/ssl/ssl3.h
-      ${OPENSSL_SOURCES_DIR}/ssl/tls1.h
-      )
-      file(COPY ${header} DESTINATION ${OPENSSL_SOURCES_DIR}/include/openssl)
-    endforeach()
-
-    file(RENAME
-      ${OPENSSL_SOURCES_DIR}/include/openssl/e_os2.h
-      ${OPENSSL_SOURCES_DIR}/include/openssl/e_os2_source.h)
-
-    # The following patch of "e_os2.h" prevents from building OpenSSL
-    # as a DLL under Windows. Otherwise, symbols have inconsistent
-    # linkage if ${OPENSSL_SOURCES} is used to create a DLL (notably
-    # if building an Orthanc plugin such as MySQL).
-    file(WRITE ${OPENSSL_SOURCES_DIR}/include/openssl/e_os2.h "
-#include \"e_os2_source.h\"
-#if defined(_WIN32)
-#  undef OPENSSL_EXPORT
-#  undef OPENSSL_IMPORT
-#  undef OPENSSL_EXTERN
-#  undef OPENSSL_GLOBAL
-#  define OPENSSL_EXPORT
-#  define OPENSSL_IMPORT
-#  define OPENSSL_EXTERN extern
-#  define OPENSSL_GLOBAL
-#endif
-")
-  endif()
-  
-  add_definitions(
-    -DOPENSSL_THREADS
-    -DOPENSSL_IA32_SSE2
-    -DOPENSSL_NO_ASM
-    -DOPENSSL_NO_DYNAMIC_ENGINE
-    -DNO_WINDOWS_BRAINDEATH
-
-    -DOPENSSL_NO_BF 
-    -DOPENSSL_NO_CAMELLIA
-    -DOPENSSL_NO_CAST 
-    -DOPENSSL_NO_EC_NISTP_64_GCC_128
-    -DOPENSSL_NO_GMP
-    -DOPENSSL_NO_GOST
-    -DOPENSSL_NO_HW
-    -DOPENSSL_NO_JPAKE
-    -DOPENSSL_NO_IDEA
-    -DOPENSSL_NO_KRB5 
-    -DOPENSSL_NO_MD2 
-    -DOPENSSL_NO_MDC2 
-    #-DOPENSSL_NO_MD4   # MD4 is necessary for MariaDB/MySQL client
-    -DOPENSSL_NO_RC2 
-    -DOPENSSL_NO_RC4 
-    -DOPENSSL_NO_RC5 
-    -DOPENSSL_NO_RFC3779
-    -DOPENSSL_NO_SCTP
-    -DOPENSSL_NO_STORE
-    -DOPENSSL_NO_SEED
-    -DOPENSSL_NO_WHIRLPOOL
-    -DOPENSSL_NO_RIPEMD
-    )
-
-  include_directories(
-    ${OPENSSL_SOURCES_DIR}
-    ${OPENSSL_SOURCES_DIR}/crypto
-    ${OPENSSL_SOURCES_DIR}/crypto/asn1
-    ${OPENSSL_SOURCES_DIR}/crypto/modes
-    ${OPENSSL_SOURCES_DIR}/crypto/evp
-    ${OPENSSL_SOURCES_DIR}/include
-    )
-
-  set(OPENSSL_SOURCES_SUBDIRS
-    ${OPENSSL_SOURCES_DIR}/crypto
-    ${OPENSSL_SOURCES_DIR}/crypto/aes
-    ${OPENSSL_SOURCES_DIR}/crypto/asn1
-    ${OPENSSL_SOURCES_DIR}/crypto/bio
-    ${OPENSSL_SOURCES_DIR}/crypto/bn
-    ${OPENSSL_SOURCES_DIR}/crypto/buffer
-    ${OPENSSL_SOURCES_DIR}/crypto/cmac
-    ${OPENSSL_SOURCES_DIR}/crypto/cms
-    ${OPENSSL_SOURCES_DIR}/crypto/comp
-    ${OPENSSL_SOURCES_DIR}/crypto/conf
-    ${OPENSSL_SOURCES_DIR}/crypto/des
-    ${OPENSSL_SOURCES_DIR}/crypto/dh
-    ${OPENSSL_SOURCES_DIR}/crypto/dsa
-    ${OPENSSL_SOURCES_DIR}/crypto/dso
-    ${OPENSSL_SOURCES_DIR}/crypto/engine
-    ${OPENSSL_SOURCES_DIR}/crypto/err
-    ${OPENSSL_SOURCES_DIR}/crypto/evp
-    ${OPENSSL_SOURCES_DIR}/crypto/hmac
-    ${OPENSSL_SOURCES_DIR}/crypto/lhash
-    ${OPENSSL_SOURCES_DIR}/crypto/md4
-    ${OPENSSL_SOURCES_DIR}/crypto/md5
-    ${OPENSSL_SOURCES_DIR}/crypto/modes
-    ${OPENSSL_SOURCES_DIR}/crypto/objects
-    ${OPENSSL_SOURCES_DIR}/crypto/ocsp
-    ${OPENSSL_SOURCES_DIR}/crypto/pem
-    ${OPENSSL_SOURCES_DIR}/crypto/pkcs12
-    ${OPENSSL_SOURCES_DIR}/crypto/pkcs7
-    ${OPENSSL_SOURCES_DIR}/crypto/pqueue
-    ${OPENSSL_SOURCES_DIR}/crypto/rand
-    ${OPENSSL_SOURCES_DIR}/crypto/rsa
-    ${OPENSSL_SOURCES_DIR}/crypto/sha
-    ${OPENSSL_SOURCES_DIR}/crypto/srp
-    ${OPENSSL_SOURCES_DIR}/crypto/stack
-    ${OPENSSL_SOURCES_DIR}/crypto/ts
-    ${OPENSSL_SOURCES_DIR}/crypto/txt_db
-    ${OPENSSL_SOURCES_DIR}/crypto/ui
-    ${OPENSSL_SOURCES_DIR}/crypto/x509
-    ${OPENSSL_SOURCES_DIR}/crypto/x509v3
-    ${OPENSSL_SOURCES_DIR}/ssl
-    )
-
-  if (ENABLE_OPENSSL_ENGINES)
-    list(APPEND OPENSSL_SOURCES_SUBDIRS
-      ${OPENSSL_SOURCES_DIR}/engines
-      )
-  endif()
-  
-  list(APPEND OPENSSL_SOURCES_SUBDIRS
-    # EC, ECDH and ECDSA are necessary for PKCS11, and for contacting
-    # HTTPS servers that use TLS certificate encrypted with ECDSA
-    # (check the output of a recent version of the "sslscan"
-    # command). Until Orthanc <= 1.4.1, these features were only
-    # enabled if ENABLE_PKCS11 support was set to "ON".
-    # https://groups.google.com/d/msg/orthanc-users/2l-bhYIMEWg/oMmK33bYBgAJ
-    ${OPENSSL_SOURCES_DIR}/crypto/ec
-    ${OPENSSL_SOURCES_DIR}/crypto/ecdh
-    ${OPENSSL_SOURCES_DIR}/crypto/ecdsa
-    )
-
-  foreach(d ${OPENSSL_SOURCES_SUBDIRS})
-    AUX_SOURCE_DIRECTORY(${d} OPENSSL_SOURCES)
-  endforeach()
-
-  list(REMOVE_ITEM OPENSSL_SOURCES
-    ${OPENSSL_SOURCES_DIR}/crypto/LPdir_unix.c
-    ${OPENSSL_SOURCES_DIR}/crypto/LPdir_vms.c
-    ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win.c
-    ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win32.c
-    ${OPENSSL_SOURCES_DIR}/crypto/LPdir_wince.c
-    ${OPENSSL_SOURCES_DIR}/crypto/armcap.c
-    ${OPENSSL_SOURCES_DIR}/crypto/bf/bfs.cpp
-    ${OPENSSL_SOURCES_DIR}/crypto/bio/bss_rtcp.c
-    ${OPENSSL_SOURCES_DIR}/crypto/bn/exp.c
-    ${OPENSSL_SOURCES_DIR}/crypto/conf/cnf_save.c
-    ${OPENSSL_SOURCES_DIR}/crypto/conf/test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/des/des.c
-    ${OPENSSL_SOURCES_DIR}/crypto/des/des3s.cpp
-    ${OPENSSL_SOURCES_DIR}/crypto/des/des_opts.c
-    ${OPENSSL_SOURCES_DIR}/crypto/des/dess.cpp
-    ${OPENSSL_SOURCES_DIR}/crypto/des/read_pwd.c
-    ${OPENSSL_SOURCES_DIR}/crypto/des/speed.c
-    ${OPENSSL_SOURCES_DIR}/crypto/evp/e_dsa.c
-    ${OPENSSL_SOURCES_DIR}/crypto/evp/m_ripemd.c
-    ${OPENSSL_SOURCES_DIR}/crypto/lhash/lh_test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/md4/md4.c
-    ${OPENSSL_SOURCES_DIR}/crypto/md4/md4s.cpp
-    ${OPENSSL_SOURCES_DIR}/crypto/md4/md4test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/md5/md5s.cpp
-    ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/bio_ber.c
-    ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/pk7_enc.c
-    ${OPENSSL_SOURCES_DIR}/crypto/ppccap.c
-    ${OPENSSL_SOURCES_DIR}/crypto/rand/randtest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/s390xcap.c
-    ${OPENSSL_SOURCES_DIR}/crypto/sparcv9cap.c
-    ${OPENSSL_SOURCES_DIR}/crypto/x509v3/tabtest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/x509v3/v3conf.c
-    ${OPENSSL_SOURCES_DIR}/ssl/ssl_task.c
-    ${OPENSSL_SOURCES_DIR}/crypto/LPdir_nyi.c
-    ${OPENSSL_SOURCES_DIR}/crypto/aes/aes_x86core.c
-    ${OPENSSL_SOURCES_DIR}/crypto/bio/bss_dgram.c
-    ${OPENSSL_SOURCES_DIR}/crypto/bn/bntest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/bn/expspeed.c
-    ${OPENSSL_SOURCES_DIR}/crypto/bn/exptest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/engine/enginetest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/evp/evp_test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/hmac/hmactest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/md5/md5.c
-    ${OPENSSL_SOURCES_DIR}/crypto/md5/md5test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/o_dir_test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/dec.c
-    ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/enc.c
-    ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/sign.c
-    ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/verify.c
-    ${OPENSSL_SOURCES_DIR}/crypto/rsa/rsa_test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/sha/sha.c
-    ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1.c
-    ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1t.c
-    ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/sha/sha256t.c
-    ${OPENSSL_SOURCES_DIR}/crypto/sha/sha512t.c
-    ${OPENSSL_SOURCES_DIR}/crypto/sha/shatest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/srp/srptest.c
-
-    ${OPENSSL_SOURCES_DIR}/crypto/bn/divtest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/bn/bnspeed.c
-    ${OPENSSL_SOURCES_DIR}/crypto/des/destest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/dh/p192.c
-    ${OPENSSL_SOURCES_DIR}/crypto/dh/p512.c
-    ${OPENSSL_SOURCES_DIR}/crypto/dh/p1024.c
-    ${OPENSSL_SOURCES_DIR}/crypto/des/rpw.c
-    ${OPENSSL_SOURCES_DIR}/ssl/ssltest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/dsa/dsagen.c
-    ${OPENSSL_SOURCES_DIR}/crypto/dsa/dsatest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/dh/dhtest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/pqueue/pq_test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/des/ncbc_enc.c
-
-    ${OPENSSL_SOURCES_DIR}/crypto/evp/evp_extra_test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/evp/verify_extra_test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/x509/verify_extra_test.c
-    ${OPENSSL_SOURCES_DIR}/crypto/x509v3/v3prin.c
-    ${OPENSSL_SOURCES_DIR}/crypto/x509v3/v3nametest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/constant_time_test.c
-
-    ${OPENSSL_SOURCES_DIR}/ssl/heartbeat_test.c
-    ${OPENSSL_SOURCES_DIR}/ssl/fatalerrtest.c
-    ${OPENSSL_SOURCES_DIR}/ssl/dtlstest.c
-    ${OPENSSL_SOURCES_DIR}/ssl/bad_dtls_test.c
-    ${OPENSSL_SOURCES_DIR}/ssl/clienthellotest.c
-    ${OPENSSL_SOURCES_DIR}/ssl/sslv2conftest.c
-
-    ${OPENSSL_SOURCES_DIR}/crypto/ec/ecp_nistz256.c
-    ${OPENSSL_SOURCES_DIR}/crypto/ec/ecp_nistz256_table.c
-    ${OPENSSL_SOURCES_DIR}/crypto/ec/ectest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/ecdh/ecdhtest.c
-    ${OPENSSL_SOURCES_DIR}/crypto/ecdsa/ecdsatest.c
-    )
-
-
-  if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows")
-    set_source_files_properties(
-      ${OPENSSL_SOURCES}
-      PROPERTIES COMPILE_DEFINITIONS
-      "OPENSSL_SYSNAME_WIN32;SO_WIN32;WIN32_LEAN_AND_MEAN;L_ENDIAN")
-
-    if (ENABLE_OPENSSL_ENGINES)
-      link_libraries(crypt32)
-    endif()
+    message(FATAL_ERROR "Unsupported version of OpenSSL: ${OPENSSL_STATIC_VERSION}")
   endif()
 
   source_group(ThirdParty\\OpenSSL REGULAR_EXPRESSION ${OPENSSL_SOURCES_DIR}/.*)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/OpenSslConfigurationStatic-1.0.2.cmake	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,332 @@
+SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.0.2p)
+SET(OPENSSL_URL "http://orthanc.osimis.io/ThirdPartyDownloads/openssl-1.0.2p.tar.gz")
+SET(OPENSSL_MD5 "ac5eb30bf5798aa14b1ae6d0e7da58df")
+
+if (IS_DIRECTORY "${OPENSSL_SOURCES_DIR}")
+  set(FirstRun OFF)
+else()
+  set(FirstRun ON)
+endif()
+
+DownloadPackage(${OPENSSL_MD5} ${OPENSSL_URL} "${OPENSSL_SOURCES_DIR}")
+
+if (FirstRun)
+  file(MAKE_DIRECTORY ${OPENSSL_SOURCES_DIR}/include/openssl)
+
+  foreach(header
+      ${OPENSSL_SOURCES_DIR}/crypto/aes/aes.h
+      ${OPENSSL_SOURCES_DIR}/crypto/asn1/asn1.h
+      ${OPENSSL_SOURCES_DIR}/crypto/asn1/asn1_mac.h
+      ${OPENSSL_SOURCES_DIR}/crypto/asn1/asn1t.h
+      ${OPENSSL_SOURCES_DIR}/crypto/bf/blowfish.h
+      ${OPENSSL_SOURCES_DIR}/crypto/bio/bio.h
+      ${OPENSSL_SOURCES_DIR}/crypto/bn/bn.h
+      ${OPENSSL_SOURCES_DIR}/crypto/buffer/buffer.h
+      ${OPENSSL_SOURCES_DIR}/crypto/camellia/camellia.h
+      ${OPENSSL_SOURCES_DIR}/crypto/cast/cast.h
+      ${OPENSSL_SOURCES_DIR}/crypto/cmac/cmac.h
+      ${OPENSSL_SOURCES_DIR}/crypto/cms/cms.h
+      ${OPENSSL_SOURCES_DIR}/crypto/comp/comp.h
+      ${OPENSSL_SOURCES_DIR}/crypto/conf/conf.h
+      ${OPENSSL_SOURCES_DIR}/crypto/conf/conf_api.h
+      ${OPENSSL_SOURCES_DIR}/crypto/crypto.h
+      ${OPENSSL_SOURCES_DIR}/crypto/des/des.h
+      ${OPENSSL_SOURCES_DIR}/crypto/des/des_old.h
+      ${OPENSSL_SOURCES_DIR}/crypto/dh/dh.h
+      ${OPENSSL_SOURCES_DIR}/crypto/dsa/dsa.h
+      ${OPENSSL_SOURCES_DIR}/crypto/dso/dso.h
+      ${OPENSSL_SOURCES_DIR}/crypto/ebcdic.h
+      ${OPENSSL_SOURCES_DIR}/crypto/ec/ec.h
+      ${OPENSSL_SOURCES_DIR}/crypto/ecdh/ecdh.h
+      ${OPENSSL_SOURCES_DIR}/crypto/ecdsa/ecdsa.h
+      ${OPENSSL_SOURCES_DIR}/crypto/engine/engine.h
+      ${OPENSSL_SOURCES_DIR}/crypto/err/err.h
+      ${OPENSSL_SOURCES_DIR}/crypto/evp/evp.h
+      ${OPENSSL_SOURCES_DIR}/crypto/hmac/hmac.h
+      ${OPENSSL_SOURCES_DIR}/crypto/idea/idea.h
+      ${OPENSSL_SOURCES_DIR}/crypto/jpake/jpake.h
+      ${OPENSSL_SOURCES_DIR}/crypto/krb5/krb5_asn.h
+      ${OPENSSL_SOURCES_DIR}/crypto/lhash/lhash.h
+      ${OPENSSL_SOURCES_DIR}/crypto/md2/md2.h
+      ${OPENSSL_SOURCES_DIR}/crypto/md4/md4.h
+      ${OPENSSL_SOURCES_DIR}/crypto/md5/md5.h
+      ${OPENSSL_SOURCES_DIR}/crypto/mdc2/mdc2.h
+      ${OPENSSL_SOURCES_DIR}/crypto/modes/modes.h
+      ${OPENSSL_SOURCES_DIR}/crypto/objects/obj_mac.h
+      ${OPENSSL_SOURCES_DIR}/crypto/objects/objects.h
+      ${OPENSSL_SOURCES_DIR}/crypto/ocsp/ocsp.h
+      ${OPENSSL_SOURCES_DIR}/crypto/opensslconf.h
+      ${OPENSSL_SOURCES_DIR}/crypto/opensslv.h
+      ${OPENSSL_SOURCES_DIR}/crypto/ossl_typ.h
+      ${OPENSSL_SOURCES_DIR}/crypto/pem/pem.h
+      ${OPENSSL_SOURCES_DIR}/crypto/pem/pem2.h
+      ${OPENSSL_SOURCES_DIR}/crypto/pkcs12/pkcs12.h
+      ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/pkcs7.h
+      ${OPENSSL_SOURCES_DIR}/crypto/pqueue/pqueue.h
+      ${OPENSSL_SOURCES_DIR}/crypto/rand/rand.h
+      ${OPENSSL_SOURCES_DIR}/crypto/rc2/rc2.h
+      ${OPENSSL_SOURCES_DIR}/crypto/rc4/rc4.h
+      ${OPENSSL_SOURCES_DIR}/crypto/rc5/rc5.h
+      ${OPENSSL_SOURCES_DIR}/crypto/ripemd/ripemd.h
+      ${OPENSSL_SOURCES_DIR}/crypto/rsa/rsa.h
+      ${OPENSSL_SOURCES_DIR}/crypto/seed/seed.h
+      ${OPENSSL_SOURCES_DIR}/crypto/sha/sha.h
+      ${OPENSSL_SOURCES_DIR}/crypto/srp/srp.h
+      ${OPENSSL_SOURCES_DIR}/crypto/stack/safestack.h
+      ${OPENSSL_SOURCES_DIR}/crypto/stack/stack.h
+      ${OPENSSL_SOURCES_DIR}/crypto/store/store.h
+      ${OPENSSL_SOURCES_DIR}/crypto/symhacks.h
+      ${OPENSSL_SOURCES_DIR}/crypto/ts/ts.h
+      ${OPENSSL_SOURCES_DIR}/crypto/txt_db/txt_db.h
+      ${OPENSSL_SOURCES_DIR}/crypto/ui/ui.h
+      ${OPENSSL_SOURCES_DIR}/crypto/ui/ui_compat.h
+      ${OPENSSL_SOURCES_DIR}/crypto/whrlpool/whrlpool.h
+      ${OPENSSL_SOURCES_DIR}/crypto/x509/x509.h
+      ${OPENSSL_SOURCES_DIR}/crypto/x509/x509_vfy.h
+      ${OPENSSL_SOURCES_DIR}/crypto/x509v3/x509v3.h
+      ${OPENSSL_SOURCES_DIR}/e_os2.h
+      ${OPENSSL_SOURCES_DIR}/ssl/dtls1.h
+      ${OPENSSL_SOURCES_DIR}/ssl/kssl.h
+      ${OPENSSL_SOURCES_DIR}/ssl/srtp.h
+      ${OPENSSL_SOURCES_DIR}/ssl/ssl.h
+      ${OPENSSL_SOURCES_DIR}/ssl/ssl2.h
+      ${OPENSSL_SOURCES_DIR}/ssl/ssl23.h
+      ${OPENSSL_SOURCES_DIR}/ssl/ssl3.h
+      ${OPENSSL_SOURCES_DIR}/ssl/tls1.h
+      )
+    file(COPY ${header} DESTINATION ${OPENSSL_SOURCES_DIR}/include/openssl)
+  endforeach()
+
+  file(RENAME
+    ${OPENSSL_SOURCES_DIR}/include/openssl/e_os2.h
+    ${OPENSSL_SOURCES_DIR}/include/openssl/e_os2_source.h)
+
+  # The following patch of "e_os2.h" prevents from building OpenSSL
+  # as a DLL under Windows. Otherwise, symbols have inconsistent
+  # linkage if ${OPENSSL_SOURCES} is used to create a DLL (notably
+  # if building an Orthanc plugin such as MySQL).
+  file(WRITE ${OPENSSL_SOURCES_DIR}/include/openssl/e_os2.h "
+#include \"e_os2_source.h\"
+#if defined(_WIN32)
+#  undef OPENSSL_EXPORT
+#  undef OPENSSL_IMPORT
+#  undef OPENSSL_EXTERN
+#  undef OPENSSL_GLOBAL
+#  define OPENSSL_EXPORT
+#  define OPENSSL_IMPORT
+#  define OPENSSL_EXTERN extern
+#  define OPENSSL_GLOBAL
+#endif
+")
+endif()
+
+add_definitions(
+  -DOPENSSL_THREADS
+  -DOPENSSL_IA32_SSE2
+  -DOPENSSL_NO_ASM
+  -DOPENSSL_NO_DYNAMIC_ENGINE
+  -DNO_WINDOWS_BRAINDEATH
+
+  -DOPENSSL_NO_BF 
+  -DOPENSSL_NO_CAMELLIA
+  -DOPENSSL_NO_CAST 
+  -DOPENSSL_NO_EC_NISTP_64_GCC_128
+  -DOPENSSL_NO_GMP
+  -DOPENSSL_NO_GOST
+  -DOPENSSL_NO_HW
+  -DOPENSSL_NO_JPAKE
+  -DOPENSSL_NO_IDEA
+  -DOPENSSL_NO_KRB5 
+  -DOPENSSL_NO_MD2 
+  -DOPENSSL_NO_MDC2 
+  #-DOPENSSL_NO_MD4   # MD4 is necessary for MariaDB/MySQL client
+  -DOPENSSL_NO_RC2 
+  -DOPENSSL_NO_RC4 
+  -DOPENSSL_NO_RC5 
+  -DOPENSSL_NO_RFC3779
+  -DOPENSSL_NO_SCTP
+  -DOPENSSL_NO_STORE
+  -DOPENSSL_NO_SEED
+  -DOPENSSL_NO_WHIRLPOOL
+  -DOPENSSL_NO_RIPEMD
+  )
+
+include_directories(
+  ${OPENSSL_SOURCES_DIR}
+  ${OPENSSL_SOURCES_DIR}/crypto
+  ${OPENSSL_SOURCES_DIR}/crypto/asn1
+  ${OPENSSL_SOURCES_DIR}/crypto/modes
+  ${OPENSSL_SOURCES_DIR}/crypto/evp
+  ${OPENSSL_SOURCES_DIR}/include
+  )
+
+set(OPENSSL_SOURCES_SUBDIRS
+  ${OPENSSL_SOURCES_DIR}/crypto
+  ${OPENSSL_SOURCES_DIR}/crypto/aes
+  ${OPENSSL_SOURCES_DIR}/crypto/asn1
+  ${OPENSSL_SOURCES_DIR}/crypto/bio
+  ${OPENSSL_SOURCES_DIR}/crypto/bn
+  ${OPENSSL_SOURCES_DIR}/crypto/buffer
+  ${OPENSSL_SOURCES_DIR}/crypto/cmac
+  ${OPENSSL_SOURCES_DIR}/crypto/cms
+  ${OPENSSL_SOURCES_DIR}/crypto/comp
+  ${OPENSSL_SOURCES_DIR}/crypto/conf
+  ${OPENSSL_SOURCES_DIR}/crypto/des
+  ${OPENSSL_SOURCES_DIR}/crypto/dh
+  ${OPENSSL_SOURCES_DIR}/crypto/dsa
+  ${OPENSSL_SOURCES_DIR}/crypto/dso
+  ${OPENSSL_SOURCES_DIR}/crypto/engine
+  ${OPENSSL_SOURCES_DIR}/crypto/err
+  ${OPENSSL_SOURCES_DIR}/crypto/evp
+  ${OPENSSL_SOURCES_DIR}/crypto/hmac
+  ${OPENSSL_SOURCES_DIR}/crypto/lhash
+  ${OPENSSL_SOURCES_DIR}/crypto/md4
+  ${OPENSSL_SOURCES_DIR}/crypto/md5
+  ${OPENSSL_SOURCES_DIR}/crypto/modes
+  ${OPENSSL_SOURCES_DIR}/crypto/objects
+  ${OPENSSL_SOURCES_DIR}/crypto/ocsp
+  ${OPENSSL_SOURCES_DIR}/crypto/pem
+  ${OPENSSL_SOURCES_DIR}/crypto/pkcs12
+  ${OPENSSL_SOURCES_DIR}/crypto/pkcs7
+  ${OPENSSL_SOURCES_DIR}/crypto/pqueue
+  ${OPENSSL_SOURCES_DIR}/crypto/rand
+  ${OPENSSL_SOURCES_DIR}/crypto/rsa
+  ${OPENSSL_SOURCES_DIR}/crypto/sha
+  ${OPENSSL_SOURCES_DIR}/crypto/srp
+  ${OPENSSL_SOURCES_DIR}/crypto/stack
+  ${OPENSSL_SOURCES_DIR}/crypto/ts
+  ${OPENSSL_SOURCES_DIR}/crypto/txt_db
+  ${OPENSSL_SOURCES_DIR}/crypto/ui
+  ${OPENSSL_SOURCES_DIR}/crypto/x509
+  ${OPENSSL_SOURCES_DIR}/crypto/x509v3
+  ${OPENSSL_SOURCES_DIR}/ssl
+  )
+
+if (ENABLE_OPENSSL_ENGINES)
+  list(APPEND OPENSSL_SOURCES_SUBDIRS
+    ${OPENSSL_SOURCES_DIR}/engines
+    )
+endif()
+
+list(APPEND OPENSSL_SOURCES_SUBDIRS
+  # EC, ECDH and ECDSA are necessary for PKCS11, and for contacting
+  # HTTPS servers that use TLS certificate encrypted with ECDSA
+  # (check the output of a recent version of the "sslscan"
+  # command). Until Orthanc <= 1.4.1, these features were only
+  # enabled if ENABLE_PKCS11 support was set to "ON".
+  # https://groups.google.com/d/msg/orthanc-users/2l-bhYIMEWg/oMmK33bYBgAJ
+  ${OPENSSL_SOURCES_DIR}/crypto/ec
+  ${OPENSSL_SOURCES_DIR}/crypto/ecdh
+  ${OPENSSL_SOURCES_DIR}/crypto/ecdsa
+  )
+
+foreach(d ${OPENSSL_SOURCES_SUBDIRS})
+  AUX_SOURCE_DIRECTORY(${d} OPENSSL_SOURCES)
+endforeach()
+
+list(REMOVE_ITEM OPENSSL_SOURCES
+  ${OPENSSL_SOURCES_DIR}/crypto/LPdir_unix.c
+  ${OPENSSL_SOURCES_DIR}/crypto/LPdir_vms.c
+  ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win.c
+  ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win32.c
+  ${OPENSSL_SOURCES_DIR}/crypto/LPdir_wince.c
+  ${OPENSSL_SOURCES_DIR}/crypto/armcap.c
+  ${OPENSSL_SOURCES_DIR}/crypto/bf/bfs.cpp
+  ${OPENSSL_SOURCES_DIR}/crypto/bio/bss_rtcp.c
+  ${OPENSSL_SOURCES_DIR}/crypto/bn/exp.c
+  ${OPENSSL_SOURCES_DIR}/crypto/conf/cnf_save.c
+  ${OPENSSL_SOURCES_DIR}/crypto/conf/test.c
+  ${OPENSSL_SOURCES_DIR}/crypto/des/des.c
+  ${OPENSSL_SOURCES_DIR}/crypto/des/des3s.cpp
+  ${OPENSSL_SOURCES_DIR}/crypto/des/des_opts.c
+  ${OPENSSL_SOURCES_DIR}/crypto/des/dess.cpp
+  ${OPENSSL_SOURCES_DIR}/crypto/des/read_pwd.c
+  ${OPENSSL_SOURCES_DIR}/crypto/des/speed.c
+  ${OPENSSL_SOURCES_DIR}/crypto/evp/e_dsa.c
+  ${OPENSSL_SOURCES_DIR}/crypto/evp/m_ripemd.c
+  ${OPENSSL_SOURCES_DIR}/crypto/lhash/lh_test.c
+  ${OPENSSL_SOURCES_DIR}/crypto/md4/md4.c
+  ${OPENSSL_SOURCES_DIR}/crypto/md4/md4s.cpp
+  ${OPENSSL_SOURCES_DIR}/crypto/md4/md4test.c
+  ${OPENSSL_SOURCES_DIR}/crypto/md5/md5s.cpp
+  ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/bio_ber.c
+  ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/pk7_enc.c
+  ${OPENSSL_SOURCES_DIR}/crypto/ppccap.c
+  ${OPENSSL_SOURCES_DIR}/crypto/rand/randtest.c
+  ${OPENSSL_SOURCES_DIR}/crypto/s390xcap.c
+  ${OPENSSL_SOURCES_DIR}/crypto/sparcv9cap.c
+  ${OPENSSL_SOURCES_DIR}/crypto/x509v3/tabtest.c
+  ${OPENSSL_SOURCES_DIR}/crypto/x509v3/v3conf.c
+  ${OPENSSL_SOURCES_DIR}/ssl/ssl_task.c
+  ${OPENSSL_SOURCES_DIR}/crypto/LPdir_nyi.c
+  ${OPENSSL_SOURCES_DIR}/crypto/aes/aes_x86core.c
+  ${OPENSSL_SOURCES_DIR}/crypto/bio/bss_dgram.c
+  ${OPENSSL_SOURCES_DIR}/crypto/bn/bntest.c
+  ${OPENSSL_SOURCES_DIR}/crypto/bn/expspeed.c
+  ${OPENSSL_SOURCES_DIR}/crypto/bn/exptest.c
+  ${OPENSSL_SOURCES_DIR}/crypto/engine/enginetest.c
+  ${OPENSSL_SOURCES_DIR}/crypto/evp/evp_test.c
+  ${OPENSSL_SOURCES_DIR}/crypto/hmac/hmactest.c
+  ${OPENSSL_SOURCES_DIR}/crypto/md5/md5.c
+  ${OPENSSL_SOURCES_DIR}/crypto/md5/md5test.c
+  ${OPENSSL_SOURCES_DIR}/crypto/o_dir_test.c
+  ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/dec.c
+  ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/enc.c
+  ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/sign.c
+  ${OPENSSL_SOURCES_DIR}/crypto/pkcs7/verify.c
+  ${OPENSSL_SOURCES_DIR}/crypto/rsa/rsa_test.c
+  ${OPENSSL_SOURCES_DIR}/crypto/sha/sha.c
+  ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1.c
+  ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1t.c
+  ${OPENSSL_SOURCES_DIR}/crypto/sha/sha1test.c
+  ${OPENSSL_SOURCES_DIR}/crypto/sha/sha256t.c
+  ${OPENSSL_SOURCES_DIR}/crypto/sha/sha512t.c
+  ${OPENSSL_SOURCES_DIR}/crypto/sha/shatest.c
+  ${OPENSSL_SOURCES_DIR}/crypto/srp/srptest.c
+
+  ${OPENSSL_SOURCES_DIR}/crypto/bn/divtest.c
+  ${OPENSSL_SOURCES_DIR}/crypto/bn/bnspeed.c
+  ${OPENSSL_SOURCES_DIR}/crypto/des/destest.c
+  ${OPENSSL_SOURCES_DIR}/crypto/dh/p192.c
+  ${OPENSSL_SOURCES_DIR}/crypto/dh/p512.c
+  ${OPENSSL_SOURCES_DIR}/crypto/dh/p1024.c
+  ${OPENSSL_SOURCES_DIR}/crypto/des/rpw.c
+  ${OPENSSL_SOURCES_DIR}/ssl/ssltest.c
+  ${OPENSSL_SOURCES_DIR}/crypto/dsa/dsagen.c
+  ${OPENSSL_SOURCES_DIR}/crypto/dsa/dsatest.c
+  ${OPENSSL_SOURCES_DIR}/crypto/dh/dhtest.c
+  ${OPENSSL_SOURCES_DIR}/crypto/pqueue/pq_test.c
+  ${OPENSSL_SOURCES_DIR}/crypto/des/ncbc_enc.c
+
+  ${OPENSSL_SOURCES_DIR}/crypto/evp/evp_extra_test.c
+  ${OPENSSL_SOURCES_DIR}/crypto/evp/verify_extra_test.c
+  ${OPENSSL_SOURCES_DIR}/crypto/x509/verify_extra_test.c
+  ${OPENSSL_SOURCES_DIR}/crypto/x509v3/v3prin.c
+  ${OPENSSL_SOURCES_DIR}/crypto/x509v3/v3nametest.c
+  ${OPENSSL_SOURCES_DIR}/crypto/constant_time_test.c
+
+  ${OPENSSL_SOURCES_DIR}/ssl/heartbeat_test.c
+  ${OPENSSL_SOURCES_DIR}/ssl/fatalerrtest.c
+  ${OPENSSL_SOURCES_DIR}/ssl/dtlstest.c
+  ${OPENSSL_SOURCES_DIR}/ssl/bad_dtls_test.c
+  ${OPENSSL_SOURCES_DIR}/ssl/clienthellotest.c
+  ${OPENSSL_SOURCES_DIR}/ssl/sslv2conftest.c
+
+  ${OPENSSL_SOURCES_DIR}/crypto/ec/ecp_nistz256.c
+  ${OPENSSL_SOURCES_DIR}/crypto/ec/ecp_nistz256_table.c
+  ${OPENSSL_SOURCES_DIR}/crypto/ec/ectest.c
+  ${OPENSSL_SOURCES_DIR}/crypto/ecdh/ecdhtest.c
+  ${OPENSSL_SOURCES_DIR}/crypto/ecdsa/ecdsatest.c
+  )
+
+
+if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows")
+  set_source_files_properties(
+    ${OPENSSL_SOURCES}
+    PROPERTIES COMPILE_DEFINITIONS
+    "OPENSSL_SYSNAME_WIN32;SO_WIN32;WIN32_LEAN_AND_MEAN;L_ENDIAN")
+
+  if (ENABLE_OPENSSL_ENGINES)
+    link_libraries(crypt32)
+  endif()
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/CMake/OpenSslConfigurationStatic-1.1.1.cmake	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,245 @@
+SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.1.1d)
+SET(OPENSSL_URL "http://orthanc.osimis.io/ThirdPartyDownloads/openssl-1.1.1d.tar.gz")
+SET(OPENSSL_MD5 "3be209000dbc7e1b95bcdf47980a3baa")
+
+if (IS_DIRECTORY "${OPENSSL_SOURCES_DIR}")
+  set(FirstRun OFF)
+else()
+  set(FirstRun ON)
+endif()
+
+DownloadPackage(${OPENSSL_MD5} ${OPENSSL_URL} "${OPENSSL_SOURCES_DIR}")
+
+if (FirstRun)
+  file(WRITE ${OPENSSL_SOURCES_DIR}/crypto/buildinf.h "
+#define DATE \"\"
+#define PLATFORM \"\"
+#define compiler_flags \"\"
+")
+  file(WRITE ${OPENSSL_SOURCES_DIR}/crypto/include/internal/bn_conf.h "")
+  file(WRITE ${OPENSSL_SOURCES_DIR}/crypto/include/internal/dso_conf.h "")
+
+  configure_file(
+    ${ORTHANC_ROOT}/Resources/Patches/openssl-1.1.1d-conf.h.in
+    ${OPENSSL_SOURCES_DIR}/include/openssl/opensslconf.h
+    )
+
+  # Apply the patches
+  execute_process(
+    COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
+    ${ORTHANC_ROOT}/Resources/Patches/openssl-1.1.1d.patch
+    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+    RESULT_VARIABLE Failure
+    )
+
+  if (Failure)
+    message(FATAL_ERROR "Error while patching a file")
+  endif()
+else()
+  message("The patches for OpenSSL have already been applied")
+endif()
+
+add_definitions(
+  -DOPENSSL_THREADS
+  -DOPENSSL_IA32_SSE2
+  -DOPENSSL_NO_ASM
+  -DOPENSSL_NO_DYNAMIC_ENGINE
+  -DOPENSSL_NO_DEVCRYPTOENG
+
+  -DOPENSSL_NO_BF 
+  -DOPENSSL_NO_CAMELLIA
+  -DOPENSSL_NO_CAST 
+  -DOPENSSL_NO_EC_NISTP_64_GCC_128
+  -DOPENSSL_NO_GMP
+  -DOPENSSL_NO_GOST
+  -DOPENSSL_NO_HW
+  -DOPENSSL_NO_JPAKE
+  -DOPENSSL_NO_IDEA
+  -DOPENSSL_NO_KRB5 
+  -DOPENSSL_NO_MD2 
+  -DOPENSSL_NO_MDC2 
+  #-DOPENSSL_NO_MD4   # MD4 is necessary for MariaDB/MySQL client
+  -DOPENSSL_NO_RC2 
+  -DOPENSSL_NO_RC4 
+  -DOPENSSL_NO_RC5 
+  -DOPENSSL_NO_RFC3779
+  -DOPENSSL_NO_SCTP
+  -DOPENSSL_NO_STORE
+  -DOPENSSL_NO_SEED
+  -DOPENSSL_NO_WHIRLPOOL
+  -DOPENSSL_NO_RIPEMD
+  -DOPENSSL_NO_AFALGENG
+
+  -DOPENSSLDIR="/usr/local/ssl"
+  )
+
+
+include_directories(
+  ${OPENSSL_SOURCES_DIR}
+  ${OPENSSL_SOURCES_DIR}/crypto
+  ${OPENSSL_SOURCES_DIR}/crypto/asn1
+  ${OPENSSL_SOURCES_DIR}/crypto/ec/curve448
+  ${OPENSSL_SOURCES_DIR}/crypto/ec/curve448/arch_32
+  ${OPENSSL_SOURCES_DIR}/crypto/evp
+  ${OPENSSL_SOURCES_DIR}/crypto/include
+  ${OPENSSL_SOURCES_DIR}/crypto/modes
+  ${OPENSSL_SOURCES_DIR}/include
+  )
+
+
+set(OPENSSL_SOURCES_SUBDIRS
+  ${OPENSSL_SOURCES_DIR}/crypto
+  ${OPENSSL_SOURCES_DIR}/crypto/aes
+  ${OPENSSL_SOURCES_DIR}/crypto/aria
+  ${OPENSSL_SOURCES_DIR}/crypto/asn1
+  ${OPENSSL_SOURCES_DIR}/crypto/async
+  ${OPENSSL_SOURCES_DIR}/crypto/async/arch
+  ${OPENSSL_SOURCES_DIR}/crypto/bio
+  ${OPENSSL_SOURCES_DIR}/crypto/blake2
+  ${OPENSSL_SOURCES_DIR}/crypto/bn
+  ${OPENSSL_SOURCES_DIR}/crypto/buffer
+  ${OPENSSL_SOURCES_DIR}/crypto/chacha
+  ${OPENSSL_SOURCES_DIR}/crypto/cmac
+  ${OPENSSL_SOURCES_DIR}/crypto/cms
+  ${OPENSSL_SOURCES_DIR}/crypto/comp
+  ${OPENSSL_SOURCES_DIR}/crypto/conf
+  ${OPENSSL_SOURCES_DIR}/crypto/ct
+  ${OPENSSL_SOURCES_DIR}/crypto/des
+  ${OPENSSL_SOURCES_DIR}/crypto/dh
+  ${OPENSSL_SOURCES_DIR}/crypto/dsa
+  ${OPENSSL_SOURCES_DIR}/crypto/dso
+  ${OPENSSL_SOURCES_DIR}/crypto/ec
+  ${OPENSSL_SOURCES_DIR}/crypto/ec/curve448
+  ${OPENSSL_SOURCES_DIR}/crypto/ec/curve448/arch_32
+  ${OPENSSL_SOURCES_DIR}/crypto/err
+  ${OPENSSL_SOURCES_DIR}/crypto/evp
+  ${OPENSSL_SOURCES_DIR}/crypto/hmac
+  ${OPENSSL_SOURCES_DIR}/crypto/kdf
+  ${OPENSSL_SOURCES_DIR}/crypto/lhash
+  ${OPENSSL_SOURCES_DIR}/crypto/md4
+  ${OPENSSL_SOURCES_DIR}/crypto/md5
+  ${OPENSSL_SOURCES_DIR}/crypto/modes
+  ${OPENSSL_SOURCES_DIR}/crypto/objects
+  ${OPENSSL_SOURCES_DIR}/crypto/ocsp
+  ${OPENSSL_SOURCES_DIR}/crypto/pem
+  ${OPENSSL_SOURCES_DIR}/crypto/pkcs12
+  ${OPENSSL_SOURCES_DIR}/crypto/pkcs7
+  ${OPENSSL_SOURCES_DIR}/crypto/poly1305
+  ${OPENSSL_SOURCES_DIR}/crypto/pqueue
+  ${OPENSSL_SOURCES_DIR}/crypto/rand
+  ${OPENSSL_SOURCES_DIR}/crypto/ripemd
+  ${OPENSSL_SOURCES_DIR}/crypto/rsa
+  ${OPENSSL_SOURCES_DIR}/crypto/sha
+  ${OPENSSL_SOURCES_DIR}/crypto/siphash
+  ${OPENSSL_SOURCES_DIR}/crypto/sm2
+  ${OPENSSL_SOURCES_DIR}/crypto/sm3
+  ${OPENSSL_SOURCES_DIR}/crypto/sm4
+  ${OPENSSL_SOURCES_DIR}/crypto/srp
+  ${OPENSSL_SOURCES_DIR}/crypto/stack
+  ${OPENSSL_SOURCES_DIR}/crypto/store
+  ${OPENSSL_SOURCES_DIR}/crypto/ts
+  ${OPENSSL_SOURCES_DIR}/crypto/txt_db
+  ${OPENSSL_SOURCES_DIR}/crypto/ui
+  ${OPENSSL_SOURCES_DIR}/crypto/x509
+  ${OPENSSL_SOURCES_DIR}/crypto/x509v3
+  ${OPENSSL_SOURCES_DIR}/ssl
+  ${OPENSSL_SOURCES_DIR}/ssl/record
+  ${OPENSSL_SOURCES_DIR}/ssl/statem
+  )
+
+if (ENABLE_OPENSSL_ENGINES)
+  add_definitions(
+    #-DENGINESDIR="/usr/local/lib/engines-1.1"  # On GNU/Linux
+    -DENGINESDIR="."
+    )
+
+  list(APPEND OPENSSL_SOURCES_SUBDIRS
+    ${OPENSSL_SOURCES_DIR}/engines
+    ${OPENSSL_SOURCES_DIR}/crypto/engine
+    )
+else()
+  add_definitions(-DOPENSSL_NO_ENGINE)
+endif()
+
+list(APPEND OPENSSL_SOURCES_SUBDIRS
+  # EC, ECDH and ECDSA are necessary for PKCS11, and for contacting
+  # HTTPS servers that use TLS certificate encrypted with ECDSA
+  # (check the output of a recent version of the "sslscan"
+  # command). Until Orthanc <= 1.4.1, these features were only
+  # enabled if ENABLE_PKCS11 support was set to "ON".
+  # https://groups.google.com/d/msg/orthanc-users/2l-bhYIMEWg/oMmK33bYBgAJ
+  ${OPENSSL_SOURCES_DIR}/crypto/ec
+  ${OPENSSL_SOURCES_DIR}/crypto/ecdh
+  ${OPENSSL_SOURCES_DIR}/crypto/ecdsa
+  )
+
+foreach(d ${OPENSSL_SOURCES_SUBDIRS})
+  AUX_SOURCE_DIRECTORY(${d} OPENSSL_SOURCES)
+endforeach()
+
+list(REMOVE_ITEM OPENSSL_SOURCES
+  ${OPENSSL_SOURCES_DIR}/crypto/LPdir_nyi.c
+  ${OPENSSL_SOURCES_DIR}/crypto/LPdir_unix.c
+  ${OPENSSL_SOURCES_DIR}/crypto/LPdir_vms.c
+  ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win.c
+  ${OPENSSL_SOURCES_DIR}/crypto/LPdir_win32.c
+  ${OPENSSL_SOURCES_DIR}/crypto/LPdir_wince.c
+  ${OPENSSL_SOURCES_DIR}/crypto/aes/aes_x86core.c
+  ${OPENSSL_SOURCES_DIR}/crypto/armcap.c
+  ${OPENSSL_SOURCES_DIR}/crypto/bio/bss_dgram.c
+  ${OPENSSL_SOURCES_DIR}/crypto/des/ncbc_enc.c
+  ${OPENSSL_SOURCES_DIR}/crypto/ec/ecp_nistz256.c
+  ${OPENSSL_SOURCES_DIR}/crypto/ec/ecp_nistz256_table.c
+  ${OPENSSL_SOURCES_DIR}/crypto/engine/eng_devcrypto.c
+  ${OPENSSL_SOURCES_DIR}/crypto/poly1305/poly1305_base2_44.c  # Cannot be compiled with MinGW
+  ${OPENSSL_SOURCES_DIR}/crypto/poly1305/poly1305_ieee754.c  # Cannot be compiled with MinGW
+  ${OPENSSL_SOURCES_DIR}/crypto/ppccap.c
+  ${OPENSSL_SOURCES_DIR}/crypto/s390xcap.c
+  ${OPENSSL_SOURCES_DIR}/crypto/sparcv9cap.c
+  ${OPENSSL_SOURCES_DIR}/engines/e_afalg.c  # Cannot be compiled with MinGW
+  )
+
+# Check out "${OPENSSL_SOURCES_DIR}/Configurations/README": "This is
+# default if no option is specified, it works on any supported
+# system." It is mandatory to define it as a macro, as it is used by
+# all the source files that include OpenSSL (e.g. "Core/Toolbox.cpp"
+# or curl)
+add_definitions(-DTHIRTY_TWO_BIT)
+
+
+if (NOT CMAKE_COMPILER_IS_GNUCXX OR
+    "${CMAKE_SYSTEM_NAME}" STREQUAL "Windows" OR
+    "${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+  # Disable the use of a gcc extension, that is neither available on
+  # MinGW, nor on LSB
+  add_definitions(
+    -DOPENSSL_NO_CRYPTO_MDEBUG_BACKTRACE
+    )
+endif()
+
+
+if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows")
+  set(OPENSSL_DEFINITIONS
+    "${OPENSSL_DEFINITIONS};OPENSSL_SYSNAME_WIN32;SO_WIN32;WIN32_LEAN_AND_MEAN;L_ENDIAN;NO_WINDOWS_BRAINDEATH")
+  
+  if (ENABLE_OPENSSL_ENGINES)
+    link_libraries(crypt32)
+  endif()
+
+  add_definitions(
+    -DOPENSSL_RAND_SEED_OS  # ${OPENSSL_SOURCES_DIR}/crypto/rand/rand_win.c
+    )
+ 
+elseif ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+  # In order for "crypto/mem_sec.c" to compile on LSB
+  add_definitions(
+    -DOPENSSL_NO_SECURE_MEMORY
+    )
+endif()
+
+
+set_source_files_properties(
+  ${OPENSSL_SOURCES}
+    PROPERTIES COMPILE_DEFINITIONS
+    "${OPENSSL_DEFINITIONS};DSO_NONE"
+    )
--- a/Resources/CMake/OrthancFrameworkConfiguration.cmake	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/CMake/OrthancFrameworkConfiguration.cmake	Thu Mar 19 11:48:30 2020 +0100
@@ -107,6 +107,7 @@
   add_definitions(
     -DORTHANC_ENABLE_DCMTK=0
     -DORTHANC_ENABLE_DCMTK_NETWORKING=0
+    -DORTHANC_ENABLE_DCMTK_TRANSCODING=0
     )
   unset(DCMTK_DICTIONARY_DIR CACHE)
   unset(DCMTK_VERSION CACHE)
@@ -123,6 +124,8 @@
 
 set(ORTHANC_CORE_SOURCES_INTERNAL
   ${ORTHANC_ROOT}/Core/Cache/MemoryCache.cpp
+  ${ORTHANC_ROOT}/Core/Cache/MemoryObjectCache.cpp
+  ${ORTHANC_ROOT}/Core/Cache/MemoryStringCache.cpp
   ${ORTHANC_ROOT}/Core/ChunkedBuffer.cpp
   ${ORTHANC_ROOT}/Core/DicomFormat/DicomTag.cpp
   ${ORTHANC_ROOT}/Core/EnumerationDictionary.h
@@ -461,6 +464,7 @@
     ${ORTHANC_ROOT}/Core/DicomParsing/DicomModification.cpp
     ${ORTHANC_ROOT}/Core/DicomParsing/DicomWebJsonVisitor.cpp
     ${ORTHANC_ROOT}/Core/DicomParsing/FromDcmtkBridge.cpp
+    ${ORTHANC_ROOT}/Core/DicomParsing/ParsedDicomDir.cpp
     ${ORTHANC_ROOT}/Core/DicomParsing/ParsedDicomFile.cpp
     ${ORTHANC_ROOT}/Core/DicomParsing/ToDcmtkBridge.cpp
 
@@ -491,6 +495,13 @@
     add_definitions(-DORTHANC_ENABLE_DCMTK_NETWORKING=0)
   endif()
 
+  # New in Orthanc 1.6.0
+  if (ENABLE_DCMTK_TRANSCODING)
+    add_definitions(-DORTHANC_ENABLE_DCMTK_TRANSCODING=1)
+  else()
+    add_definitions(-DORTHANC_ENABLE_DCMTK_TRANSCODING=0)
+  endif()
+
   if (STANDALONE_BUILD AND NOT HAS_EMBEDDED_RESOURCES)
     EmbedResources(
       ${DCMTK_DICTIONARIES}
--- a/Resources/CMake/OrthancFrameworkParameters.cmake	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/CMake/OrthancFrameworkParameters.cmake	Thu Mar 19 11:48:30 2020 +0100
@@ -3,7 +3,7 @@
 #####################################################################
 
 # Version of the build, should always be "mainline" except in release branches
-set(ORTHANC_VERSION "1.5.8")
+set(ORTHANC_VERSION "1.6.0")
 
 # Version of the database schema. History:
 #   * Orthanc 0.1.0 -> Orthanc 0.3.0 = no versioning
@@ -17,7 +17,7 @@
 # Version of the Orthanc API, can be retrieved from "/system" URI in
 # order to check whether new URI endpoints are available even if using
 # the mainline version of Orthanc
-set(ORTHANC_API_VERSION "4")
+set(ORTHANC_API_VERSION "5")
 
 
 #####################################################################
@@ -57,7 +57,7 @@
 
 # Parameters specific to DCMTK
 set(DCMTK_DICTIONARY_DIR "" CACHE PATH "Directory containing the DCMTK dictionaries \"dicom.dic\" and \"private.dic\" (only when using system version of DCMTK)")
-set(DCMTK_STATIC_VERSION "3.6.4" CACHE STRING "Version of DCMTK to be used in static builds (can be \"3.6.0\", \"3.6.2\", or \"3.6.4\")")
+set(DCMTK_STATIC_VERSION "3.6.5" CACHE STRING "Version of DCMTK to be used in static builds (can be \"3.6.0\", \"3.6.2\", \"3.6.4\", or \"3.6.5\")")
 set(USE_DCMTK_362_PRIVATE_DIC ON CACHE BOOL "Use the dictionary of private tags from DCMTK 3.6.2 if using DCMTK 3.6.0")
 set(USE_SYSTEM_DCMTK ON CACHE BOOL "Use the system version of DCMTK")
 set(ENABLE_DCMTK_LOG ON CACHE BOOL "Enable logging internal to DCMTK")
@@ -73,6 +73,7 @@
 set(USE_LEGACY_LIBICU OFF CACHE BOOL "Use icu icu4c-58_2, latest version not requiring a C++11 compiler (for LSB and old versions of Visual Studio)")
 set(MSVC_MULTIPLE_PROCESSES OFF CACHE BOOL "Add the /MP option to build with multiple processes if using Visual Studio")
 set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND OFF CACHE BOOL "Sets the compiler flags required to use the LLVM Web Assembly backend in emscripten")
+set(OPENSSL_STATIC_VERSION "1.1.1" CACHE STRING "Version of OpenSSL to be used in static builds (can be \"1.0.2\", or \"1.1.1\")")
 
 mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE)
 mark_as_advanced(SYSTEM_MONGOOSE_USE_CALLBACKS)
@@ -102,6 +103,7 @@
 set(ENABLE_WEB_SERVER OFF CACHE INTERNAL "Enable embedded Web server")
 set(ENABLE_DCMTK OFF CACHE INTERNAL "Enable DCMTK")
 set(ENABLE_DCMTK_NETWORKING OFF CACHE INTERNAL "Enable DICOM networking in DCMTK")
+set(ENABLE_DCMTK_TRANSCODING OFF CACHE INTERNAL "Enable DICOM transcoding in DCMTK")
 set(ENABLE_OPENSSL_ENGINES OFF CACHE INTERNAL "Enable support of engines in OpenSSL")
 
 set(HAS_EMBEDDED_RESOURCES OFF CACHE INTERNAL
--- a/Resources/Configuration.json	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Configuration.json	Thu Mar 19 11:48:30 2020 +0100
@@ -96,7 +96,8 @@
   // receive files or to do query/retrieve through the DICOM protocol.
   "DicomServerEnabled" : true,
 
-  // The DICOM Application Entity Title
+  // The DICOM Application Entity Title (cannot be longer than 16
+  // characters)
   "DicomAet" : "ORTHANC",
 
   // Check whether the called AET corresponds to the AET of Orthanc
@@ -123,6 +124,7 @@
   "JpipTransferSyntaxAccepted"         : true,
   "Mpeg2TransferSyntaxAccepted"        : true,
   "RleTransferSyntaxAccepted"          : true,
+  "Mpeg4TransferSyntaxAccepted"        : true,  // New in Orthanc 1.6.0
 
   // Whether Orthanc accepts to act as C-Store SCP for unknown storage
   // SOP classes (aka. "promiscuous mode")
@@ -175,8 +177,12 @@
     /**
      * Uncommenting the following line would enable Orthanc to
      * connect to an instance of the "storescp" open-source DICOM
-     * store (shipped in the DCMTK distribution) started by the
-     * command line "storescp 2000".
+     * store (shipped in the DCMTK distribution), as started by the
+     * command line "storescp 2000". The first parameter is the
+     * AET of the remote modality (cannot be longer than 16 
+     * characters), the second one is the remote network address,
+     * and the third one is the TCP port number corresponding
+     * to the DICOM protocol on the remote modality (usually 104).
      **/
     // "sample" : [ "STORESCP", "127.0.0.1", 2000 ]
 
@@ -189,34 +195,34 @@
      * - "GenericNoUniversalWildcard" (to replace "*" by "" in all fields
      *   in outgoing C-Find SCU requests originating from Orthanc),
      * - "StoreScp" (storescp tool from DCMTK),
-     * - "ClearCanvas",
-     * - "Dcm4Chee",
      * - "Vitrea",
      * - "GE" (Enterprise Archive, MRI consoles and Advantage Workstation
      *   from GE Healthcare).
      *
      * This parameter is case-sensitive.
      **/
-    // "clearcanvas" : [ "CLEARCANVAS", "192.168.1.1", 104, "ClearCanvas" ]
+    // "vitrea" : [ "VITREA", "192.168.1.1", 104, "Vitrea" ]
 
     /**
      * By default, the Orthanc SCP accepts all DICOM commands (C-ECHO,
-     * C-STORE, C-FIND, C-MOVE) issued by the registered remote SCU
-     * modalities. Starting with Orthanc 1.5.0, it is possible to
-     * specify which DICOM commands are allowed, separately for each
-     * remote modality, using the syntax below. The "AllowEcho" (resp.
-     * "AllowStore") option only has an effect respectively if global
-     * option "DicomAlwaysAllowEcho" (resp. "DicomAlwaysAllowStore")
-     * is set to false.
+     * C-STORE, C-FIND, C-MOVE, and storage commitment) issued by the
+     * registered remote SCU modalities. Starting with Orthanc 1.5.0,
+     * it is possible to specify which DICOM commands are allowed,
+     * separately for each remote modality, using the syntax
+     * below. The "AllowEcho" (resp.  "AllowStore") option only has an
+     * effect respectively if global option "DicomAlwaysAllowEcho"
+     * (resp. "DicomAlwaysAllowStore") is set to false.
      **/
     //"untrusted" : {
     //  "AET" : "ORTHANC",
     //  "Port" : 104,
     //  "Host" : "127.0.0.1",
+    //  "Manufacturer" : "Generic",
     //  "AllowEcho" : false,
     //  "AllowFind" : false,
     //  "AllowMove" : false,
-    //  "AllowStore" : true
+    //  "AllowStore" : true,
+    //  "AllowStorageCommitment" : false  // new in 1.6.0
     //}
   },
 
@@ -517,5 +523,14 @@
   // to option "request_timeout_ms" of Mongoose/Civetweb. It will set
   // the socket options "SO_RCVTIMEO" and "SO_SNDTIMEO" to the
   // specified value.
-  "HttpRequestTimeout" : 30
+  "HttpRequestTimeout" : 30,
+
+  // Set the default private creator that is used by Orthanc when it
+  // looks for a private tag in its dictionary (cf. "Dictionary"
+  // option), or when it creates/modifies a DICOM file (new in Orthanc 1.6.0).
+  "DefaultPrivateCreator" : "",
+
+  // Maximum number of storage commitment reports (i.e. received from
+  // remote modalities) to be kept in memory (new in Orthanc 1.6.0).
+  "StorageCommitmentReportsSize" : 100
 }
--- a/Resources/DicomConformanceStatement.py	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/DicomConformanceStatement.py	Thu Mar 19 11:48:30 2020 +0100
@@ -3,7 +3,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/DicomTransferSyntaxes.json	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,376 @@
+[
+  {
+    "UID" : "1.2.840.10008.1.2",
+    "Name" : "Implicit VR Little Endian",
+    "Value" : "LittleEndianImplicit",
+    "Retired" : false,
+    "DCMTK" : "EXS_LittleEndianImplicit",
+    "GDCM" : "gdcm::TransferSyntax::ImplicitVRLittleEndian"
+  },
+  
+  {
+    "UID" : "1.2.840.10008.1.2.1",
+    "Name" : "Explicit VR Little Endian",
+    "Value" : "LittleEndianExplicit",
+    "Retired" : false,
+    "DCMTK" : "EXS_LittleEndianExplicit",
+    "GDCM" : "gdcm::TransferSyntax::ExplicitVRLittleEndian"
+  },
+  
+  {
+    "UID" : "1.2.840.10008.1.2.1.99",
+    "Name" : "Deflated Explicit VR Little Endian",
+    "Value" : "DeflatedLittleEndianExplicit",
+    "Retired" : false,
+    "DCMTK" : "EXS_DeflatedLittleEndianExplicit"
+  },
+  
+  {
+    "UID" : "1.2.840.10008.1.2.2",
+    "Name" : "Explicit VR Big Endian",
+    "Value" : "BigEndianExplicit",
+    "Retired" : false,
+    "DCMTK" : "EXS_BigEndianExplicit"
+  },
+  
+  {
+    "UID" : "1.2.840.10008.1.2.4.50",
+    "Name" : "JPEG Baseline (process 1, lossy)",
+    "Value" : "JPEGProcess1",
+    "Retired" : false,
+    "Note" : "Default Transfer Syntax for Lossy JPEG 8-bit Image Compression",
+    "DCMTK" : "EXS_JPEGProcess1",
+    "DCMTK360" : "EXS_JPEGProcess1TransferSyntax",
+    "GDCM" : "gdcm::TransferSyntax::JPEGBaselineProcess1"
+  },
+  
+  {
+    "UID" : "1.2.840.10008.1.2.4.51",
+    "Name" : "JPEG Extended Sequential (processes 2 & 4)",
+    "Value" : "JPEGProcess2_4",
+    "Retired" : false,
+    "Note" : "Default Transfer Syntax for Lossy JPEG (lossy, 8/12 bit), 12-bit Image Compression (Process 4 only)",
+    "DCMTK" : "EXS_JPEGProcess2_4",
+    "DCMTK360" : "EXS_JPEGProcess2_4TransferSyntax",
+    "GDCM" : "gdcm::TransferSyntax::JPEGExtendedProcess2_4"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.52",
+    "Name" : "JPEG Extended Sequential (lossy, 8/12 bit), arithmetic coding",
+    "Value" : "JPEGProcess3_5",
+    "Retired" : true,
+    "DCMTK" : "EXS_JPEGProcess3_5",
+    "DCMTK360" : "EXS_JPEGProcess3_5TransferSyntax"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.53",
+    "Name" : "JPEG Spectral Selection, Nonhierarchical (lossy, 8/12 bit)",
+    "Value" : "JPEGProcess6_8",
+    "Retired" : true,
+    "DCMTK" : "EXS_JPEGProcess6_8",
+    "DCMTK360" : "EXS_JPEGProcess6_8TransferSyntax"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.54",
+    "Name" : "JPEG Spectral Selection, Nonhierarchical (lossy, 8/12 bit), arithmetic coding",
+    "Value" : "JPEGProcess7_9",
+    "Retired" : true,
+    "DCMTK" : "EXS_JPEGProcess7_9",
+    "DCMTK360" : "EXS_JPEGProcess7_9TransferSyntax"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.55",
+    "Name" : "JPEG Full Progression, Nonhierarchical (lossy, 8/12 bit)",
+    "Value" : "JPEGProcess10_12",
+    "Retired" : true,
+    "DCMTK" : "EXS_JPEGProcess10_12",
+    "DCMTK360" : "EXS_JPEGProcess10_12TransferSyntax"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.56",
+    "Name" : "JPEG Full Progression, Nonhierarchical (lossy, 8/12 bit), arithmetic coding",
+    "Value" : "JPEGProcess11_13",
+    "Retired" : true,
+    "DCMTK" : "EXS_JPEGProcess11_13",
+    "DCMTK360" : "EXS_JPEGProcess11_13TransferSyntax"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.57",
+    "Name" : "JPEG Lossless, Nonhierarchical with any selection value (process 14)",
+    "Value" : "JPEGProcess14",
+    "Retired" : false,
+    "DCMTK" : "EXS_JPEGProcess14",
+    "DCMTK360" : "EXS_JPEGProcess14TransferSyntax",
+    "GDCM" : "gdcm::TransferSyntax::JPEGLosslessProcess14"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.58",
+    "Name" : "JPEG Lossless with any selection value, arithmetic coding",
+    "Value" : "JPEGProcess15",
+    "Retired" : true,
+    "DCMTK" : "EXS_JPEGProcess15",
+    "DCMTK360" : "EXS_JPEGProcess15TransferSyntax"
+  },
+  
+  {
+    "UID" : "1.2.840.10008.1.2.4.59",
+    "Name" : "JPEG Extended Sequential, Hierarchical (lossy, 8/12 bit)",
+    "Value" : "JPEGProcess16_18",
+    "Retired" : true,
+    "DCMTK" : "EXS_JPEGProcess16_18",
+    "DCMTK360" : "EXS_JPEGProcess16_18TransferSyntax"
+  },
+  
+  {
+    "UID" : "1.2.840.10008.1.2.4.60",
+    "Name" : "JPEG Extended Sequential, Hierarchical (lossy, 8/12 bit), arithmetic coding",
+    "Value" : "JPEGProcess17_19",
+    "Retired" : true,
+    "DCMTK" : "EXS_JPEGProcess17_19",
+    "DCMTK360" : "EXS_JPEGProcess17_19TransferSyntax"
+  },
+  
+  {
+    "UID" : "1.2.840.10008.1.2.4.61",
+    "Name" : "JPEG Spectral Selection, Hierarchical (lossy, 8/12 bit)",
+    "Value" : "JPEGProcess20_22",
+    "Retired" : true,
+    "DCMTK" : "EXS_JPEGProcess20_22",
+    "DCMTK360" : "EXS_JPEGProcess20_22TransferSyntax"
+  },
+  
+  {
+    "UID" : "1.2.840.10008.1.2.4.62",
+    "Name" : "JPEG Spectral Selection, Hierarchical (lossy, 8/12 bit), arithmetic coding",
+    "Value" : "JPEGProcess21_23",
+    "Retired" : true,
+    "DCMTK" : "EXS_JPEGProcess21_23",
+    "DCMTK360" : "EXS_JPEGProcess21_23TransferSyntax"
+  },
+  
+  {
+    "UID" : "1.2.840.10008.1.2.4.63",
+    "Name" : "JPEG Full Progression, Hierarchical (lossy, 8/12 bit)",
+    "Value" : "JPEGProcess24_26",
+    "Retired" : true,
+    "DCMTK" : "EXS_JPEGProcess24_26",
+    "DCMTK360" : "EXS_JPEGProcess24_26TransferSyntax"
+  },
+  
+  {
+    "UID" : "1.2.840.10008.1.2.4.64",
+    "Name" : "JPEG Full Progression, Hierarchical (lossy, 8/12 bit), arithmetic coding",
+    "Value" : "JPEGProcess25_27",
+    "Retired" : true,
+    "DCMTK" : "EXS_JPEGProcess25_27",
+    "DCMTK360" : "EXS_JPEGProcess25_27TransferSyntax"
+  },
+  
+  {
+    "UID" : "1.2.840.10008.1.2.4.65",
+    "Name" : "JPEG Lossless, Hierarchical",
+    "Value" : "JPEGProcess28",
+    "Retired" : true,
+    "DCMTK" : "EXS_JPEGProcess28",
+    "DCMTK360" : "EXS_JPEGProcess28TransferSyntax"
+  },
+  
+  {
+    "UID" : "1.2.840.10008.1.2.4.66",
+    "Name" : "JPEG Lossless, Hierarchical, arithmetic coding",
+    "Value" : "JPEGProcess29",
+    "Retired" : true,
+    "DCMTK" : "EXS_JPEGProcess29",
+    "DCMTK360" : "EXS_JPEGProcess29TransferSyntax"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.70",
+    "Name" : "JPEG Lossless, Nonhierarchical, First-Order Prediction (Processes 14 [Selection Value 1])",
+    "Value" : "JPEGProcess14SV1",
+    "Retired" : false,
+    "Note" : "Default Transfer Syntax for Lossless JPEG Image Compression",
+    "DCMTK" : "EXS_JPEGProcess14SV1",
+    "DCMTK360" : "EXS_JPEGProcess14SV1TransferSyntax",
+    "GDCM" : "gdcm::TransferSyntax::JPEGLosslessProcess14_1"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.80",
+    "Name" : "JPEG-LS (lossless)",
+    "Value" : "JPEGLSLossless",
+    "Retired" : false,
+    "DCMTK" : "EXS_JPEGLSLossless",
+    "GDCM" : "gdcm::TransferSyntax::JPEGLSLossless"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.81",
+    "Name" : "JPEG-LS (lossy or near-lossless)",
+    "Value" : "JPEGLSLossy",
+    "Retired" : false,
+    "DCMTK" : "EXS_JPEGLSLossy",
+    "GDCM" : "gdcm::TransferSyntax::JPEGLSNearLossless"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.90",
+    "Name" : "JPEG 2000 (lossless)",
+    "Value" : "JPEG2000LosslessOnly",
+    "Retired" : false,
+    "DCMTK" : "EXS_JPEG2000LosslessOnly",
+    "GDCM" : "gdcm::TransferSyntax::JPEG2000Lossless"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.91",
+    "Name" : "JPEG 2000 (lossless or lossy)",
+    "Value" : "JPEG2000",
+    "Retired" : false,
+    "DCMTK" : "EXS_JPEG2000",
+    "GDCM" : "gdcm::TransferSyntax::JPEG2000"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.92",
+    "Name" : "JPEG 2000 part 2 multicomponent extensions (lossless)",
+    "Value" : "JPEG2000MulticomponentLosslessOnly",
+    "Retired" : false,
+    "DCMTK" : "EXS_JPEG2000MulticomponentLosslessOnly",
+    "GDCM" : "gdcm::TransferSyntax::JPEG2000Part2Lossless"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.93",
+    "Name" : "JPEG 2000 part 2 multicomponent extensions (lossless or lossy)",
+    "Value" : "JPEG2000Multicomponent",
+    "Retired" : false,
+    "DCMTK" : "EXS_JPEG2000Multicomponent",
+    "GDCM" : "gdcm::TransferSyntax::JPEG2000Part2"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.94",
+    "Name" : "JPIP Referenced",
+    "Value" : "JPIPReferenced",
+    "Retired" : false,
+    "DCMTK" : "EXS_JPIPReferenced"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.95",
+    "Name" : "JPIP Referenced Deflate",
+    "Value" : "JPIPReferencedDeflate",
+    "Retired" : false,
+    "DCMTK" : "EXS_JPIPReferencedDeflate"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.100",
+    "Name" : "MPEG2 Main Profile at Main Level",
+    "Value" : "MPEG2MainProfileAtMainLevel",
+    "Retired" : false,
+    "DCMTK" : "EXS_MPEG2MainProfileAtMainLevel"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.101",
+    "Name" : "MPEG2 Main Profile at High Level",
+    "Value" : "MPEG2MainProfileAtHighLevel",
+    "Retired" : false,
+    "DCMTK" : "EXS_MPEG2MainProfileAtHighLevel"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.102",
+    "Name" : "MPEG4 High Profile / Level 4.1",
+    "Value" : "MPEG4HighProfileLevel4_1",
+    "Retired" : false,
+    "DCMTK" : "EXS_MPEG4HighProfileLevel4_1",
+    "SinceDCMTK" : "361"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.103",
+    "Name" : "MPEG4 BD-compatible High Profile / Level 4.1",
+    "Value" : "MPEG4BDcompatibleHighProfileLevel4_1",
+    "Retired" : false,
+    "DCMTK" : "EXS_MPEG4BDcompatibleHighProfileLevel4_1",
+    "SinceDCMTK" : "361"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.104",
+    "Name" : "MPEG4 High Profile / Level 4.2 For 2D Video",
+    "Value" : "MPEG4HighProfileLevel4_2_For2DVideo",
+    "Retired" : false,
+    "DCMTK" : "EXS_MPEG4HighProfileLevel4_2_For2DVideo",
+    "SinceDCMTK" : "361"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.105",
+    "Name" : "MPEG4 High Profile / Level 4.2 For 3D Video",
+    "Value" : "MPEG4HighProfileLevel4_2_For3DVideo",
+    "Retired" : false,
+    "DCMTK" : "EXS_MPEG4HighProfileLevel4_2_For3DVideo",
+    "SinceDCMTK" : "361"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.106",
+    "Name" : "1.2.840.10008.1.2.4.106",
+    "Value" : "MPEG4StereoHighProfileLevel4_2",
+    "Retired" : false,
+    "DCMTK" : "EXS_MPEG4StereoHighProfileLevel4_2",
+    "SinceDCMTK" : "361"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.107",
+    "Name" : "HEVC/H.265 Main Profile / Level 5.1",
+    "Value" : "HEVCMainProfileLevel5_1",
+    "Retired" : false,
+    "DCMTK" : "EXS_HEVCMainProfileLevel5_1",
+    "SinceDCMTK" : "362"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.4.108",
+    "Name" : "HEVC/H.265 Main 10 Profile / Level 5.1",
+    "Value" : "HEVCMain10ProfileLevel5_1",
+    "Retired" : false,
+    "DCMTK" : "EXS_HEVCMain10ProfileLevel5_1",
+    "SinceDCMTK" : "362"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.5",
+    "Name" : "RLE - Run Length Encoding (lossless)",
+    "Value" : "RLELossless",
+    "Retired" : false,
+    "DCMTK" : "EXS_RLELossless",
+    "GDCM" : "gdcm::TransferSyntax::RLELossless"
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.6.1",
+    "Name" : "RFC 2557 MIME Encapsulation",
+    "Value" : "RFC2557MimeEncapsulation",
+    "Retired" : true
+  },
+
+  {
+    "UID" : "1.2.840.10008.1.2.6.2",
+    "Name" : "XML Encoding",
+    "Value" : "XML",
+    "Retired" : true
+  }
+]
--- a/Resources/DownloadOrthancFramework.cmake	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/DownloadOrthancFramework.cmake	Thu Mar 19 11:48:30 2020 +0100
@@ -1,7 +1,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
@@ -110,6 +110,19 @@
         set(ORTHANC_FRAMEWORK_MD5 "3c29de1e289b5472342947168f0105c0")
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.7")
         set(ORTHANC_FRAMEWORK_MD5 "e1b76f01116d9b5d4ac8cc39980560e3")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.5.8")
+        set(ORTHANC_FRAMEWORK_MD5 "82323e8c49a667f658a3639ea4dbc336")
+
+      # Below this point are development snapshots that were used to
+      # release some plugin, before an official release of the Orthanc
+      # framework was available. Here is the command to be used to
+      # generate a proper archive:
+      #
+      #   $ hg archive /tmp/Orthanc-`hg id -i | sed 's/\+//'`.tar.gz
+      #
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "ae0e3fd609df")
+        # DICOMweb 1.1 (framework pre-1.6.0)
+        set(ORTHANC_FRAMEWORK_MD5 "7e09e9b530a2f527854f0b782d7e0645")
       endif()
     endif()
   endif()
--- a/Resources/EmbedResources.py	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/EmbedResources.py	Thu Mar 19 11:48:30 2020 +0100
@@ -3,7 +3,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
@@ -101,6 +101,8 @@
         # The resource is a directory: Recursively explore its files
         content = {}
         for root, dirs, files in os.walk(pathName):
+            dirs.sort()
+            files.sort()
             base = os.path.relpath(root, pathName)
 
             # Fix issue #24 (Build fails on OSX when directory has .DS_Store files):
--- a/Resources/ErrorCodes.json	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/ErrorCodes.json	Thu Mar 19 11:48:30 2020 +0100
@@ -547,6 +547,11 @@
     "Name": "AlreadyExistingTag",
     "Description": "Cannot override the value of a tag that already exists"
   },
+  {
+    "Code": 2043, 
+    "Name": "NoStorageCommitmentHandler", 
+    "Description": "No request handler factory for DICOM N-ACTION SCP (storage commitment)"
+  },
 
 
 
--- a/Resources/Fonts/GenerateFont.py	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Fonts/GenerateFont.py	Thu Mar 19 11:48:30 2020 +0100
@@ -3,7 +3,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
--- a/Resources/GenerateAnonymizationProfile.py	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/GenerateAnonymizationProfile.py	Thu Mar 19 11:48:30 2020 +0100
@@ -3,7 +3,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
--- a/Resources/GenerateErrorCodes.py	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/GenerateErrorCodes.py	Thu Mar 19 11:48:30 2020 +0100
@@ -3,7 +3,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/GenerateTransferSyntaxes.py	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,84 @@
+#!/usr/bin/python
+
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# In addition, as a special exception, the copyright holders of this
+# program give permission to link the code of its release with the
+# OpenSSL project's "OpenSSL" library (or with modified versions of it
+# that use the same license as the "OpenSSL" library), and distribute
+# the linked executables. You must obey the GNU General Public License
+# in all respects for all of the code used other than "OpenSSL". If you
+# modify file(s) with this exception, you may extend this exception to
+# your version of the file(s), but you are not obligated to do so. If
+# you do not wish to do so, delete this exception statement from your
+# version. If you delete this exception statement from all source files
+# in the program, then also delete it here.
+# 
+# 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
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import json
+import os
+import re
+import sys
+import pystache
+
+BASE = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
+
+
+
+## https://www.dicomlibrary.com/dicom/transfer-syntax/
+## https://cedocs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=EDICOM_transfer_syntax
+
+
+with open(os.path.join(BASE, 'Resources', 'DicomTransferSyntaxes.json'), 'r') as f:
+    SYNTAXES = json.loads(f.read())
+
+
+
+##
+## Generate the "DicomTransferSyntax" enumeration in "Enumerations.h"
+##
+
+path = os.path.join(BASE, 'Core', 'Enumerations.h')
+with open(path, 'r') as f:
+    a = f.read()
+
+s = ',\n'.join(map(lambda x: '    DicomTransferSyntax_%s    /*!< %s */' % (x['Value'], x['Name']), SYNTAXES))
+
+a = re.sub('(enum DicomTransferSyntax\s*{)[^}]*?(\s*};)', r'\1\n%s\2' % s, a, re.DOTALL)
+
+with open(path, 'w') as f:
+    f.write(a)
+
+
+
+##
+## Generate the implementations
+##
+
+with open(os.path.join(BASE, 'Core', 'Enumerations_TransferSyntaxes.impl.h'), 'w') as b:
+    with open(os.path.join(BASE, 'Resources', 'GenerateTransferSyntaxesEnumerations.mustache'), 'r') as a:
+        b.write(pystache.render(a.read(), {
+            'Syntaxes' : SYNTAXES
+        }))
+
+with open(os.path.join(BASE, 'Core', 'DicomParsing', 'FromDcmtkBridge_TransferSyntaxes.impl.h'), 'w') as b:
+    with open(os.path.join(BASE, 'Resources', 'GenerateTransferSyntaxesDcmtk.mustache'), 'r') as a:
+        b.write(pystache.render(a.read(), {
+            'Syntaxes' : SYNTAXES
+        }))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/GenerateTransferSyntaxesDcmtk.mustache	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,103 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+// This file is autogenerated by "../Resources/GenerateTransferSyntaxes.py"
+
+namespace Orthanc
+{
+  bool FromDcmtkBridge::LookupDcmtkTransferSyntax(E_TransferSyntax& target,
+                                                  DicomTransferSyntax source)
+  {
+    switch (source)
+    {
+      {{#Syntaxes}}
+      {{#DCMTK}}
+      {{#SinceDCMTK}}
+#if DCMTK_VERSION_NUMBER >= {{SinceDCMTK}}
+      {{/SinceDCMTK}}
+      case DicomTransferSyntax_{{Value}}:
+        {{#DCMTK360}}
+#  if DCMTK_VERSION_NUMBER <= 360
+        target = {{DCMTK360}};
+#  else
+        target = {{DCMTK}};
+#  endif
+        {{/DCMTK360}}
+        {{^DCMTK360}}
+        target = {{DCMTK}};
+        {{/DCMTK360}}
+        return true;
+      {{#SinceDCMTK}}
+#endif
+      {{/SinceDCMTK}}
+
+      {{/DCMTK}}
+      {{/Syntaxes}}
+      default:
+        return false;
+    }
+  }
+  
+
+  bool FromDcmtkBridge::LookupOrthancTransferSyntax(DicomTransferSyntax& target,
+                                                    E_TransferSyntax source)
+  {
+    switch (source)
+    {
+      {{#Syntaxes}}
+      {{#DCMTK}}
+      {{#SinceDCMTK}}
+#if DCMTK_VERSION_NUMBER >= {{SinceDCMTK}}
+      {{/SinceDCMTK}}
+      {{#DCMTK360}}
+#  if DCMTK_VERSION_NUMBER <= 360
+      case {{DCMTK360}}:
+#  else
+      case {{DCMTK}}:
+#  endif
+      {{/DCMTK360}}
+      {{^DCMTK360}}
+      case {{DCMTK}}:
+      {{/DCMTK360}}
+        target = DicomTransferSyntax_{{Value}};
+        return true;
+      {{#SinceDCMTK}}
+#endif
+      {{/SinceDCMTK}}
+
+      {{/DCMTK}}
+      {{/Syntaxes}}
+      default:
+        return false;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/GenerateTransferSyntaxesEnumerations.mustache	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,85 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+// This file is autogenerated by "../Resources/GenerateTransferSyntaxes.py"
+
+namespace Orthanc
+{
+  const char* GetTransferSyntaxUid(DicomTransferSyntax syntax)
+  {
+    switch (syntax)
+    {
+      {{#Syntaxes}}
+      case DicomTransferSyntax_{{Value}}:
+        return "{{UID}}";
+
+      {{/Syntaxes}}
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  bool IsRetiredTransferSyntax(DicomTransferSyntax syntax)
+  {
+    switch (syntax)
+    {
+      {{#Syntaxes}}
+      case DicomTransferSyntax_{{Value}}:
+        {{#Retired}}
+        return true;
+        {{/Retired}}
+        {{^Retired}}
+        return false;
+        {{/Retired}}
+
+      {{/Syntaxes}}
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  bool LookupTransferSyntax(DicomTransferSyntax& target,
+                            const std::string& uid)
+  {
+    {{#Syntaxes}}
+    if (uid == "{{UID}}")
+    {
+      target = DicomTransferSyntax_{{Value}};
+      return true;
+    }
+    
+    {{/Syntaxes}}
+    return false;
+  }
+}
--- a/Resources/Graveyard/DatabaseOptimizations/LookupIdentifierQuery.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/DatabaseOptimizations/LookupIdentifierQuery.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/DatabaseOptimizations/LookupIdentifierQuery.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/DatabaseOptimizations/LookupIdentifierQuery.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/DatabaseOptimizations/LookupResource.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/DatabaseOptimizations/LookupResource.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/DatabaseOptimizations/LookupResource.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/DatabaseOptimizations/LookupResource.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/DatabasePluginSample/Database.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/DatabasePluginSample/Database.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/DatabasePluginSample/Database.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/DatabasePluginSample/Database.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/DatabasePluginSample/DatabaseWrapperBase.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/DatabasePluginSample/DatabaseWrapperBase.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/DatabasePluginSample/DatabaseWrapperBase.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/DatabasePluginSample/DatabaseWrapperBase.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/DatabasePluginSample/Plugin.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/DatabasePluginSample/Plugin.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Graveyard/FromDcmtkBridge.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,168 @@
+  DcmElement* FromDcmtkBridge::CreateElementForTag(const DicomTag& tag)
+  {
+    DcmTag key(tag.GetGroup(), tag.GetElement());
+
+    if (tag.IsPrivate())
+    {
+      // This raises BitBucket issue 140 (Modifying private tags with
+      // REST API changes VR from LO to UN)
+      // https://bitbucket.org/sjodogne/orthanc/issues/140
+      LOG(WARNING) << "You are using DCMTK < 3.6.1: All the private tags "
+        "are considered as having a binary value representation";
+      return new DcmOtherByteOtherWord(key);
+    }
+    else if (IsBinaryTag(key))
+    {
+      return new DcmOtherByteOtherWord(key);
+    }
+
+    switch (key.getEVR())
+    {
+      // http://support.dcmtk.org/docs/dcvr_8h-source.html
+
+      /**
+       * Binary types, handled above
+       **/
+    
+#if DCMTK_VERSION_NUMBER >= 361
+      case EVR_OD:
+#endif            
+
+#if DCMTK_VERSION_NUMBER >= 362
+      case EVR_OL:
+#endif            
+
+      case EVR_OB:  // other byte
+      case EVR_OF:  // other float
+      case EVR_OW:  // other word
+      case EVR_UN:  // unknown value representation
+      case EVR_ox:  // OB or OW depending on context
+        throw OrthancException(ErrorCode_InternalError);
+
+
+      /**
+       * String types.
+       * http://support.dcmtk.org/docs/classDcmByteString.html
+       **/
+      
+      case EVR_AS:  // age string
+        return new DcmAgeString(key);
+
+      case EVR_AE:  // application entity title
+        return new DcmApplicationEntity(key);
+
+      case EVR_CS:  // code string
+        return new DcmCodeString(key);        
+
+      case EVR_DA:  // date string
+        return new DcmDate(key);
+        
+      case EVR_DT:  // date time string
+        return new DcmDateTime(key);
+
+      case EVR_DS:  // decimal string
+        return new DcmDecimalString(key);
+
+      case EVR_IS:  // integer string
+        return new DcmIntegerString(key);
+
+      case EVR_TM:  // time string
+        return new DcmTime(key);
+
+      case EVR_UI:  // unique identifier
+        return new DcmUniqueIdentifier(key);
+
+      case EVR_ST:  // short text
+        return new DcmShortText(key);
+
+      case EVR_LO:  // long string
+        return new DcmLongString(key);
+
+      case EVR_LT:  // long text
+        return new DcmLongText(key);
+
+      case EVR_UT:  // unlimited text
+        return new DcmUnlimitedText(key);
+
+      case EVR_SH:  // short string
+        return new DcmShortString(key);
+
+      case EVR_PN:  // person name
+        return new DcmPersonName(key);
+
+#if DCMTK_VERSION_NUMBER >= 361
+      case EVR_UC:  // unlimited characters
+        return new DcmUnlimitedCharacters(key);
+#endif
+
+#if DCMTK_VERSION_NUMBER >= 361
+      case EVR_UR:  // URI/URL
+        return new DcmUniversalResourceIdentifierOrLocator(key);
+#endif
+          
+        
+      /**
+       * Numerical types
+       **/ 
+      
+      case EVR_SL:  // signed long
+        return new DcmSignedLong(key);
+
+      case EVR_SS:  // signed short
+        return new DcmSignedShort(key);
+
+      case EVR_UL:  // unsigned long
+        return new DcmUnsignedLong(key);
+
+      case EVR_US:  // unsigned short
+        return new DcmUnsignedShort(key);
+
+      case EVR_FL:  // float single-precision
+        return new DcmFloatingPointSingle(key);
+
+      case EVR_FD:  // float double-precision
+        return new DcmFloatingPointDouble(key);
+
+
+      /**
+       * Sequence types, should never occur at this point.
+       **/
+
+      case EVR_SQ:  // sequence of items
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+
+
+      /**
+       * TODO
+       **/
+
+      case EVR_AT:  // attribute tag
+        throw OrthancException(ErrorCode_NotImplemented);
+
+
+      /**
+       * Internal to DCMTK.
+       **/ 
+
+      case EVR_xs:  // SS or US depending on context
+      case EVR_lt:  // US, SS or OW depending on context, used for LUT Data (thus the name)
+      case EVR_na:  // na="not applicable", for data which has no VR
+      case EVR_up:  // up="unsigned pointer", used internally for DICOMDIR suppor
+      case EVR_item:  // used internally for items
+      case EVR_metainfo:  // used internally for meta info datasets
+      case EVR_dataset:  // used internally for datasets
+      case EVR_fileFormat:  // used internally for DICOM files
+      case EVR_dicomDir:  // used internally for DICOMDIR objects
+      case EVR_dirRecord:  // used internally for DICOMDIR records
+      case EVR_pixelSQ:  // used internally for pixel sequences in a compressed image
+      case EVR_pixelItem:  // used internally for pixel items in a compressed image
+      case EVR_UNKNOWN: // used internally for elements with unknown VR (encoded with 4-byte length field in explicit VR)
+      case EVR_PixelData:  // used internally for uncompressed pixeld data
+      case EVR_OverlayData:  // used internally for overlay data
+      case EVR_UNKNOWN2B:  // used internally for elements with unknown VR with 2-byte length field in explicit VR
+      default:
+        break;
+    }
+
+    throw OrthancException(ErrorCode_InternalError);
+  }
--- a/Resources/Graveyard/Multithreading/BagOfTasks.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/Multithreading/BagOfTasks.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/Multithreading/BagOfTasksProcessor.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/Multithreading/BagOfTasksProcessor.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/Multithreading/BagOfTasksProcessor.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/Multithreading/BagOfTasksProcessor.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/Multithreading/ICommand.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/Multithreading/ICommand.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/Multithreading/ILockable.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/Multithreading/ILockable.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/Multithreading/Locker.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/Multithreading/Locker.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/Multithreading/Mutex.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/Multithreading/Mutex.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/Multithreading/Mutex.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/Multithreading/Mutex.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/Multithreading/ReaderWriterLock.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/Multithreading/ReaderWriterLock.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/Multithreading/ReaderWriterLock.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/Multithreading/ReaderWriterLock.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/CallSystemCommand.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/OldScheduler/CallSystemCommand.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/CallSystemCommand.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/OldScheduler/CallSystemCommand.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/DeleteInstanceCommand.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/OldScheduler/DeleteInstanceCommand.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/DeleteInstanceCommand.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/OldScheduler/DeleteInstanceCommand.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/IServerCommand.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/OldScheduler/IServerCommand.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/ModifyInstanceCommand.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/OldScheduler/ModifyInstanceCommand.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/ModifyInstanceCommand.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/OldScheduler/ModifyInstanceCommand.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/OldScheduler/ReusableDicomUserConnection.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/ServerCommandInstance.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/OldScheduler/ServerCommandInstance.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/ServerCommandInstance.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/OldScheduler/ServerCommandInstance.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/ServerJob.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/OldScheduler/ServerJob.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/ServerJob.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/OldScheduler/ServerJob.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/ServerScheduler.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/OldScheduler/ServerScheduler.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/ServerScheduler.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/OldScheduler/ServerScheduler.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/StorePeerCommand.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/OldScheduler/StorePeerCommand.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/StorePeerCommand.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/OldScheduler/StorePeerCommand.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/StoreScuCommand.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/OldScheduler/StoreScuCommand.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Graveyard/OldScheduler/StoreScuCommand.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Graveyard/OldScheduler/StoreScuCommand.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/LinuxStandardBaseToolchain.cmake	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/LinuxStandardBaseToolchain.cmake	Thu Mar 19 11:48:30 2020 +0100
@@ -1,4 +1,4 @@
-# LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../Resources/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON -DUSE_LEGACY_LIBICU=ON -DBOOST_LOCALE_BACKEND=icu
+# LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../Resources/LinuxStandardBaseToolchain.cmake -DUSE_LEGACY_JSONCPP=ON -DUSE_LEGACY_LIBICU=ON -DBOOST_LOCALE_BACKEND=icu -G Ninja
 
 INCLUDE(CMakeForceCompiler)
 
@@ -31,7 +31,12 @@
 
 # which compilers to use for C and C++
 SET(CMAKE_C_COMPILER ${LSB_PATH}/bin/lsbcc)
-CMAKE_FORCE_CXX_COMPILER(${LSB_PATH}/bin/lsbc++ GNU)
+
+if (${CMAKE_VERSION} VERSION_LESS "3.6.0") 
+  CMAKE_FORCE_CXX_COMPILER(${LSB_PATH}/bin/lsbc++ GNU)
+else()
+  SET(CMAKE_CXX_COMPILER ${LSB_PATH}/bin/lsbc++)
+endif()
 
 # here is the target environment located
 SET(CMAKE_FIND_ROOT_PATH ${LSB_PATH})
--- a/Resources/Patches/dcmtk-3.6.2-cmath.patch	Wed Mar 18 08:59:06 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-diff -urEb dcmtk-3.6.2.orig/CMake/GenerateDCMTKConfigure.cmake dcmtk-3.6.2/CMake/GenerateDCMTKConfigure.cmake
---- dcmtk-3.6.2.orig/CMake/GenerateDCMTKConfigure.cmake	2018-09-20 09:30:34.364831213 +0200
-+++ dcmtk-3.6.2/CMake/GenerateDCMTKConfigure.cmake	2018-09-20 09:47:52.013660067 +0200
-@@ -568,12 +568,12 @@
-   ENDIF(HAVE_CSTDDEF)
- 
-   CHECK_FUNCTIONWITHHEADER_EXISTS(feenableexcept "${HEADERS}" HAVE_PROTOTYPE_FEENABLEEXCEPT)
--  CHECK_FUNCTIONWITHHEADER_EXISTS(isinf "${HEADERS}" HAVE_PROTOTYPE_ISINF)
--  CHECK_FUNCTIONWITHHEADER_EXISTS(isnan "${HEADERS}" HAVE_PROTOTYPE_ISNAN)
--  CHECK_FUNCTIONWITHHEADER_EXISTS(finite "${HEADERS}" HAVE_PROTOTYPE_FINITE)
--  CHECK_FUNCTIONWITHHEADER_EXISTS(std::isinf "${HEADERS}" HAVE_PROTOTYPE_STD__ISINF)
--  CHECK_FUNCTIONWITHHEADER_EXISTS(std::isnan "${HEADERS}" HAVE_PROTOTYPE_STD__ISNAN)
--  CHECK_FUNCTIONWITHHEADER_EXISTS(std::finite "${HEADERS}" HAVE_PROTOTYPE_STD__FINITE)
-+  CHECK_FUNCTIONWITHHEADER_EXISTS("isinf(0.)" "${HEADERS}" HAVE_PROTOTYPE_ISINF)
-+  CHECK_FUNCTIONWITHHEADER_EXISTS("isnan(0.)" "${HEADERS}" HAVE_PROTOTYPE_ISNAN)
-+  CHECK_FUNCTIONWITHHEADER_EXISTS("finite(0.)" "${HEADERS}" HAVE_PROTOTYPE_FINITE)
-+  CHECK_FUNCTIONWITHHEADER_EXISTS("std::isinf(0.)" "${HEADERS}" HAVE_PROTOTYPE_STD__ISINF)
-+  CHECK_FUNCTIONWITHHEADER_EXISTS("std::isnan(0.)" "${HEADERS}" HAVE_PROTOTYPE_STD__ISNAN)
-+  CHECK_FUNCTIONWITHHEADER_EXISTS("std::finite(0.)" "${HEADERS}" HAVE_PROTOTYPE_STD__FINITE)
-   CHECK_FUNCTIONWITHHEADER_EXISTS(flock "${HEADERS}" HAVE_PROTOTYPE_FLOCK)
-   CHECK_FUNCTIONWITHHEADER_EXISTS(gethostbyname "${HEADERS}" HAVE_PROTOTYPE_GETHOSTBYNAME)
-   CHECK_FUNCTIONWITHHEADER_EXISTS(gethostbyname_r "${HEADERS}" HAVE_PROTOTYPE_GETHOSTBYNAME_R)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Patches/dcmtk-3.6.2.patch	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,48 @@
+diff -urEb dcmtk-3.6.2.orig/CMake/GenerateDCMTKConfigure.cmake dcmtk-3.6.2/CMake/GenerateDCMTKConfigure.cmake
+--- dcmtk-3.6.2.orig/CMake/GenerateDCMTKConfigure.cmake	2020-01-06 17:42:52.299540389 +0100
++++ dcmtk-3.6.2/CMake/GenerateDCMTKConfigure.cmake	2020-01-06 17:43:56.707520036 +0100
+@@ -568,12 +568,12 @@
+   ENDIF(HAVE_CSTDDEF)
+ 
+   CHECK_FUNCTIONWITHHEADER_EXISTS(feenableexcept "${HEADERS}" HAVE_PROTOTYPE_FEENABLEEXCEPT)
+-  CHECK_FUNCTIONWITHHEADER_EXISTS(isinf "${HEADERS}" HAVE_PROTOTYPE_ISINF)
+-  CHECK_FUNCTIONWITHHEADER_EXISTS(isnan "${HEADERS}" HAVE_PROTOTYPE_ISNAN)
+-  CHECK_FUNCTIONWITHHEADER_EXISTS(finite "${HEADERS}" HAVE_PROTOTYPE_FINITE)
+-  CHECK_FUNCTIONWITHHEADER_EXISTS(std::isinf "${HEADERS}" HAVE_PROTOTYPE_STD__ISINF)
+-  CHECK_FUNCTIONWITHHEADER_EXISTS(std::isnan "${HEADERS}" HAVE_PROTOTYPE_STD__ISNAN)
+-  CHECK_FUNCTIONWITHHEADER_EXISTS(std::finite "${HEADERS}" HAVE_PROTOTYPE_STD__FINITE)
++  CHECK_FUNCTIONWITHHEADER_EXISTS("isinf(0.)" "${HEADERS}" HAVE_PROTOTYPE_ISINF)
++  CHECK_FUNCTIONWITHHEADER_EXISTS("isnan(0.)" "${HEADERS}" HAVE_PROTOTYPE_ISNAN)
++  CHECK_FUNCTIONWITHHEADER_EXISTS("finite(0.)" "${HEADERS}" HAVE_PROTOTYPE_FINITE)
++  CHECK_FUNCTIONWITHHEADER_EXISTS("std::isinf(0.)" "${HEADERS}" HAVE_PROTOTYPE_STD__ISINF)
++  CHECK_FUNCTIONWITHHEADER_EXISTS("std::isnan(0.)" "${HEADERS}" HAVE_PROTOTYPE_STD__ISNAN)
++  CHECK_FUNCTIONWITHHEADER_EXISTS("std::finite(0.)" "${HEADERS}" HAVE_PROTOTYPE_STD__FINITE)
+   CHECK_FUNCTIONWITHHEADER_EXISTS(flock "${HEADERS}" HAVE_PROTOTYPE_FLOCK)
+   CHECK_FUNCTIONWITHHEADER_EXISTS(gethostbyname "${HEADERS}" HAVE_PROTOTYPE_GETHOSTBYNAME)
+   CHECK_FUNCTIONWITHHEADER_EXISTS(gethostbyname_r "${HEADERS}" HAVE_PROTOTYPE_GETHOSTBYNAME_R)
+diff -urEb dcmtk-3.6.2.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h dcmtk-3.6.2/dcmdata/include/dcmtk/dcmdata/dcdict.h
+--- dcmtk-3.6.2.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h	2020-01-06 17:42:52.283540394 +0100
++++ dcmtk-3.6.2/dcmdata/include/dcmtk/dcmdata/dcdict.h	2020-01-06 17:46:21.711473976 +0100
+@@ -152,6 +152,12 @@
+     /// returns an iterator to the end of the repeating tag dictionary
+     DcmDictEntryListIterator repeatingEnd() { return repDict.end(); }
+ 
++    // Function by the Orthanc project to load a dictionary from a
++    // memory buffer, which is necessary in sandboxed
++    // environments. This is an adapted version of
++    // DcmDataDictionary::loadDictionary().
++    OFBool loadFromMemory(const std::string& content, OFBool errorIfAbsent = OFTrue);
++
+ private:
+ 
+     /** private undefined assignment operator
+diff -urEb dcmtk-3.6.2.orig/dcmdata/libsrc/dcdict.cc dcmtk-3.6.2/dcmdata/libsrc/dcdict.cc
+--- dcmtk-3.6.2.orig/dcmdata/libsrc/dcdict.cc	2020-01-06 17:42:52.287540392 +0100
++++ dcmtk-3.6.2/dcmdata/libsrc/dcdict.cc	2020-01-06 17:47:18.335299472 +0100
+@@ -876,3 +876,6 @@
+   wrlock().clear();
+   unlock();
+ }
++
++
++#include "dcdict_orthanc.cc"
--- a/Resources/Patches/dcmtk-3.6.4.patch	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Patches/dcmtk-3.6.4.patch	Thu Mar 19 11:48:30 2020 +0100
@@ -1,6 +1,32 @@
+diff -urEb dcmtk-3.6.4.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h dcmtk-3.6.4/dcmdata/include/dcmtk/dcmdata/dcdict.h
+--- dcmtk-3.6.4.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h	2020-01-06 19:55:12.887153062 +0100
++++ dcmtk-3.6.4/dcmdata/include/dcmtk/dcmdata/dcdict.h	2020-01-06 19:55:28.156447233 +0100
+@@ -152,6 +152,12 @@
+     /// returns an iterator to the end of the repeating tag dictionary
+     DcmDictEntryListIterator repeatingEnd() { return repDict.end(); }
+ 
++    // Function by the Orthanc project to load a dictionary from a
++    // memory buffer, which is necessary in sandboxed
++    // environments. This is an adapted version of
++    // DcmDataDictionary::loadDictionary().
++    OFBool loadFromMemory(const std::string& content, OFBool errorIfAbsent = OFTrue);
++
+ private:
+ 
+     /** private undefined assignment operator
+diff -urEb dcmtk-3.6.4.orig/dcmdata/libsrc/dcdict.cc dcmtk-3.6.4/dcmdata/libsrc/dcdict.cc
+--- dcmtk-3.6.4.orig/dcmdata/libsrc/dcdict.cc	2020-01-06 19:55:12.899154075 +0100
++++ dcmtk-3.6.4/dcmdata/libsrc/dcdict.cc	2020-01-06 19:55:28.156447233 +0100
+@@ -899,3 +899,6 @@
+   wrlock().clear();
+   wrunlock();
+ }
++
++
++#include "dcdict_orthanc.cc"
 diff -urEb dcmtk-3.6.4.orig/dcmdata/libsrc/dcpxitem.cc dcmtk-3.6.4/dcmdata/libsrc/dcpxitem.cc
---- dcmtk-3.6.4.orig/dcmdata/libsrc/dcpxitem.cc	2019-02-21 15:30:21.657110805 +0100
-+++ dcmtk-3.6.4/dcmdata/libsrc/dcpxitem.cc	2019-02-21 16:28:43.721049550 +0100
+--- dcmtk-3.6.4.orig/dcmdata/libsrc/dcpxitem.cc	2020-01-06 19:55:12.899154075 +0100
++++ dcmtk-3.6.4/dcmdata/libsrc/dcpxitem.cc	2020-01-06 19:55:28.156447233 +0100
 @@ -36,6 +36,9 @@
  #include "dcmtk/dcmdata/dcostrma.h"    /* for class DcmOutputStream */
  #include "dcmtk/dcmdata/dcwcache.h"    /* for class DcmWriteCache */
@@ -11,9 +37,57 @@
  
  // ********************************
  
+diff -urEb dcmtk-3.6.4.orig/oflog/include/dcmtk/oflog/thread/syncpub.h dcmtk-3.6.4/oflog/include/dcmtk/oflog/thread/syncpub.h
+--- dcmtk-3.6.4.orig/oflog/include/dcmtk/oflog/thread/syncpub.h	2020-01-06 19:55:12.911155088 +0100
++++ dcmtk-3.6.4/oflog/include/dcmtk/oflog/thread/syncpub.h	2020-01-06 19:56:26.991372656 +0100
+@@ -63,7 +63,7 @@
+ 
+ DCMTK_LOG4CPLUS_INLINE_EXPORT
+ Mutex::Mutex (Mutex::Type t)
+-    : mtx (DCMTK_LOG4CPLUS_THREADED (new impl::Mutex (t)) + 0)
++    : mtx (DCMTK_LOG4CPLUS_THREADED (new impl::Mutex (t)))
+ { }
+ 
+ 
+@@ -106,7 +106,7 @@
+ DCMTK_LOG4CPLUS_INLINE_EXPORT
+ Semaphore::Semaphore (unsigned DCMTK_LOG4CPLUS_THREADED (max),
+     unsigned DCMTK_LOG4CPLUS_THREADED (initial))
+-    : sem (DCMTK_LOG4CPLUS_THREADED (new impl::Semaphore (max, initial)) + 0)
++    : sem (DCMTK_LOG4CPLUS_THREADED (new impl::Semaphore (max, initial)))
+ { }
+ 
+ 
+@@ -148,7 +148,7 @@
+ 
+ DCMTK_LOG4CPLUS_INLINE_EXPORT
+ FairMutex::FairMutex ()
+-    : mtx (DCMTK_LOG4CPLUS_THREADED (new impl::FairMutex) + 0)
++    : mtx (DCMTK_LOG4CPLUS_THREADED (new impl::FairMutex))
+ { }
+ 
+ 
+@@ -190,7 +190,7 @@
+ 
+ DCMTK_LOG4CPLUS_INLINE_EXPORT
+ ManualResetEvent::ManualResetEvent (bool DCMTK_LOG4CPLUS_THREADED (sig))
+-    : ev (DCMTK_LOG4CPLUS_THREADED (new impl::ManualResetEvent (sig)) + 0)
++    : ev (DCMTK_LOG4CPLUS_THREADED (new impl::ManualResetEvent (sig)))
+ { }
+ 
+ 
+@@ -252,7 +252,7 @@
+ 
+ DCMTK_LOG4CPLUS_INLINE_EXPORT
+ SharedMutex::SharedMutex ()
+-    : sm (DCMTK_LOG4CPLUS_THREADED (new impl::SharedMutex) + 0)
++    : sm (DCMTK_LOG4CPLUS_THREADED (new impl::SharedMutex))
+ { }
+ 
+ 
 diff -urEb dcmtk-3.6.4.orig/ofstd/include/dcmtk/ofstd/offile.h dcmtk-3.6.4/ofstd/include/dcmtk/ofstd/offile.h
---- dcmtk-3.6.4.orig/ofstd/include/dcmtk/ofstd/offile.h	2019-02-21 15:30:21.645110805 +0100
-+++ dcmtk-3.6.4/ofstd/include/dcmtk/ofstd/offile.h	2019-02-21 15:30:48.273110339 +0100
+--- dcmtk-3.6.4.orig/ofstd/include/dcmtk/ofstd/offile.h	2020-01-06 19:55:12.951158464 +0100
++++ dcmtk-3.6.4/ofstd/include/dcmtk/ofstd/offile.h	2020-01-06 19:55:28.156447233 +0100
 @@ -575,7 +575,7 @@
     */
    void setlinebuf()
@@ -23,4 +97,3 @@
      this->setvbuf(NULL, _IOLBF, 0);
  #else
      :: setlinebuf(file_);
-Only in dcmtk-3.6.4/ofstd/include/dcmtk/ofstd: offile.h~
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Patches/dcmtk-3.6.5.patch	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,113 @@
+diff -urEb dcmtk-3.6.5.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h dcmtk-3.6.5/dcmdata/include/dcmtk/dcmdata/dcdict.h
+--- dcmtk-3.6.5.orig/dcmdata/include/dcmtk/dcmdata/dcdict.h	2020-03-18 10:22:41.555166774 +0100
++++ dcmtk-3.6.5/dcmdata/include/dcmtk/dcmdata/dcdict.h	2020-03-18 10:22:53.395131056 +0100
+@@ -152,6 +152,12 @@
+     /// returns an iterator to the end of the repeating tag dictionary
+     DcmDictEntryListIterator repeatingEnd() { return repDict.end(); }
+ 
++    // Function by the Orthanc project to load a dictionary from a
++    // memory buffer, which is necessary in sandboxed
++    // environments. This is an adapted version of
++    // DcmDataDictionary::loadDictionary().
++    OFBool loadFromMemory(const std::string& content, OFBool errorIfAbsent = OFTrue);
++
+ private:
+ 
+     /** private undefined assignment operator
+diff -urEb dcmtk-3.6.5.orig/dcmdata/libsrc/dcdict.cc dcmtk-3.6.5/dcmdata/libsrc/dcdict.cc
+--- dcmtk-3.6.5.orig/dcmdata/libsrc/dcdict.cc	2020-03-18 10:22:41.559166762 +0100
++++ dcmtk-3.6.5/dcmdata/libsrc/dcdict.cc	2020-03-18 10:22:53.395131056 +0100
+@@ -900,3 +900,6 @@
+   wrlock().clear();
+   wrunlock();
+ }
++
++
++#include "dcdict_orthanc.cc"
+diff -urEb dcmtk-3.6.5.orig/dcmdata/libsrc/dcpxitem.cc dcmtk-3.6.5/dcmdata/libsrc/dcpxitem.cc
+--- dcmtk-3.6.5.orig/dcmdata/libsrc/dcpxitem.cc	2020-03-18 10:22:41.559166762 +0100
++++ dcmtk-3.6.5/dcmdata/libsrc/dcpxitem.cc	2020-03-18 10:22:53.395131056 +0100
+@@ -36,6 +36,9 @@
+ #include "dcmtk/dcmdata/dcostrma.h"    /* for class DcmOutputStream */
+ #include "dcmtk/dcmdata/dcwcache.h"    /* for class DcmWriteCache */
+ 
++#undef max
++#include "dcmtk/ofstd/oflimits.h"
++
+ 
+ // ********************************
+ 
+diff -urEb dcmtk-3.6.5.orig/oflog/include/dcmtk/oflog/thread/syncpub.h dcmtk-3.6.5/oflog/include/dcmtk/oflog/thread/syncpub.h
+--- dcmtk-3.6.5.orig/oflog/include/dcmtk/oflog/thread/syncpub.h	2020-03-18 10:22:41.543166810 +0100
++++ dcmtk-3.6.5/oflog/include/dcmtk/oflog/thread/syncpub.h	2020-03-18 10:22:53.395131056 +0100
+@@ -63,7 +63,7 @@
+ 
+ DCMTK_LOG4CPLUS_INLINE_EXPORT
+ Mutex::Mutex (Mutex::Type t)
+-    : mtx (DCMTK_LOG4CPLUS_THREADED (new impl::Mutex (t)) + 0)
++    : mtx (DCMTK_LOG4CPLUS_THREADED (new impl::Mutex (t)))
+ { }
+ 
+ 
+@@ -106,7 +106,7 @@
+ DCMTK_LOG4CPLUS_INLINE_EXPORT
+ Semaphore::Semaphore (unsigned DCMTK_LOG4CPLUS_THREADED (max),
+     unsigned DCMTK_LOG4CPLUS_THREADED (initial))
+-    : sem (DCMTK_LOG4CPLUS_THREADED (new impl::Semaphore (max, initial)) + 0)
++    : sem (DCMTK_LOG4CPLUS_THREADED (new impl::Semaphore (max, initial)))
+ { }
+ 
+ 
+@@ -148,7 +148,7 @@
+ 
+ DCMTK_LOG4CPLUS_INLINE_EXPORT
+ FairMutex::FairMutex ()
+-    : mtx (DCMTK_LOG4CPLUS_THREADED (new impl::FairMutex) + 0)
++    : mtx (DCMTK_LOG4CPLUS_THREADED (new impl::FairMutex))
+ { }
+ 
+ 
+@@ -190,7 +190,7 @@
+ 
+ DCMTK_LOG4CPLUS_INLINE_EXPORT
+ ManualResetEvent::ManualResetEvent (bool DCMTK_LOG4CPLUS_THREADED (sig))
+-    : ev (DCMTK_LOG4CPLUS_THREADED (new impl::ManualResetEvent (sig)) + 0)
++    : ev (DCMTK_LOG4CPLUS_THREADED (new impl::ManualResetEvent (sig)))
+ { }
+ 
+ 
+@@ -252,7 +252,7 @@
+ 
+ DCMTK_LOG4CPLUS_INLINE_EXPORT
+ SharedMutex::SharedMutex ()
+-    : sm (DCMTK_LOG4CPLUS_THREADED (new impl::SharedMutex) + 0)
++    : sm (DCMTK_LOG4CPLUS_THREADED (new impl::SharedMutex))
+ { }
+ 
+ 
+diff -urEb dcmtk-3.6.5.orig/oflog/libsrc/oflog.cc dcmtk-3.6.5/oflog/libsrc/oflog.cc
+--- dcmtk-3.6.5.orig/oflog/libsrc/oflog.cc	2020-03-18 10:22:41.547166798 +0100
++++ dcmtk-3.6.5/oflog/libsrc/oflog.cc	2020-03-18 11:55:50.116856932 +0100
+@@ -19,6 +19,10 @@
+  *
+  */
+ 
++#if defined(_WIN32)
++#  include <winsock2.h>
++#endif
++
+ #include "dcmtk/config/osconfig.h"    /* make sure OS specific configuration is included first */
+ #include "dcmtk/oflog/oflog.h"
+ 
+diff -urEb dcmtk-3.6.5.orig/ofstd/include/dcmtk/ofstd/offile.h dcmtk-3.6.5/ofstd/include/dcmtk/ofstd/offile.h
+--- dcmtk-3.6.5.orig/ofstd/include/dcmtk/ofstd/offile.h	2020-03-18 10:22:41.587166677 +0100
++++ dcmtk-3.6.5/ofstd/include/dcmtk/ofstd/offile.h	2020-03-18 10:22:53.395131056 +0100
+@@ -575,7 +575,7 @@
+    */
+   void setlinebuf()
+   {
+-#if defined(_WIN32) || defined(__hpux)
++#if defined(_WIN32) || defined(__hpux) || defined(__LSB_VERSION__)
+     this->setvbuf(NULL, _IOLBF, 0);
+ #else
+     :: setlinebuf(file_);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Patches/dcmtk-dcdict_orthanc.cc	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,205 @@
+// Function by the Orthanc project to load a dictionary from a memory
+// buffer, which is necessary in sandboxed environments. This is an
+// adapted version of DcmDataDictionary::loadDictionary().
+
+#include <string>
+#include <boost/noncopyable.hpp>
+
+struct OrthancLinesIterator;
+
+// This plain old C class is implemented in "../../Core/Toolbox.h"
+OrthancLinesIterator* OrthancLinesIterator_Create(const std::string& content);
+
+bool OrthancLinesIterator_GetLine(std::string& target,
+                                  const OrthancLinesIterator* iterator);
+
+void OrthancLinesIterator_Next(OrthancLinesIterator* iterator);
+
+void OrthancLinesIterator_Free(OrthancLinesIterator* iterator);
+
+
+class LinesIterator : public boost::noncopyable
+{
+private:
+  OrthancLinesIterator* iterator_;
+  
+public:
+  LinesIterator(const std::string& content) :
+    iterator_(NULL)
+  {
+    iterator_ = OrthancLinesIterator_Create(content);
+  }
+
+  ~LinesIterator()
+  {
+    if (iterator_ != NULL)
+    {
+      OrthancLinesIterator_Free(iterator_);
+      iterator_ = NULL;
+    }
+  }
+  
+  bool GetLine(std::string& target) const
+  {
+    if (iterator_ != NULL)
+    {
+      return OrthancLinesIterator_GetLine(target, iterator_);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  void Next()
+  {
+    if (iterator_ != NULL)
+    {
+      OrthancLinesIterator_Next(iterator_);
+    }
+  }
+};
+
+
+
+OFBool
+DcmDataDictionary::loadFromMemory(const std::string& content, OFBool errorIfAbsent)
+{
+  int lineNumber = 0;
+  char* lineFields[DCM_MAXDICTFIELDS + 1];
+  int fieldsPresent;
+  DcmDictEntry* e;
+  int errorsEncountered = 0;
+  OFBool errorOnThisLine = OFFalse;
+  int i;
+
+  DcmTagKey key, upperKey;
+  DcmDictRangeRestriction groupRestriction = DcmDictRange_Unspecified;
+  DcmDictRangeRestriction elementRestriction = DcmDictRange_Unspecified;
+  DcmVR vr;
+  char* vrName;
+  char* tagName;
+  char* privCreator;
+  int vmMin, vmMax = 1;
+  const char* standardVersion;
+
+  LinesIterator iterator(content);
+
+  std::string line;
+  while (iterator.GetLine(line)) {
+    iterator.Next();
+
+    if (line.size() >= DCM_MAXDICTLINESIZE) {
+      DCMDATA_ERROR("DcmDataDictionary: Too long line: " << line);
+      continue;
+    }
+
+    lineNumber++;
+
+    if (onlyWhitespace(line.c_str())) {
+      continue; /* ignore this line */
+    }
+    if (isaCommentLine(line.c_str())) {
+      continue; /* ignore this line */
+    }
+
+    errorOnThisLine = OFFalse;
+
+    /* fields are tab separated */
+    fieldsPresent = splitFields(line.c_str(), lineFields,
+                                DCM_MAXDICTFIELDS,
+                                DCM_DICT_FIELD_SEPARATOR_CHAR);
+
+    /* initialize dict entry fields */
+    vrName = NULL;
+    tagName = NULL;
+    privCreator = NULL;
+    vmMin = vmMax = 1;
+    standardVersion = "DICOM";
+
+    switch (fieldsPresent) {
+      case 0:
+      case 1:
+      case 2:
+        DCMDATA_ERROR("DcmDataDictionary: "
+                      << "too few fields (line " << lineNumber << ")");
+        errorOnThisLine = OFTrue;
+        break;
+      default:
+        DCMDATA_ERROR("DcmDataDictionary: "
+                      << "too many fields (line " << lineNumber << "): ");
+        errorOnThisLine = OFTrue;
+        break;
+      case 5:
+        stripWhitespace(lineFields[4]);
+        standardVersion = lineFields[4];
+        /* drop through to next case label */
+      case 4:
+        /* the VM field is present */
+        if (!parseVMField(lineFields[3], vmMin, vmMax)) {
+          DCMDATA_ERROR("DcmDataDictionary: "
+                        << "bad VM field (line " << lineNumber << "): " << lineFields[3]);
+          errorOnThisLine = OFTrue;
+        }
+        /* drop through to next case label */
+      case 3:
+        if (!parseWholeTagField(lineFields[0], key, upperKey,
+                                groupRestriction, elementRestriction, privCreator))
+        {
+          DCMDATA_ERROR("DcmDataDictionary: "
+                        << "bad Tag field (line " << lineNumber << "): " << lineFields[0]);
+          errorOnThisLine = OFTrue;
+        } else {
+          /* all is OK */
+          vrName = lineFields[1];
+          stripWhitespace(vrName);
+
+          tagName = lineFields[2];
+          stripWhitespace(tagName);
+        }
+    }
+
+    if (!errorOnThisLine) {
+      /* check the VR Field */
+      vr.setVR(vrName);
+      if (vr.getEVR() == EVR_UNKNOWN) {
+        DCMDATA_ERROR("DcmDataDictionary: "
+                      << "bad VR field (line " << lineNumber << "): " << vrName);
+        errorOnThisLine = OFTrue;
+      }
+    }
+
+    if (!errorOnThisLine) {
+      e = new DcmDictEntry(
+        key.getGroup(), key.getElement(),
+        upperKey.getGroup(), upperKey.getElement(),
+        vr, tagName, vmMin, vmMax, standardVersion, OFTrue,
+        privCreator);
+
+      e->setGroupRangeRestriction(groupRestriction);
+      e->setElementRangeRestriction(elementRestriction);
+      addEntry(e);
+    }
+
+    for (i = 0; i < fieldsPresent; i++) {
+      free(lineFields[i]);
+      lineFields[i] = NULL;
+    }
+
+    delete[] privCreator;
+
+    if (errorOnThisLine) {
+      errorsEncountered++;
+    }
+  }
+
+  /* return OFFalse in case of errors and set internal state accordingly */
+  if (errorsEncountered == 0) {
+    dictionaryLoaded = OFTrue;
+    return OFTrue;
+  }
+  else {
+    dictionaryLoaded = OFFalse;
+    return OFFalse;
+  }
+}
--- a/Resources/Patches/dcmtk.txt	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Patches/dcmtk.txt	Thu Mar 19 11:48:30 2020 +0100
@@ -4,6 +4,7 @@
 diff -urEb dcmtk-3.6.0.orig/ dcmtk-3.6.0
 diff -urEb dcmtk-3.6.2.orig/ dcmtk-3.6.2
 diff -urEb dcmtk-3.6.4.orig/ dcmtk-3.6.4
+diff -urEb dcmtk-3.6.5.orig/ dcmtk-3.6.5
 
 For "dcmtk-3.6.2-private.dic"
 =============================
--- a/Resources/Patches/libp11-0.4.0.patch	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Patches/libp11-0.4.0.patch	Thu Mar 19 11:48:30 2020 +0100
@@ -1,6 +1,6 @@
 diff -urEb libp11-0.4.0.orig/src/atfork.c libp11-0.4.0/src/atfork.c
---- libp11-0.4.0.orig/src/atfork.c	2016-06-20 13:38:43.845575107 +0200
-+++ libp11-0.4.0/src/atfork.c	2016-06-20 13:46:52.969575591 +0200
+--- libp11-0.4.0.orig/src/atfork.c	2020-03-05 20:48:55.447852662 +0100
++++ libp11-0.4.0/src/atfork.c	2020-03-05 20:49:05.983770656 +0100
 @@ -25,7 +25,7 @@
  #include <sys/stat.h>
  #include <sys/types.h>
@@ -11,8 +11,8 @@
  #ifdef __sun
  # pragma fini(lib_deinit)
 diff -urEb libp11-0.4.0.orig/src/engine.h libp11-0.4.0/src/engine.h
---- libp11-0.4.0.orig/src/engine.h	2016-06-20 13:38:43.845575107 +0200
-+++ libp11-0.4.0/src/engine.h	2016-06-20 13:46:27.421575566 +0200
+--- libp11-0.4.0.orig/src/engine.h	2020-03-05 20:48:55.447852662 +0100
++++ libp11-0.4.0/src/engine.h	2020-03-05 20:49:05.983770656 +0100
 @@ -29,7 +29,7 @@
  #define _ENGINE_PKCS11_H
  
@@ -23,8 +23,8 @@
  
  #include "libp11.h"
 diff -urEb libp11-0.4.0.orig/src/libp11-int.h libp11-0.4.0/src/libp11-int.h
---- libp11-0.4.0.orig/src/libp11-int.h	2016-06-20 13:38:43.845575107 +0200
-+++ libp11-0.4.0/src/libp11-int.h	2016-06-20 13:46:27.421575566 +0200
+--- libp11-0.4.0.orig/src/libp11-int.h	2020-03-05 20:48:55.447852662 +0100
++++ libp11-0.4.0/src/libp11-int.h	2020-03-05 20:49:05.983770656 +0100
 @@ -20,7 +20,7 @@
  #define _LIBP11_INT_H
  
@@ -34,3 +34,31 @@
  #endif
  
  #include "libp11.h"
+diff -urEb libp11-0.4.0.orig/src/p11_key.c libp11-0.4.0/src/p11_key.c
+--- libp11-0.4.0.orig/src/p11_key.c	2020-03-05 20:48:55.447852662 +0100
++++ libp11-0.4.0/src/p11_key.c	2020-03-05 20:49:24.959625180 +0100
+@@ -21,6 +21,10 @@
+ #include <string.h>
+ #include <openssl/bn.h>
+ 
++#if OPENSSL_VERSION_NUMBER >= 0x10100000L // OpenSSL 1.0.2
++#  include <crypto/rsa/rsa_locl.h>
++#endif
++
+ #ifdef _WIN32
+ #define strncasecmp strnicmp
+ #endif
+diff -urEb libp11-0.4.0.orig/src/p11_rsa.c libp11-0.4.0/src/p11_rsa.c
+--- libp11-0.4.0.orig/src/p11_rsa.c	2020-03-05 20:48:55.447852662 +0100
++++ libp11-0.4.0/src/p11_rsa.c	2020-03-05 20:49:20.095662204 +0100
+@@ -27,6 +27,10 @@
+ #include <openssl/evp.h>
+ #include <openssl/rsa.h>
+ 
++#if OPENSSL_VERSION_NUMBER >= 0x10100000L // OpenSSL 1.0.2
++#  include <crypto/rsa/rsa_locl.h>
++#endif
++
+ static int rsa_ex_index = 0;
+ 
+ #if OPENSSL_VERSION_NUMBER < 0x10100003L
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Patches/openssl-1.1.1d-conf.h.in	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,122 @@
+/*
+ * {- join("\n * ", @autowarntext) -}
+ *
+ * Copyright 2016-2018 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the OpenSSL license (the "License").  You may not use
+ * this file except in compliance with the License.  You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#include <openssl/opensslv.h>
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+#ifdef OPENSSL_ALGORITHM_DEFINES
+# error OPENSSL_ALGORITHM_DEFINES no longer supported
+#endif
+
+
+/*
+ * Sometimes OPENSSSL_NO_xxx ends up with an empty file and some compilers
+ * don't like that.  This will hopefully silence them.
+ */
+#define NON_EMPTY_TRANSLATION_UNIT static void *dummy = &dummy;
+
+/*
+ * Applications should use -DOPENSSL_API_COMPAT=<version> to suppress the
+ * declarations of functions deprecated in or before <version>. Otherwise, they
+ * still won't see them if the library has been built to disable deprecated
+ * functions.
+ */
+#ifndef DECLARE_DEPRECATED
+# define DECLARE_DEPRECATED(f)   f;
+# ifdef __GNUC__
+#  if __GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ > 0)
+#   undef DECLARE_DEPRECATED
+#   define DECLARE_DEPRECATED(f)    f __attribute__ ((deprecated));
+#  endif
+# endif
+#endif
+
+#ifndef OPENSSL_FILE
+# ifdef OPENSSL_NO_FILENAMES
+#  define OPENSSL_FILE ""
+#  define OPENSSL_LINE 0
+# else
+#  define OPENSSL_FILE __FILE__
+#  define OPENSSL_LINE __LINE__
+# endif
+#endif
+
+#ifndef OPENSSL_MIN_API
+# define OPENSSL_MIN_API 0
+#endif
+
+#if !defined(OPENSSL_API_COMPAT) || OPENSSL_API_COMPAT < OPENSSL_MIN_API
+# undef OPENSSL_API_COMPAT
+# define OPENSSL_API_COMPAT OPENSSL_MIN_API
+#endif
+
+/*
+ * Do not deprecate things to be deprecated in version 1.2.0 before the
+ * OpenSSL version number matches.
+ */
+#if OPENSSL_VERSION_NUMBER < 0x10200000L
+# define DEPRECATEDIN_1_2_0(f)   f;
+#elif OPENSSL_API_COMPAT < 0x10200000L
+# define DEPRECATEDIN_1_2_0(f)   DECLARE_DEPRECATED(f)
+#else
+# define DEPRECATEDIN_1_2_0(f)
+#endif
+
+#if OPENSSL_API_COMPAT < 0x10100000L
+# define DEPRECATEDIN_1_1_0(f)   DECLARE_DEPRECATED(f)
+#else
+# define DEPRECATEDIN_1_1_0(f)
+#endif
+
+#if OPENSSL_API_COMPAT < 0x10000000L
+# define DEPRECATEDIN_1_0_0(f)   DECLARE_DEPRECATED(f)
+#else
+# define DEPRECATEDIN_1_0_0(f)
+#endif
+
+#if OPENSSL_API_COMPAT < 0x00908000L
+# define DEPRECATEDIN_0_9_8(f)   DECLARE_DEPRECATED(f)
+#else
+# define DEPRECATEDIN_0_9_8(f)
+#endif
+
+
+#define OPENSSL_UNISTD <unistd.h>
+
+#if 0
+/* Generate 80386 code? */
+{- ${processor} eq "386" ? "#define" : "#undef" -} I386_ONLY
+
+#undef OPENSSL_UNISTD
+#define OPENSSL_UNISTD {- ${unistd} -}
+
+{- ${export_var_as_fn} ? "#define" : "#undef" -} OPENSSL_EXPORT_VAR_AS_FUNCTION
+
+/*
+ * The following are cipher-specific, but are part of the public API.
+ */
+#if !defined(OPENSSL_SYS_UEFI)
+{- ${bn_ll} ? "# define" : "# undef" -} BN_LLONG
+/* Only one for the following should be defined */
+{- ${b64l} ? "# define" : "# undef" -} SIXTY_FOUR_BIT_LONG
+{- ${b64}  ? "# define" : "# undef" -} SIXTY_FOUR_BIT
+{- ${b32}  ? "# define" : "# undef" -} THIRTY_TWO_BIT
+#endif
+
+#define RC4_INT {- ${rc4_int} -}
+#endif
+
+#ifdef  __cplusplus
+}
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Patches/openssl-1.1.1d.patch	Thu Mar 19 11:48:30 2020 +0100
@@ -0,0 +1,12 @@
+diff -urEb openssl-1.1.1d.orig/crypto/rand/rand_unix.c openssl-1.1.1d/crypto/rand/rand_unix.c
+--- openssl-1.1.1d.orig/crypto/rand/rand_unix.c	2019-09-10 15:13:07.000000000 +0200
++++ openssl-1.1.1d/crypto/rand/rand_unix.c	2020-03-05 16:29:33.030136203 +0100
+@@ -340,7 +340,7 @@
+ #  endif
+ 
+     /* Linux supports this since version 3.17 */
+-#  if defined(__linux) && defined(__NR_getrandom)
++#  if defined(__linux) && defined(__NR_getrandom) && !defined(__LSB_VERSION__)
+     return syscall(__NR_getrandom, buf, buflen, 0);
+ #  elif (defined(__FreeBSD__) || defined(__NetBSD__)) && defined(KERN_ARND)
+     return sysctl_random(buf, buflen);
--- a/Resources/RetrieveCACertificates.py	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/RetrieveCACertificates.py	Thu Mar 19 11:48:30 2020 +0100
@@ -3,7 +3,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/CppHelpers/Logging/ILogger.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Samples/CppHelpers/Logging/ILogger.h	Thu Mar 19 11:48:30 2020 +0100
@@ -8,6 +8,21 @@
 namespace OrthancHelpers
 {
 
+
+  inline std::string ShortenId(const std::string& orthancUuid)
+  {
+    size_t firstHyphenPos = orthancUuid.find_first_of('-');
+    if (firstHyphenPos == std::string::npos)
+    {
+      return orthancUuid;
+    }
+    else
+    {
+      return orthancUuid.substr(0, firstHyphenPos);
+    }
+  }
+
+
   // Interface for loggers providing the same interface
   // in Orthanc framework or in an Orthanc plugins.
   // Furthermore, compared to the LOG and VLOG macros,
@@ -25,9 +40,9 @@
     virtual void Error(const char* message) = 0;
     virtual void Error(const std::string& message) = 0;
 
-    virtual void EnterContext(const char* message) = 0;
-    virtual void EnterContext(const std::string& message) = 0;
-    virtual void LeaveContext() = 0;
+    virtual void EnterContext(const char* message, bool forceLogContextChange = false) = 0;
+    virtual void EnterContext(const std::string& message, bool forceLogContextChange = false) = 0;
+    virtual void LeaveContext(bool forceLogContextChange = false) = 0;
   };
 
 
@@ -55,12 +70,12 @@
       logContextChanges_ = enable;
     }
 
-    virtual void EnterContext(const char* message)
+    virtual void EnterContext(const char* message, bool forceLogContextChange = false)
     {
-      EnterContext(std::string(message));
+      EnterContext(std::string(message), forceLogContextChange);
     }
 
-    virtual void EnterContext(const std::string& message)
+    virtual void EnterContext(const std::string& message, bool forceLogContextChange = false)
     {
       if (!contexts_.get())
       {
@@ -68,15 +83,15 @@
       }
       contexts_->push_back(message);
 
-      if (logContextChanges_)
+      if (logContextChanges_ || forceLogContextChange)
       {
         Info(".. entering");
       }
     }
 
-    virtual void LeaveContext()
+    virtual void LeaveContext(bool forceLogContextChange = false)
     {
-      if (logContextChanges_)
+      if (logContextChanges_ || forceLogContextChange)
       {
         Info(".. leaving");
       }
@@ -128,22 +143,25 @@
   class LogContext
   {
     ILogger* logger_;
+    bool     forceLogContextChange_;
   public:
-    LogContext(ILogger* logger, const char* context) :
-      logger_(logger)
+    LogContext(ILogger* logger, const char* context, bool forceLogContextChange = false) :
+      logger_(logger),
+      forceLogContextChange_(forceLogContextChange)
     {
-      logger_->EnterContext(context);
+      logger_->EnterContext(context, forceLogContextChange_);
     }
 
-    LogContext(ILogger* logger, const std::string& context) :
-      logger_(logger)
+    LogContext(ILogger* logger, const std::string& context, bool forceLogContextChange = false) :
+      logger_(logger),
+      forceLogContextChange_(forceLogContextChange)
     {
-      logger_->EnterContext(context);
+      logger_->EnterContext(context, forceLogContextChange_);
     }
 
     ~LogContext()
     {
-      logger_->LeaveContext();
+      logger_->LeaveContext(forceLogContextChange_);
     }
 
   };
--- a/Resources/Samples/ImportDicomFiles/ImportDicomFiles.py	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Samples/ImportDicomFiles/ImportDicomFiles.py	Thu Mar 19 11:48:30 2020 +0100
@@ -3,7 +3,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Lua/CallWebService.js	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Samples/Lua/CallWebService.js	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Lua/TransferSyntaxDisable.lua	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Samples/Lua/TransferSyntaxDisable.lua	Thu Mar 19 11:48:30 2020 +0100
@@ -22,6 +22,10 @@
    return false
 end
 
+function IsMpeg4TransferSyntaxAccepted(aet, ip)
+   return false
+end
+
 function IsRleTransferSyntaxAccepted(aet, ip)
    return false
 end
--- a/Resources/Samples/Lua/TransferSyntaxEnable.lua	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Samples/Lua/TransferSyntaxEnable.lua	Thu Mar 19 11:48:30 2020 +0100
@@ -22,6 +22,10 @@
    return true
 end
 
+function IsMpeg4TransferSyntaxAccepted(aet, ip)
+   return true
+end
+
 function IsRleTransferSyntaxAccepted(aet, ip)
    return true
 end
--- a/Resources/Samples/Python/AnonymizeAllPatients.py	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Samples/Python/AnonymizeAllPatients.py	Thu Mar 19 11:48:30 2020 +0100
@@ -4,7 +4,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Python/ArchiveAllPatients.py	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Samples/Python/ArchiveAllPatients.py	Thu Mar 19 11:48:30 2020 +0100
@@ -4,7 +4,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Python/ArchiveStudiesInTimeRange.py	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Samples/Python/ArchiveStudiesInTimeRange.py	Thu Mar 19 11:48:30 2020 +0100
@@ -4,7 +4,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Python/AutoClassify.py	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Samples/Python/AutoClassify.py	Thu Mar 19 11:48:30 2020 +0100
@@ -4,7 +4,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Python/ChangesLoop.py	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Samples/Python/ChangesLoop.py	Thu Mar 19 11:48:30 2020 +0100
@@ -3,7 +3,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Python/ContinuousPatientAnonymization.py	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Samples/Python/ContinuousPatientAnonymization.py	Thu Mar 19 11:48:30 2020 +0100
@@ -3,7 +3,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Python/DeleteAllStudies.py	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Samples/Python/DeleteAllStudies.py	Thu Mar 19 11:48:30 2020 +0100
@@ -4,7 +4,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Python/DownloadAnonymized.py	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Samples/Python/DownloadAnonymized.py	Thu Mar 19 11:48:30 2020 +0100
@@ -4,7 +4,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Python/HighPerformanceAutoRouting.py	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Samples/Python/HighPerformanceAutoRouting.py	Thu Mar 19 11:48:30 2020 +0100
@@ -4,7 +4,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Python/ManualModification.py	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Samples/Python/ManualModification.py	Thu Mar 19 11:48:30 2020 +0100
@@ -3,7 +3,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Python/Replicate.py	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Samples/Python/Replicate.py	Thu Mar 19 11:48:30 2020 +0100
@@ -3,7 +3,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Python/RestToolbox.py	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Samples/Python/RestToolbox.py	Thu Mar 19 11:48:30 2020 +0100
@@ -1,7 +1,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/Tools/RecoverCompressedFile.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Samples/Tools/RecoverCompressedFile.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/WebApplications/DrawingDicomizer.js	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Samples/WebApplications/DrawingDicomizer.js	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/WebApplications/DrawingDicomizer/orthanc.js	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Samples/WebApplications/DrawingDicomizer/orthanc.js	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Samples/WebApplications/NodeToolbox.js	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/Samples/WebApplications/NodeToolbox.js	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/WebAssembly/dcdict.cc	Wed Mar 18 08:59:06 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1087 +0,0 @@
-/*
- *
- *  Copyright (C) 1994-2016, OFFIS e.V.
- *  All rights reserved.  See COPYRIGHT file for details.
- *
- *  This software and supporting documentation were developed by
- *
- *    OFFIS e.V.
- *    R&D Division Health
- *    Escherweg 2
- *    D-26121 Oldenburg, Germany
- *
- *
- *  Module:  dcmdata
- *
- *  Author:  Andrew Hewett
- *
- *  Purpose: loadable DICOM data dictionary
- *
- */
-
-
-#include "dcmtk/config/osconfig.h"    /* make sure OS specific configuration is included first */
-
-#include "dcmtk/ofstd/ofstd.h"
-#include "dcmtk/dcmdata/dcdict.h"
-#include "dcmtk/ofstd/ofdefine.h"
-#include "dcmtk/dcmdata/dcdicent.h"
-#include "dcmtk/dcmdata/dctypes.h"
-
-#define INCLUDE_CSTDLIB
-#define INCLUDE_CSTDIO
-#define INCLUDE_CSTRING
-#define INCLUDE_CCTYPE
-#include "dcmtk/ofstd/ofstdinc.h"
-
-/*
-** The separator character between fields in the data dictionary file(s)
-*/
-#define DCM_DICT_FIELD_SEPARATOR_CHAR '\t'
-
-/*
-** Comment character for the data dictionary file(s)
-*/
-#define DCM_DICT_COMMENT_CHAR '#'
-
-/*
-** THE Global DICOM Data Dictionary
-*/
-
-GlobalDcmDataDictionary dcmDataDict;
-
-
-/*
-** Member Functions
-*/
-
-static DcmDictEntry*
-makeSkelEntry(Uint16 group, Uint16 element,
-              Uint16 upperGroup, Uint16 upperElement,
-              DcmEVR evr, const char* tagName, int vmMin, int vmMax,
-              const char* standardVersion,
-              DcmDictRangeRestriction groupRestriction,
-              DcmDictRangeRestriction elementRestriction,
-              const char* privCreator)
-{
-    DcmDictEntry* e = NULL;
-    e = new DcmDictEntry(group, element, upperGroup, upperElement, evr,
-                         tagName, vmMin, vmMax, standardVersion, OFFalse, privCreator);
-    if (e != NULL) {
-        e->setGroupRangeRestriction(groupRestriction);
-        e->setElementRangeRestriction(elementRestriction);
-    }
-    return e;
-}
-
-
-OFBool DcmDataDictionary::loadSkeletonDictionary()
-{
-    /*
-    ** We need to know about Group Lengths to compute them
-    */
-    DcmDictEntry* e = NULL;
-    e = makeSkelEntry(0x0000, 0x0000, 0xffff, 0x0000,
-                      EVR_UL, "GenericGroupLength", 1, 1, "GENERIC",
-                      DcmDictRange_Unspecified, DcmDictRange_Unspecified, NULL);
-    addEntry(e);
-
-    /*
-    ** We need to know about Items and Delimitation Items to parse
-    ** (and construct) sequences.
-    */
-    e = makeSkelEntry(0xfffe, 0xe000, 0xfffe, 0xe000,
-                      EVR_na, "Item", 1, 1, "DICOM",
-                      DcmDictRange_Unspecified, DcmDictRange_Unspecified, NULL);
-    addEntry(e);
-    e = makeSkelEntry(0xfffe, 0xe00d, 0xfffe, 0xe00d,
-                      EVR_na, "ItemDelimitationItem", 1, 1, "DICOM",
-                      DcmDictRange_Unspecified, DcmDictRange_Unspecified, NULL);
-    addEntry(e);
-    e = makeSkelEntry(0xfffe, 0xe0dd, 0xfffe, 0xe0dd,
-                      EVR_na, "SequenceDelimitationItem", 1, 1, "DICOM",
-                      DcmDictRange_Unspecified, DcmDictRange_Unspecified, NULL);
-    addEntry(e);
-
-    skeletonCount = numberOfEntries();
-    return OFTrue;
-}
-
-
-DcmDataDictionary::DcmDataDictionary(OFBool loadBuiltin, OFBool loadExternal)
-  : hashDict(),
-    repDict(),
-    skeletonCount(0),
-    dictionaryLoaded(OFFalse)
-{
-    reloadDictionaries(loadBuiltin, loadExternal);
-}
-
-DcmDataDictionary::~DcmDataDictionary()
-{
-    clear();
-}
-
-
-void DcmDataDictionary::clear()
-{
-   hashDict.clear();
-   repDict.clear();
-   skeletonCount = 0;
-   dictionaryLoaded = OFFalse;
-}
-
-
-static void
-stripWhitespace(char* s)
-{
-  if (s)
-  {
-    unsigned char c;
-    unsigned char *t;
-    unsigned char *p;
-    t=p=OFreinterpret_cast(unsigned char *, s);
-    while ((c = *t++)) if (!isspace(c)) *p++ = c;
-    *p = '\0';
-  }
-}
-
-static char*
-stripTrailingWhitespace(char* s)
-{
-    if (s == NULL) return s;
-    for
-    (
-        char* it = s + strlen(s) - 1;
-        it >= s && isspace(OFstatic_cast(unsigned char, *it));
-        *it-- = '\0'
-    );
-    return s;
-}
-
-static void
-stripLeadingWhitespace(char* s)
-{
-  if (s)
-  {
-    unsigned char c;
-    unsigned char *t;
-    unsigned char *p;
-    t=p=OFreinterpret_cast(unsigned char *, s);
-    while (isspace(*t)) t++;
-    while ((c = *t++)) *p++ = c;
-    *p = '\0';
-  }
-}
-
-static OFBool
-parseVMField(char* vmField, int& vmMin, int& vmMax)
-{
-    OFBool ok = OFTrue;
-    char c = 0;
-    int dummy = 0;
-
-    /* strip any whitespace */
-    stripWhitespace(vmField);
-
-    if (sscanf(vmField, "%d-%d%c", &vmMin, &dummy, &c) == 3) {
-        /* treat "2-2n" like "2-n" for the moment */
-        if ((c == 'n') || (c == 'N')) {
-            vmMax = DcmVariableVM;
-        } else {
-            ok = OFFalse;
-        }
-    } else if (sscanf(vmField, "%d-%d", &vmMin, &vmMax) == 2) {
-        /* range VM (e.g. "2-6") */
-    } else if (sscanf(vmField, "%d-%c", &vmMin, &c) == 2) {
-        if ((c == 'n') || (c == 'N')) {
-            vmMax = DcmVariableVM;
-        } else {
-            ok = OFFalse;
-        }
-    } else if (sscanf(vmField, "%d%c", &vmMin, &c) == 2) {
-        /* treat "2n" like "2-n" for the moment */
-        if ((c == 'n') || (c == 'N')) {
-            vmMax = DcmVariableVM;
-        } else {
-            ok = OFFalse;
-        }
-    } else if (sscanf(vmField, "%d", &vmMin) == 1) {
-        /* fixed VM */
-        vmMax = vmMin;
-    } else if (sscanf(vmField, "%c", &c) == 1) {
-        /* treat "n" like "1-n" */
-        if ((c == 'n') || (c == 'N')) {
-            vmMin = 1;
-            vmMax = DcmVariableVM;
-        } else {
-            ok = OFFalse;
-        }
-    } else {
-        ok = OFFalse;
-    }
-    return ok;
-}
-
-static int
-splitFields(const char* line, char* fields[], int maxFields, char splitChar)
-{
-    const char *p;
-    int foundFields = 0;
-    size_t len;
-
-    do {
-#ifdef __BORLANDC__
-        // Borland Builder expects a non-const argument
-        p = strchr(OFconst_cast(char *, line), splitChar);
-#else
-        p = strchr(line, splitChar);
-#endif
-        if (p == NULL) {
-            len = strlen(line);
-        } else {
-            len = p - line;
-        }
-        fields[foundFields] = OFstatic_cast(char *, malloc(len + 1));
-        strncpy(fields[foundFields], line, len);
-        fields[foundFields][len] = '\0';
-        foundFields++;
-        line = p + 1;
-    } while ((foundFields < maxFields) && (p != NULL));
-
-    return foundFields;
-}
-
-static OFBool
-parseTagPart(char *s, unsigned int& l, unsigned int& h,
-             DcmDictRangeRestriction& r)
-{
-    OFBool ok = OFTrue;
-    char restrictor = ' ';
-
-    r = DcmDictRange_Unspecified; /* by default */
-
-    if (sscanf(s, "%x-%c-%x", &l, &restrictor, &h) == 3) {
-        switch (restrictor) {
-        case 'o':
-        case 'O':
-            r = DcmDictRange_Odd;
-            break;
-        case 'e':
-        case 'E':
-            r = DcmDictRange_Even;
-            break;
-        case 'u':
-        case 'U':
-            r = DcmDictRange_Unspecified;
-            break;
-        default:
-            DCMDATA_ERROR("DcmDataDictionary: Unknown range restrictor: " << restrictor);
-            ok = OFFalse;
-            break;
-        }
-    } else if (sscanf(s, "%x-%x", &l, &h) == 2) {
-        r = DcmDictRange_Even; /* by default */
-    } else if (sscanf(s, "%x", &l) == 1) {
-        h = l;
-    } else {
-        ok = OFFalse;
-    }
-    return ok;
-}
-
-static OFBool
-parseWholeTagField(char* s, DcmTagKey& key,
-                   DcmTagKey& upperKey,
-                   DcmDictRangeRestriction& groupRestriction,
-                   DcmDictRangeRestriction& elementRestriction,
-                   char *&privCreator)
-{
-    unsigned int gl, gh, el, eh;
-    groupRestriction = DcmDictRange_Unspecified;
-    elementRestriction = DcmDictRange_Unspecified;
-
-    stripLeadingWhitespace(s);
-    stripTrailingWhitespace(s);
-
-    char gs[64];
-    char es[64];
-    char pc[64];
-    size_t slen = strlen(s);
-
-    if (s[0] != '(') return OFFalse;
-    if (s[slen - 1] != ')') return OFFalse;
-    if (strchr(s, ',') == NULL) return OFFalse;
-
-    /* separate the group and element parts */
-    int i = 1; /* after the '(' */
-    int gi = 0;
-    for (; s[i] != ',' && s[i] != '\0'; i++)
-    {
-        gs[gi] = s[i];
-        gi++;
-    }
-    gs[gi] = '\0';
-
-    if (s[i] == '\0') return OFFalse; /* element part missing */
-    i++; /* after the ',' */
-
-    stripLeadingWhitespace(s + i);
-
-    int pi = 0;
-    if (s[i] == '\"') /* private creator */
-    {
-        i++;  // skip opening quotation mark
-        for (; s[i] != '\"' && s[i] != '\0'; i++) pc[pi++] = s[i];
-        pc[pi] = '\0';
-        if (s[i] == '\0') return OFFalse; /* closing quotation mark missing */
-        i++;
-        stripLeadingWhitespace(s + i);
-        if (s[i] != ',') return OFFalse; /* element part missing */
-        i++; /* after the ',' */
-    }
-
-    int ei = 0;
-    for (; s[i] != ')' && s[i] != '\0'; i++) {
-        es[ei] = s[i];
-        ei++;
-    }
-    es[ei] = '\0';
-
-    /* parse the tag parts into their components */
-    stripWhitespace(gs);
-    if (parseTagPart(gs, gl, gh, groupRestriction) == OFFalse)
-        return OFFalse;
-
-    stripWhitespace(es);
-    if (parseTagPart(es, el, eh, elementRestriction) == OFFalse)
-        return OFFalse;
-
-    if (pi > 0)
-    {
-      // copy private creator name
-      privCreator = new char[strlen(pc) + 1]; // deleted by caller
-      if (privCreator) strcpy(privCreator,pc);
-    }
-
-    key.set(OFstatic_cast(unsigned short, gl), OFstatic_cast(unsigned short, el));
-    upperKey.set(OFstatic_cast(unsigned short, gh), OFstatic_cast(unsigned short, eh));
-
-    return OFTrue;
-}
-
-static OFBool
-onlyWhitespace(const char* s)
-{
-    size_t len = strlen(s);
-    int charsFound = OFFalse;
-
-    for (size_t i = 0; (!charsFound) && (i < len); ++i) {
-        charsFound = !isspace(OFstatic_cast(unsigned char, s[i]));
-    }
-    return (!charsFound)? (OFTrue) : (OFFalse);
-}
-
-static char*
-getLine(char* line, int maxLineLen, FILE* f)
-{
-    char* s;
-
-    s = fgets(line, maxLineLen, f);
-
-    /* strip any trailing white space */
-    stripTrailingWhitespace(line);
-
-    return s;
-}
-
-static OFBool
-isaCommentLine(const char* s)
-{
-    OFBool isComment = OFFalse; /* assumption */
-    size_t len = strlen(s);
-    size_t i = 0;
-    for (i = 0; i < len && isspace(OFstatic_cast(unsigned char, s[i])); ++i) /*loop*/;
-    isComment = (s[i] == DCM_DICT_COMMENT_CHAR);
-    return isComment;
-}
-
-OFBool
-DcmDataDictionary::reloadDictionaries(OFBool loadBuiltin, OFBool loadExternal)
-{
-    OFBool result = OFTrue;
-    clear();
-    loadSkeletonDictionary();
-    if (loadBuiltin) {
-        loadBuiltinDictionary();
-        dictionaryLoaded = (numberOfEntries() > skeletonCount);
-        if (!dictionaryLoaded) result = OFFalse;
-    }
-    if (loadExternal) {
-        if (loadExternalDictionaries())
-            dictionaryLoaded = OFTrue;
-        else
-            result = OFFalse;
-    }
-    return result;
-}
-
-OFBool
-DcmDataDictionary::loadDictionary(const char* fileName, OFBool errorIfAbsent)
-{
-
-    char lineBuf[DCM_MAXDICTLINESIZE + 1];
-    FILE* f = NULL;
-    int lineNumber = 0;
-    char* lineFields[DCM_MAXDICTFIELDS + 1];
-    int fieldsPresent;
-    DcmDictEntry* e;
-    int errorsEncountered = 0;
-    OFBool errorOnThisLine = OFFalse;
-    int i;
-
-    DcmTagKey key, upperKey;
-    DcmDictRangeRestriction groupRestriction = DcmDictRange_Unspecified;
-    DcmDictRangeRestriction elementRestriction = DcmDictRange_Unspecified;
-    DcmVR vr;
-    char* vrName;
-    char* tagName;
-    char* privCreator;
-    int vmMin, vmMax = 1;
-    const char* standardVersion;
-
-    /* first, check whether 'fileName' really points to a file (and not to a directory or the like) */
-    if (!OFStandard::fileExists(fileName) || (f = fopen(fileName, "r")) == NULL) {
-        if (errorIfAbsent) {
-            DCMDATA_ERROR("DcmDataDictionary: Cannot open file: " << fileName);
-        }
-        return OFFalse;
-    }
-
-    DCMDATA_DEBUG("DcmDataDictionary: Loading file: " << fileName);
-
-    while (getLine(lineBuf, DCM_MAXDICTLINESIZE, f)) {
-        lineNumber++;
-
-        if (onlyWhitespace(lineBuf)) {
-            continue; /* ignore this line */
-        }
-        if (isaCommentLine(lineBuf)) {
-            continue; /* ignore this line */
-        }
-
-        errorOnThisLine = OFFalse;
-
-        /* fields are tab separated */
-        fieldsPresent = splitFields(lineBuf, lineFields,
-                                    DCM_MAXDICTFIELDS,
-                                    DCM_DICT_FIELD_SEPARATOR_CHAR);
-
-        /* initialize dict entry fields */
-        vrName = NULL;
-        tagName = NULL;
-        privCreator = NULL;
-        vmMin = vmMax = 1;
-        standardVersion = "DICOM";
-
-        switch (fieldsPresent) {
-        case 0:
-        case 1:
-        case 2:
-            DCMDATA_ERROR("DcmDataDictionary: "<< fileName << ": "
-                 << "too few fields (line " << lineNumber << ")");
-            errorOnThisLine = OFTrue;
-            break;
-        default:
-            DCMDATA_ERROR("DcmDataDictionary: " << fileName << ": "
-                 << "too many fields (line " << lineNumber << "): ");
-            errorOnThisLine = OFTrue;
-            break;
-        case 5:
-            stripWhitespace(lineFields[4]);
-            standardVersion = lineFields[4];
-            /* drop through to next case label */
-        case 4:
-            /* the VM field is present */
-            if (!parseVMField(lineFields[3], vmMin, vmMax)) {
-                DCMDATA_ERROR("DcmDataDictionary: " << fileName << ": "
-                     << "bad VM field (line " << lineNumber << "): " << lineFields[3]);
-                errorOnThisLine = OFTrue;
-            }
-            /* drop through to next case label */
-        case 3:
-            if (!parseWholeTagField(lineFields[0], key, upperKey,
-                 groupRestriction, elementRestriction, privCreator))
-            {
-                DCMDATA_ERROR("DcmDataDictionary: " << fileName << ": "
-                     << "bad Tag field (line " << lineNumber << "): " << lineFields[0]);
-                errorOnThisLine = OFTrue;
-            } else {
-                /* all is OK */
-                vrName = lineFields[1];
-                stripWhitespace(vrName);
-
-                tagName = lineFields[2];
-                stripWhitespace(tagName);
-            }
-        }
-
-        if (!errorOnThisLine) {
-            /* check the VR Field */
-            vr.setVR(vrName);
-            if (vr.getEVR() == EVR_UNKNOWN) {
-                DCMDATA_ERROR("DcmDataDictionary: " << fileName << ": "
-                     << "bad VR field (line " << lineNumber << "): " << vrName);
-                errorOnThisLine = OFTrue;
-            }
-        }
-
-        if (!errorOnThisLine) {
-            e = new DcmDictEntry(
-                key.getGroup(), key.getElement(),
-                upperKey.getGroup(), upperKey.getElement(),
-                vr, tagName, vmMin, vmMax, standardVersion, OFTrue,
-                privCreator);
-
-            e->setGroupRangeRestriction(groupRestriction);
-            e->setElementRangeRestriction(elementRestriction);
-            addEntry(e);
-        }
-
-        for (i = 0; i < fieldsPresent; i++) {
-            free(lineFields[i]);
-            lineFields[i] = NULL;
-        }
-
-        delete[] privCreator;
-
-        if (errorOnThisLine) {
-            errorsEncountered++;
-        }
-    }
-
-    fclose(f);
-
-    /* return OFFalse in case of errors and set internal state accordingly */
-    if (errorsEncountered == 0) {
-        dictionaryLoaded = OFTrue;
-        return OFTrue;
-    }
-    else {
-        dictionaryLoaded = OFFalse;
-        return OFFalse;
-    }
-}
-
-#ifndef HAVE_GETENV
-
-static
-char* getenv() {
-    return NULL;
-}
-
-#endif /* !HAVE_GETENV */
-
-
-
-OFBool
-DcmDataDictionary::loadExternalDictionaries()
-{
-    const char* env = NULL;
-    size_t len;
-    int sepCnt = 0;
-    OFBool msgIfDictAbsent = OFTrue;
-    OFBool loadFailed = OFFalse;
-
-    env = getenv(DCM_DICT_ENVIRONMENT_VARIABLE);
-    if ((env == NULL) || (strlen(env) == 0)) {
-        env = DCM_DICT_DEFAULT_PATH;
-        msgIfDictAbsent = OFFalse;
-    }
-
-    if ((env != NULL) && (strlen(env) != 0)) {
-        len = strlen(env);
-        for (size_t i = 0; i < len; ++i) {
-            if (env[i] == ENVIRONMENT_PATH_SEPARATOR) {
-                sepCnt++;
-            }
-        }
-
-        if (sepCnt == 0) {
-            if (!loadDictionary(env, msgIfDictAbsent)) {
-                return OFFalse;
-            }
-        } else {
-            char** dictArray;
-
-            dictArray = OFstatic_cast(char **, malloc((sepCnt + 1) * sizeof(char*)));
-
-            int ndicts = splitFields(env, dictArray, sepCnt + 1,
-                                     ENVIRONMENT_PATH_SEPARATOR);
-
-            for (int ii = 0; ii < ndicts; ii++) {
-                if ((dictArray[ii] != NULL) && (strlen(dictArray[ii]) > 0)) {
-                    if (!loadDictionary(dictArray[ii], msgIfDictAbsent)) {
-                        loadFailed = OFTrue;
-                    }
-                }
-                free(dictArray[ii]);
-            }
-            free(dictArray);
-        }
-    }
-
-    return (loadFailed) ? (OFFalse) : (OFTrue);
-}
-
-
-void
-DcmDataDictionary::addEntry(DcmDictEntry* e)
-{
-    if (e->isRepeating()) {
-        /*
-         * Find the best position in repeating tag list
-         * Existing entries are replaced if the ranges and repetition
-         * constraints are the same.
-         * If a range represents a subset of an existing range then it
-         * will be placed before it in the list.  This ensures that a
-         * search will find the subset rather than the superset.
-         * Otherwise entries are appended to the end of the list.
-         */
-        OFBool inserted = OFFalse;
-
-        DcmDictEntryListIterator iter(repDict.begin());
-        DcmDictEntryListIterator last(repDict.end());
-        for (; !inserted && iter != last; ++iter) {
-            if (e->setEQ(**iter)) {
-                /* replace the old entry with the new */
-                DcmDictEntry *old = *iter;
-                *iter = e;
-#ifdef PRINT_REPLACED_DICTIONARY_ENTRIES
-                DCMDATA_WARN("replacing " << *old);
-#endif
-                delete old;
-                inserted = OFTrue;
-            } else if (e->subset(**iter)) {
-                /* e is a subset of the current list position, insert before */
-                repDict.insert(iter, e);
-                inserted = OFTrue;
-            }
-        }
-        if (!inserted) {
-            /* insert at end */
-            repDict.push_back(e);
-            inserted = OFTrue;
-        }
-    } else {
-        hashDict.put(e);
-    }
-}
-
-void
-DcmDataDictionary::deleteEntry(const DcmDictEntry& entry)
-{
-    DcmDictEntry* e = NULL;
-    e = OFconst_cast(DcmDictEntry *, findEntry(entry));
-    if (e != NULL) {
-        if (e->isRepeating()) {
-            repDict.remove(e);
-            delete e;
-        } else {
-            hashDict.del(entry.getKey(), entry.getPrivateCreator());
-        }
-    }
-}
-
-const DcmDictEntry*
-DcmDataDictionary::findEntry(const DcmDictEntry& entry) const
-{
-    const DcmDictEntry* e = NULL;
-
-    if (entry.isRepeating()) {
-        OFBool found = OFFalse;
-        DcmDictEntryListConstIterator iter(repDict.begin());
-        DcmDictEntryListConstIterator last(repDict.end());
-        for (; !found && iter != last; ++iter) {
-            if (entry.setEQ(**iter)) {
-                found = OFTrue;
-                e = *iter;
-            }
-        }
-    } else {
-        e = hashDict.get(entry, entry.getPrivateCreator());
-    }
-    return e;
-}
-
-const DcmDictEntry*
-DcmDataDictionary::findEntry(const DcmTagKey& key, const char *privCreator) const
-{
-    /* search first in the normal tags dictionary and if not found
-     * then search in the repeating tags list.
-     */
-    const DcmDictEntry* e = NULL;
-
-    e = hashDict.get(key, privCreator);
-    if (e == NULL) {
-        /* search in the repeating tags dictionary */
-        OFBool found = OFFalse;
-        DcmDictEntryListConstIterator iter(repDict.begin());
-        DcmDictEntryListConstIterator last(repDict.end());
-        for (; !found && iter != last; ++iter) {
-            if ((*iter)->contains(key, privCreator)) {
-                found = OFTrue;
-                e = *iter;
-            }
-        }
-    }
-    return e;
-}
-
-const DcmDictEntry*
-DcmDataDictionary::findEntry(const char *name) const
-{
-    const DcmDictEntry* e = NULL;
-    const DcmDictEntry* ePrivate = NULL;
-
-    /* search first in the normal tags dictionary and if not found
-     * then search in the repeating tags list.
-     */
-    DcmHashDictIterator iter;
-    for (iter = hashDict.begin(); (e == NULL) && (iter != hashDict.end()); ++iter) {
-        if ((*iter)->contains(name)) {
-            e = *iter;
-            if (e->getGroup() % 2)
-            {
-                /* tag is a private tag - continue search to be sure to find non-private keys first */
-                if (!ePrivate) ePrivate = e;
-                e = NULL;
-            }
-        }
-    }
-
-    if (e == NULL) {
-        /* search in the repeating tags dictionary */
-        OFBool found = OFFalse;
-        DcmDictEntryListConstIterator iter2(repDict.begin());
-        DcmDictEntryListConstIterator last(repDict.end());
-        for (; !found && iter2 != last; ++iter2) {
-            if ((*iter2)->contains(name)) {
-                found = OFTrue;
-                e = *iter2;
-            }
-        }
-    }
-
-    if (e == NULL && ePrivate != NULL) {
-        /* no standard key found - use the first private key found */
-        e = ePrivate;
-    }
-
-    return e;
-}
-
-
-/* ================================================================== */
-
-
-GlobalDcmDataDictionary::GlobalDcmDataDictionary()
-  : dataDict(NULL)
-#ifdef WITH_THREADS
-  , dataDictLock()
-#endif
-{
-}
-
-GlobalDcmDataDictionary::~GlobalDcmDataDictionary()
-{
-  /* No threads may be active any more, so no locking needed */
-  delete dataDict;
-}
-
-void GlobalDcmDataDictionary::createDataDict()
-{
-  /* Make sure only one thread tries to initialize the dictionary */
-#ifdef WITH_THREADS
-  dataDictLock.wrlock();
-#endif
-#ifdef DONT_LOAD_EXTERNAL_DICTIONARIES
-  const OFBool loadExternal = OFFalse;
-#else
-  const OFBool loadExternal = OFTrue;
-#endif
-  /* Make sure no other thread managed to create the dictionary
-   * before we got our write lock. */
-  if (!dataDict)
-    dataDict = new DcmDataDictionary(OFTrue /*loadBuiltin*/, loadExternal);
-#ifdef WITH_THREADS
-  dataDictLock.unlock();
-#endif
-}
-
-const DcmDataDictionary& GlobalDcmDataDictionary::rdlock()
-{
-#ifdef WITH_THREADS
-  dataDictLock.rdlock();
-#endif
-  if (!dataDict)
-  {
-    /* dataDictLock must not be locked during createDataDict() */
-#ifdef WITH_THREADS
-    dataDictLock.unlock();
-#endif
-    createDataDict();
-#ifdef WITH_THREADS
-    dataDictLock.rdlock();
-#endif
-  }
-  return *dataDict;
-}
-
-DcmDataDictionary& GlobalDcmDataDictionary::wrlock()
-{
-#ifdef WITH_THREADS
-  dataDictLock.wrlock();
-#endif
-  if (!dataDict)
-  {
-    /* dataDictLock must not be locked during createDataDict() */
-#ifdef WITH_THREADS
-    dataDictLock.unlock();
-#endif
-    createDataDict();
-#ifdef WITH_THREADS
-    dataDictLock.wrlock();
-#endif
-  }
-  return *dataDict;
-}
-
-void GlobalDcmDataDictionary::unlock()
-{
-#ifdef WITH_THREADS
-  dataDictLock.unlock();
-#endif
-}
-
-OFBool GlobalDcmDataDictionary::isDictionaryLoaded()
-{
-  OFBool result = rdlock().isDictionaryLoaded();
-  unlock();
-  return result;
-}
-
-void GlobalDcmDataDictionary::clear()
-{
-  wrlock().clear();
-  unlock();
-}
-
-
-
-
-// Function by the Orthanc project to load a dictionary from a memory
-// buffer, which is necessary in sandboxed environments. This is an
-// adapted version of DcmDataDictionary::loadDictionary().
-
-
-#include <boost/noncopyable.hpp>
-
-struct OrthancLinesIterator;
-
-// This plain old C class is implemented in "../../Core/Toolbox.h"
-OrthancLinesIterator* OrthancLinesIterator_Create(const std::string& content);
-
-bool OrthancLinesIterator_GetLine(std::string& target,
-                                  const OrthancLinesIterator* iterator);
-
-void OrthancLinesIterator_Next(OrthancLinesIterator* iterator);
-
-void OrthancLinesIterator_Free(OrthancLinesIterator* iterator);
-
-
-class LinesIterator : public boost::noncopyable
-{
-private:
-  OrthancLinesIterator* iterator_;
-  
-public:
-  LinesIterator(const std::string& content) :
-    iterator_(NULL)
-  {
-    iterator_ = OrthancLinesIterator_Create(content);
-  }
-
-  ~LinesIterator()
-  {
-    if (iterator_ != NULL)
-    {
-      OrthancLinesIterator_Free(iterator_);
-      iterator_ = NULL;
-    }
-  }
-  
-  bool GetLine(std::string& target) const
-  {
-    if (iterator_ != NULL)
-    {
-      return OrthancLinesIterator_GetLine(target, iterator_);
-    }
-    else
-    {
-      return false;
-    }
-  }
-
-  void Next()
-  {
-    if (iterator_ != NULL)
-    {
-      OrthancLinesIterator_Next(iterator_);
-    }
-  }
-};
-
-
-
-OFBool
-DcmDataDictionary::loadFromMemory(const std::string& content, OFBool errorIfAbsent)
-{
-  int lineNumber = 0;
-  char* lineFields[DCM_MAXDICTFIELDS + 1];
-  int fieldsPresent;
-  DcmDictEntry* e;
-  int errorsEncountered = 0;
-  OFBool errorOnThisLine = OFFalse;
-  int i;
-
-  DcmTagKey key, upperKey;
-  DcmDictRangeRestriction groupRestriction = DcmDictRange_Unspecified;
-  DcmDictRangeRestriction elementRestriction = DcmDictRange_Unspecified;
-  DcmVR vr;
-  char* vrName;
-  char* tagName;
-  char* privCreator;
-  int vmMin, vmMax = 1;
-  const char* standardVersion;
-
-  LinesIterator iterator(content);
-
-  std::string line;
-  while (iterator.GetLine(line)) {
-    iterator.Next();
-
-    if (line.size() >= DCM_MAXDICTLINESIZE) {
-      DCMDATA_ERROR("DcmDataDictionary: Too long line: " << line);
-      continue;
-    }
-
-    lineNumber++;
-
-    if (onlyWhitespace(line.c_str())) {
-      continue; /* ignore this line */
-    }
-    if (isaCommentLine(line.c_str())) {
-      continue; /* ignore this line */
-    }
-
-    errorOnThisLine = OFFalse;
-
-    /* fields are tab separated */
-    fieldsPresent = splitFields(line.c_str(), lineFields,
-                                DCM_MAXDICTFIELDS,
-                                DCM_DICT_FIELD_SEPARATOR_CHAR);
-
-    /* initialize dict entry fields */
-    vrName = NULL;
-    tagName = NULL;
-    privCreator = NULL;
-    vmMin = vmMax = 1;
-    standardVersion = "DICOM";
-
-    switch (fieldsPresent) {
-      case 0:
-      case 1:
-      case 2:
-        DCMDATA_ERROR("DcmDataDictionary: "
-                      << "too few fields (line " << lineNumber << ")");
-        errorOnThisLine = OFTrue;
-        break;
-      default:
-        DCMDATA_ERROR("DcmDataDictionary: "
-                      << "too many fields (line " << lineNumber << "): ");
-        errorOnThisLine = OFTrue;
-        break;
-      case 5:
-        stripWhitespace(lineFields[4]);
-        standardVersion = lineFields[4];
-        /* drop through to next case label */
-      case 4:
-        /* the VM field is present */
-        if (!parseVMField(lineFields[3], vmMin, vmMax)) {
-          DCMDATA_ERROR("DcmDataDictionary: "
-                        << "bad VM field (line " << lineNumber << "): " << lineFields[3]);
-          errorOnThisLine = OFTrue;
-        }
-        /* drop through to next case label */
-      case 3:
-        if (!parseWholeTagField(lineFields[0], key, upperKey,
-                                groupRestriction, elementRestriction, privCreator))
-        {
-          DCMDATA_ERROR("DcmDataDictionary: "
-                        << "bad Tag field (line " << lineNumber << "): " << lineFields[0]);
-          errorOnThisLine = OFTrue;
-        } else {
-          /* all is OK */
-          vrName = lineFields[1];
-          stripWhitespace(vrName);
-
-          tagName = lineFields[2];
-          stripWhitespace(tagName);
-        }
-    }
-
-    if (!errorOnThisLine) {
-      /* check the VR Field */
-      vr.setVR(vrName);
-      if (vr.getEVR() == EVR_UNKNOWN) {
-        DCMDATA_ERROR("DcmDataDictionary: "
-                      << "bad VR field (line " << lineNumber << "): " << vrName);
-        errorOnThisLine = OFTrue;
-      }
-    }
-
-    if (!errorOnThisLine) {
-      e = new DcmDictEntry(
-        key.getGroup(), key.getElement(),
-        upperKey.getGroup(), upperKey.getElement(),
-        vr, tagName, vmMin, vmMax, standardVersion, OFTrue,
-        privCreator);
-
-      e->setGroupRangeRestriction(groupRestriction);
-      e->setElementRangeRestriction(elementRestriction);
-      addEntry(e);
-    }
-
-    for (i = 0; i < fieldsPresent; i++) {
-      free(lineFields[i]);
-      lineFields[i] = NULL;
-    }
-
-    delete[] privCreator;
-
-    if (errorOnThisLine) {
-      errorsEncountered++;
-    }
-  }
-
-  /* return OFFalse in case of errors and set internal state accordingly */
-  if (errorsEncountered == 0) {
-    dictionaryLoaded = OFTrue;
-    return OFTrue;
-  }
-  else {
-    dictionaryLoaded = OFFalse;
-    return OFFalse;
-  }
-}
--- a/Resources/WebAssembly/dcdict.h	Wed Mar 18 08:59:06 2020 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,302 +0,0 @@
-/*
- *
- *  Copyright (C) 1994-2015, OFFIS e.V.
- *  All rights reserved.  See COPYRIGHT file for details.
- *
- *  This software and supporting documentation were developed by
- *
- *    OFFIS e.V.
- *    R&D Division Health
- *    Escherweg 2
- *    D-26121 Oldenburg, Germany
- *
- *
- *  Module:  dcmdata
- *
- *  Author:  Andrew Hewett
- *
- *  Purpose: Interface for loadable DICOM data dictionary
- *
- */
-
-
-#ifndef DCMDICT_H
-#define DCMDICT_H
-
-#include "dcmtk/config/osconfig.h"    /* make sure OS specific configuration is included first */
-
-#include "dcmtk/ofstd/ofthread.h"
-#include "dcmtk/dcmdata/dchashdi.h"
-
-/// maximum length of a line in the loadable DICOM dictionary
-#define DCM_MAXDICTLINESIZE     2048
-
-/// maximum number of fields per entry in the loadable DICOM dictionary
-#define DCM_MAXDICTFIELDS       6
-
-/// environment variable pointing to the data dictionary file
-#define DCM_DICT_ENVIRONMENT_VARIABLE   "DCMDICTPATH"
-
-#ifndef DCM_DICT_DEFAULT_PATH
-/*
-** The default dictionary path is system dependent.  It should
-** be defined in a configuration file included from "osconfig.h"
-*/
-#error "DCM_DICT_DEFAULT_PATH is not defined via osconfig.h"
-#endif /* !DCM_DICT_DEFAULT_PATH */
-
-#ifndef ENVIRONMENT_PATH_SEPARATOR
-#define ENVIRONMENT_PATH_SEPARATOR '\n' /* at least define something unlikely */
-#endif
-
-
-/** this class implements a loadable DICOM Data Dictionary
- */
-class DCMTK_DCMDATA_EXPORT DcmDataDictionary
-{
-public:
-
-    /** constructor
-     *  @param loadBuiltin flag indicating if a built-in data dictionary
-     *    (if any) should be loaded.
-     *  @param loadExternal flag indicating if an external data dictionary
-     *    should be read from file.
-     */
-    DcmDataDictionary(OFBool loadBuiltin, OFBool loadExternal);
-
-    /// destructor
-    ~DcmDataDictionary();
-
-    /** checks if a data dictionary is loaded (excluding the skeleton dictionary)
-     *  @return true if loaded, false if no dictionary is present
-     */
-    OFBool isDictionaryLoaded() const { return dictionaryLoaded; }
-
-    /// returns the number of normal (non-repeating) tag entries
-    int numberOfNormalTagEntries() const { return hashDict.size(); }
-
-    /// returns the number of repeating tag entries
-    int numberOfRepeatingTagEntries() const { return OFstatic_cast(int, repDict.size()); }
-
-    /** returns the number of dictionary entries that were loaded
-     *  either from file or from a built-in dictionary or both.
-     */
-    int numberOfEntries() const
-        { return numberOfNormalTagEntries()
-              + numberOfRepeatingTagEntries() - skeletonCount; }
-
-    /** returns the number of skeleton entries. The skeleton is a collection
-     *  of dictionary entries which are always present, even if neither internal
-     *  nor external dictionary have been loaded. It contains very basic
-     *  things like item delimitation and sequence delimitation.
-     */
-    int numberOfSkeletonEntries() const { return skeletonCount; }
-
-    /** reload data dictionaries. First, all dictionary entries are deleted.
-     *  @param loadBuiltin flag indicating if a built-in data dictionary
-     *    (if any) should be loaded.
-     *  @param loadExternal flag indicating if an external data dictionary
-     *    should be read from file.
-     *  @return true if reload was successful, false if an error occurred
-     */
-    OFBool reloadDictionaries(OFBool loadBuiltin, OFBool loadExternal);
-
-    /** load a particular dictionary from file.
-     *  @param fileName filename
-     *  @param errorIfAbsent causes the method to return false
-     *     if the file cannot be opened
-     *  @return false if the file contains a parse error or if the file could
-     *     not be opened and errorIfAbsent was set, true otherwise.
-     */
-    OFBool loadDictionary(const char* fileName, OFBool errorIfAbsent = OFTrue);
-
-    /** dictionary lookup for the given tag key and private creator name.
-     *  First the normal tag dictionary is searched.  If not found
-     *  then the repeating tag dictionary is searched.
-     *  @param key tag key
-     *  @param privCreator private creator name, may be NULL
-     */
-    const DcmDictEntry* findEntry(const DcmTagKey& key, const char *privCreator) const;
-
-    /** dictionary lookup for the given attribute name.
-     *  First the normal tag dictionary is searched.  If not found
-     *  then the repeating tag dictionary is searched.
-     *  Only considers standard attributes (i. e. without private creator)
-     *  @param name attribute name
-     */
-    const DcmDictEntry* findEntry(const char *name) const;
-
-    /// deletes all dictionary entries
-    void clear();
-
-    /** adds an entry to the dictionary.  Must be allocated via new.
-     *  The entry becomes the property of the dictionary and will be
-     *  deallocated (via delete) upon clear() or dictionary destruction.
-     *  If an equivalent entry already exists it will be replaced by
-     *  the new entry and the old entry deallocated (via delete).
-     *  @param entry pointer to new entry
-     */
-    void addEntry(DcmDictEntry* entry);
-
-    /* Iterators to access the normal and the repeating entries */
-
-    /// returns an iterator to the start of the normal (non-repeating) dictionary
-    DcmHashDictIterator normalBegin() { return hashDict.begin(); }
-
-    /// returns an iterator to the end of the normal (non-repeating) dictionary
-    DcmHashDictIterator normalEnd() { return hashDict.end(); }
-
-    /// returns an iterator to the start of the repeating tag dictionary
-    DcmDictEntryListIterator repeatingBegin() { return repDict.begin(); }
-
-    /// returns an iterator to the end of the repeating tag dictionary
-    DcmDictEntryListIterator repeatingEnd() { return repDict.end(); }
-
-    // Function by the Orthanc project to load a dictionary from a
-    // memory buffer, which is necessary in sandboxed
-    // environments. This is an adapted version of
-    // DcmDataDictionary::loadDictionary().
-    OFBool loadFromMemory(const std::string& content, OFBool errorIfAbsent = OFTrue);
-
-private:
-
-    /** private undefined assignment operator
-     */
-    DcmDataDictionary &operator=(const DcmDataDictionary &);
-
-    /** private undefined copy constructor
-     */
-    DcmDataDictionary(const DcmDataDictionary &);
-
-    /** loads external dictionaries defined via environment variables
-     *  @return true if successful
-     */
-    OFBool loadExternalDictionaries();
-
-    /** loads a builtin (compiled) data dictionary.
-     *  Depending on which code is in use, this function may not
-     *  do anything.
-     */
-    void loadBuiltinDictionary();
-
-    /** loads the skeleton dictionary (the bare minimum needed to run)
-     *  @return true if successful
-     */
-    OFBool loadSkeletonDictionary();
-
-    /** looks up the given directory entry in the two dictionaries.
-     *  @return pointer to entry if found, NULL otherwise
-     */
-    const DcmDictEntry* findEntry(const DcmDictEntry& entry) const;
-
-    /** deletes the given entry from either dictionary
-     */
-    void deleteEntry(const DcmDictEntry& entry);
-
-
-    /** dictionary of normal tags
-     */
-    DcmHashDict hashDict;
-
-    /** dictionary of repeating tags
-     */
-    DcmDictEntryList repDict;
-
-    /** the number of skeleton entries
-     */
-    int skeletonCount;
-
-    /** is a dictionary loaded (more than skeleton)
-     */
-    OFBool dictionaryLoaded;
-
-};
-
-
-/** global singleton dicom dictionary that is used by DCMTK in order to lookup
- *  attribute VR, tag names and so on.  The dictionary is internally populated
- *  on first use, if the user accesses it via rdlock() or wrlock().  The
- *  dictionary allows safe read (shared) and write (exclusive) access from
- *  multiple threads in parallel.
- */
-class DCMTK_DCMDATA_EXPORT GlobalDcmDataDictionary
-{
-public:
-  /** constructor.
-   */
-  GlobalDcmDataDictionary();
-
-  /** destructor
-   */
-  ~GlobalDcmDataDictionary();
-
-  /** acquires a read lock and returns a const reference to
-   *  the dictionary.
-   *  @return const reference to dictionary
-   */
-  const DcmDataDictionary& rdlock();
-
-  /** acquires a write lock and returns a non-const reference
-   *  to the dictionary.
-   *  @return non-const reference to dictionary.
-   */
-  DcmDataDictionary& wrlock();
-
-  /** unlocks the read or write lock which must have been acquired previously.
-   */
-  void unlock();
-
-  /** checks if a data dictionary has been loaded. This method acquires and
-   *  releases a read lock. It must not be called with another lock on the
-   *  dictionary being held by the calling thread.
-   *  @return OFTrue if dictionary has been loaded, OFFalse otherwise.
-   */
-  OFBool isDictionaryLoaded();
-
-  /** erases the contents of the dictionary. This method acquires and
-   *  releases a write lock. It must not be called with another lock on the
-   *  dictionary being held by the calling thread.  This method is intended
-   *  as a help for debugging memory leaks.
-   */
-  void clear();
-
-private:
-  /** private undefined assignment operator
-   */
-  GlobalDcmDataDictionary &operator=(const GlobalDcmDataDictionary &);
-
-  /** private undefined copy constructor
-   */
-  GlobalDcmDataDictionary(const GlobalDcmDataDictionary &);
-
-  /** create the data dictionary instance for this class. Used for first
-   * intialization.  The caller must not have dataDictLock locked.
-   */
-  void createDataDict();
-
-  /** the data dictionary managed by this class
-   */
-  DcmDataDictionary *dataDict;
-
-#ifdef WITH_THREADS
-  /** the read/write lock used to protect access from multiple threads
-   */
-  OFReadWriteLock dataDictLock;
-#endif
-};
-
-
-/** The Global DICOM Data Dictionary.
- *  Will be created before main() starts and gets populated on its first use.
- *  Tries to load a builtin data dictionary (if compiled in).
- *  Tries to load data dictionaries from files specified by
- *  the DCMDICTPATH environment variable.  If this environment
- *  variable does not exist then a default file is loaded (if
- *  it exists).
- *  It is possible that no data dictionary gets loaded.  This
- *  is likely to cause unexpected behaviour in the dcmdata
- *  toolkit classes.
- */
-extern DCMTK_DCMDATA_EXPORT GlobalDcmDataDictionary dcmDataDict;
-
-#endif
--- a/Resources/WindowsResources.py	Wed Mar 18 08:59:06 2020 +0100
+++ b/Resources/WindowsResources.py	Thu Mar 19 11:48:30 2020 +0100
@@ -3,7 +3,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2019 Osimis S.A., Belgium
+# Copyright (C) 2017-2020 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
--- a/TODO	Wed Mar 18 08:59:06 2020 +0100
+++ b/TODO	Thu Mar 19 11:48:30 2020 +0100
@@ -114,7 +114,7 @@
 * Image transcoding API
 * Add plugins for normalized operations (notably so as to support
   Print SCU/SCP):
-  https://www.medicalconnections.co.uk/kb/DICOM_Print_Service
+  https://web.archive.org/web/20170923150432/https://www.medicalconnections.co.uk/kb/DICOM_Print_Service
 * Provide access to the Orthanc::DicomUserConnection class in plugins:
   https://groups.google.com/d/msg/orthanc-users/ycDA1xPuTRY/nsT2_GOtEgAJ
 * Provide a C++ callback similar to "ReceivedInstanceFilter()" in Lua
@@ -193,3 +193,31 @@
 
 * Create REST bindings with Slicer
 * Create REST bindings with Horos/OsiriX
+
+
+====
+Misc
+====
+
+-------
+Logging
+-------
+
+This is a wish expressed in issue #65 on BitBucket:
+
+"Different levels for various modules (nice to have)
+
+We often need to debug DICOM interactions and logs are 'polluted' by
+logs from the Rest API, i.e: since I have a process calling the
+/changes route every 5 second, I'm lost in 17000 /changes record in my
+logs everyday while I just want to check what kind of C-Find requests
+are issued by a modality. If we go for it, logs could be configured
+via the configuration file: i.e.:
+
+{
+  "Web": "info",
+  "Dicom": "debug",
+  "Db": "warning",
+  "WebViewer": "warning", // here WebViewer is a plugin
+  "Generic": "warning", // for all stuffs we could not port to the new logging API or old plugins ....
+}"
--- a/UnitTestsSources/DatabaseLookupTests.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/DatabaseLookupTests.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/DicomMapTests.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/DicomMapTests.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -34,6 +34,7 @@
 #include "PrecompiledHeadersUnitTests.h"
 #include "gtest/gtest.h"
 
+#include "../Core/Compatibility.h"
 #include "../Core/OrthancException.h"
 #include "../Core/DicomFormat/DicomMap.h"
 #include "../Core/DicomParsing/FromDcmtkBridge.h"
@@ -121,7 +122,7 @@
   ASSERT_EQ(1u, s.size());
   ASSERT_EQ(DICOM_TAG_PATIENT_NAME, *s.begin());
 
-  std::auto_ptr<DicomMap> mm(m.Clone());
+  std::unique_ptr<DicomMap> mm(m.Clone());
   ASSERT_EQ("PatientName", mm->GetValue(DICOM_TAG_PATIENT_NAME).GetContent());  
 
   m.SetValue(DICOM_TAG_PATIENT_ID, "Hello", false);
@@ -450,10 +451,10 @@
   dataset.insertEmptyElement(DCM_StudyID, OFFalse);
 
   {
-    std::auto_ptr<DcmSequenceOfItems> sequence(new DcmSequenceOfItems(DCM_ReferencedSeriesSequence));
+    std::unique_ptr<DcmSequenceOfItems> sequence(new DcmSequenceOfItems(DCM_ReferencedSeriesSequence));
 
     {
-      std::auto_ptr<DcmItem> item(new DcmItem);
+      std::unique_ptr<DcmItem> item(new DcmItem);
       item->putAndInsertString(DCM_ReferencedSOPInstanceUID, "nope", OFFalse);
       ASSERT_TRUE(sequence->insert(item.release(), false, false).good());
     }
@@ -566,6 +567,24 @@
 }
 
 
+TEST(DicomMap, RemoveBinary)
+{
+  DicomMap b;
+  b.SetValue(DICOM_TAG_PATIENT_NAME, "A", false);
+  b.SetValue(DICOM_TAG_PATIENT_ID, "B", true);
+  b.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, DicomValue());  // NULL
+  b.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, DicomValue("C", false));
+  b.SetValue(DICOM_TAG_SOP_INSTANCE_UID, DicomValue("D", true));
+
+  b.RemoveBinaryTags();
+
+  std::string s;
+  ASSERT_EQ(2u, b.GetSize());
+  ASSERT_TRUE(b.LookupStringValue(s, DICOM_TAG_PATIENT_NAME, false)); ASSERT_EQ("A", s);
+  ASSERT_TRUE(b.LookupStringValue(s, DICOM_TAG_SERIES_INSTANCE_UID, false)); ASSERT_EQ("C", s);
+}
+
+
 
 TEST(DicomWebJson, Multiplicity)
 {
@@ -576,7 +595,7 @@
   dicom.ReplacePlainString(DICOM_TAG_IMAGE_ORIENTATION_PATIENT, "1\\2.3\\4");
   dicom.ReplacePlainString(DICOM_TAG_IMAGE_POSITION_PATIENT, "");
 
-  Orthanc::DicomWebJsonVisitor visitor;
+  DicomWebJsonVisitor visitor;
   dicom.Apply(visitor);
 
   {
@@ -615,7 +634,7 @@
     ASSERT_TRUE(m.LookupStringValue(s, DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false));
 
     std::vector<std::string> v;
-    Orthanc::Toolbox::TokenizeString(v, s, '\\');
+    Toolbox::TokenizeString(v, s, '\\');
     ASSERT_FLOAT_EQ(1.0f, boost::lexical_cast<float>(v[0]));
     ASSERT_FLOAT_EQ(2.3f, boost::lexical_cast<float>(v[1]));
     ASSERT_FLOAT_EQ(4.0f, boost::lexical_cast<float>(v[2]));
@@ -630,7 +649,7 @@
   ParsedDicomFile dicom(false);
   dicom.ReplacePlainString(DICOM_TAG_IMAGE_ORIENTATION_PATIENT, "1.5\\\\\\2.5");
 
-  Orthanc::DicomWebJsonVisitor visitor;
+  DicomWebJsonVisitor visitor;
   dicom.Apply(visitor);
 
   {
@@ -660,7 +679,7 @@
     ASSERT_TRUE(m.LookupStringValue(s, DICOM_TAG_IMAGE_ORIENTATION_PATIENT, false));
 
     std::vector<std::string> v;
-    Orthanc::Toolbox::TokenizeString(v, s, '\\');
+    Toolbox::TokenizeString(v, s, '\\');
     ASSERT_FLOAT_EQ(1.5f, boost::lexical_cast<float>(v[0]));
     ASSERT_TRUE(v[1].empty());
     ASSERT_TRUE(v[2].empty());
@@ -677,7 +696,7 @@
   // "dicom.GetDcmtkObject().getDataset()->putAndInsertTagKey(tag,
   // value)" that was not available in DCMTK 3.6.0
 
-  std::auto_ptr<DcmAttributeTag> element(new DcmAttributeTag(ToDcmtkBridge::Convert(tag)));
+  std::unique_ptr<DcmAttributeTag> element(new DcmAttributeTag(ToDcmtkBridge::Convert(tag)));
 
   DcmTagKey v = ToDcmtkBridge::Convert(value);
   if (!element->putTagVal(v).good())
@@ -707,9 +726,9 @@
   dicom.ReplacePlainString(DicomTag(0x0008, 0x0070), "LO");
   dicom.ReplacePlainString(DicomTag(0x0010, 0x4000), "LT");
   dicom.ReplacePlainString(DicomTag(0x0028, 0x2000), "OB");
-  dicom.ReplacePlainString(DicomTag(0x7fe0, 0x0009), "OD");
-  dicom.ReplacePlainString(DicomTag(0x0064, 0x0009), "OF");
-  dicom.ReplacePlainString(DicomTag(0x0066, 0x0040), "46");
+  dicom.ReplacePlainString(DicomTag(0x7fe0, 0x0009), "3.14159");  // OD (other double)
+  dicom.ReplacePlainString(DicomTag(0x0064, 0x0009), "2.71828");  // OF (other float)
+  dicom.ReplacePlainString(DicomTag(0x0066, 0x0040), "46");  // OL (other long)
   ASSERT_THROW(dicom.ReplacePlainString(DicomTag(0x0028, 0x1201), "O"), OrthancException);
   dicom.ReplacePlainString(DicomTag(0x0028, 0x1201), "OWOW");
   dicom.ReplacePlainString(DicomTag(0x0010, 0x0010), "PN");
@@ -726,7 +745,7 @@
   dicom.ReplacePlainString(DicomTag(0x0008, 0x0301), "17");   // US
   dicom.ReplacePlainString(DicomTag(0x0040, 0x0031), "UT");  
 
-  Orthanc::DicomWebJsonVisitor visitor;
+  DicomWebJsonVisitor visitor;
   dicom.Apply(visitor);
 
   std::string s;
@@ -766,16 +785,17 @@
 
 #if DCMTK_VERSION_NUMBER >= 361
   ASSERT_EQ("OD", visitor.GetResult() ["7FE00009"]["vr"].asString());
+  ASSERT_FLOAT_EQ(3.14159f, boost::lexical_cast<float>(visitor.GetResult() ["7FE00009"]["Value"][0].asString()));
 #else
   ASSERT_EQ("UN", visitor.GetResult() ["7FE00009"]["vr"].asString());
+  Toolbox::DecodeBase64(s, visitor.GetResult() ["7FE00009"]["InlineBinary"].asString());
+  ASSERT_EQ(8u, s.size()); // Because of padding
+  ASSERT_EQ(0, s[7]);
+  ASSERT_EQ("3.14159", s.substr(0, 7));
 #endif
 
-  Toolbox::DecodeBase64(s, visitor.GetResult() ["7FE00009"]["InlineBinary"].asString());
-  ASSERT_EQ("OD", s);
-
   ASSERT_EQ("OF", visitor.GetResult() ["00640009"]["vr"].asString());
-  Toolbox::DecodeBase64(s, visitor.GetResult() ["00640009"]["InlineBinary"].asString());
-  ASSERT_EQ("OF", s);
+  ASSERT_FLOAT_EQ(2.71828f, boost::lexical_cast<float>(visitor.GetResult() ["00640009"]["Value"][0].asString()));
 
 #if DCMTK_VERSION_NUMBER < 361
   ASSERT_EQ("UN", visitor.GetResult() ["00660040"]["vr"].asString());
@@ -784,10 +804,9 @@
 #elif DCMTK_VERSION_NUMBER == 361
   ASSERT_EQ("UL", visitor.GetResult() ["00660040"]["vr"].asString());
   ASSERT_EQ(46, visitor.GetResult() ["00660040"]["Value"][0].asInt());
-#elif DCMTK_VERSION_NUMBER > 361
+#else
   ASSERT_EQ("OL", visitor.GetResult() ["00660040"]["vr"].asString());
-  Toolbox::DecodeBase64(s, visitor.GetResult() ["00660040"]["InlineBinary"].asString());
-  ASSERT_EQ("46", s);
+  ASSERT_EQ(46, visitor.GetResult() ["00660040"]["Value"][0].asInt());
 #endif
 
   ASSERT_EQ("OW", visitor.GetResult() ["00281201"]["vr"].asString());
@@ -875,8 +894,18 @@
     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0070), false));  ASSERT_EQ("LO", s);
     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x4000), false));  ASSERT_EQ("LT", s);
     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0028, 0x2000), true));   ASSERT_EQ("OB", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x7fe0, 0x0009), true));   ASSERT_EQ("OD", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0064, 0x0009), true));   ASSERT_EQ("OF", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x7fe0, 0x0009), true));
+
+#if DCMTK_VERSION_NUMBER >= 361
+    ASSERT_FLOAT_EQ(3.14159f, boost::lexical_cast<float>(s));
+#else
+    ASSERT_EQ(8u, s.size()); // Because of padding
+    ASSERT_EQ(0, s[7]);
+    ASSERT_EQ("3.14159", s.substr(0, 7));
+#endif
+
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0064, 0x0009), true));
+    ASSERT_FLOAT_EQ(2.71828f, boost::lexical_cast<float>(s));
     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0028, 0x1201), true));   ASSERT_EQ("OWOW", s);
     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x0010), false));  ASSERT_EQ("PN", s);
     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0050), false));  ASSERT_EQ("SH", s);
@@ -884,20 +913,23 @@
     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0018, 0x9219), false));  ASSERT_EQ("-16", s);
     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0081), false));  ASSERT_EQ("ST", s);
     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0013), false));  ASSERT_EQ("TM", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0119), false));  ASSERT_EQ("UC", s);
     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0016), false));  ASSERT_EQ("UI", s);
     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x1161), false));  ASSERT_EQ("128", s);
     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x4342, 0x1234), true));   ASSERT_EQ("UN", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0120), false));  ASSERT_EQ("UR", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0301), false));  ASSERT_EQ("17", s);
     ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0040, 0x0031), false));  ASSERT_EQ("UT", s);
 
-#if DCMTK_VERSION_NUMBER == 361
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0066, 0x0040), false));
+#if DCMTK_VERSION_NUMBER >= 361
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0066, 0x0040), false));  ASSERT_EQ("46", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0119), false));  ASSERT_EQ("UC", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0120), false));  ASSERT_EQ("UR", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0301), false));  ASSERT_EQ("17", s);
 #else
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0066, 0x0040), true));
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0066, 0x0040), true));  ASSERT_EQ("46", s);  // OL
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0119), true));  ASSERT_EQ("UC", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0120), true));  ASSERT_EQ("UR", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0301), true));  ASSERT_EQ("17", s);  // US (but tag unknown to DCMTK 3.6.0)
 #endif
-    ASSERT_EQ("46", s);
+    
   }
 }
 
@@ -907,11 +939,11 @@
   ParsedDicomFile dicom(false);
   
   {
-    std::auto_ptr<DcmSequenceOfItems> sequence(new DcmSequenceOfItems(DCM_ReferencedSeriesSequence));
+    std::unique_ptr<DcmSequenceOfItems> sequence(new DcmSequenceOfItems(DCM_ReferencedSeriesSequence));
 
     for (unsigned int i = 0; i < 3; i++)
     {
-      std::auto_ptr<DcmItem> item(new DcmItem);
+      std::unique_ptr<DcmItem> item(new DcmItem);
       std::string s = "item" + boost::lexical_cast<std::string>(i);
       item->putAndInsertString(DCM_ReferencedSOPInstanceUID, s.c_str(), OFFalse);
       ASSERT_TRUE(sequence->insert(item.release(), false, false).good());
@@ -920,7 +952,7 @@
     ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->insert(sequence.release(), false, false).good());
   }
 
-  Orthanc::DicomWebJsonVisitor visitor;
+  DicomWebJsonVisitor visitor;
   dicom.Apply(visitor);
 
   ASSERT_EQ("SQ", visitor.GetResult() ["00081115"]["vr"].asString());
@@ -947,6 +979,70 @@
   {
     DicomMap m;
     m.FromDicomWeb(visitor.GetResult());
-    ASSERT_EQ(0u, m.GetSize());  // Sequences are not handled by Orthanc::DicomMap
+    ASSERT_EQ(0u, m.GetSize());  // Sequences are not handled by DicomMap
   }
 }
+
+
+TEST(DicomWebJson, PixelSpacing)
+{
+  // Test related to locales: Make sure that decimal separator is
+  // correctly handled (dot "." vs comma ",")
+  ParsedDicomFile source(false);
+  source.ReplacePlainString(DICOM_TAG_PIXEL_SPACING, "1.5\\1.3");
+
+  DicomWebJsonVisitor visitor;
+  source.Apply(visitor);
+
+  DicomMap target;
+  target.FromDicomWeb(visitor.GetResult());
+
+  ASSERT_EQ("DS", visitor.GetResult() ["00280030"]["vr"].asString());
+  ASSERT_FLOAT_EQ(1.5f, visitor.GetResult() ["00280030"]["Value"][0].asFloat());
+  ASSERT_FLOAT_EQ(1.3f, visitor.GetResult() ["00280030"]["Value"][1].asFloat());
+
+  std::string s;
+  ASSERT_TRUE(target.LookupStringValue(s, DICOM_TAG_PIXEL_SPACING, false));
+  ASSERT_EQ(s, "1.5\\1.3");
+}
+
+
+TEST(DicomMap, MainTagNames)
+{
+  ASSERT_EQ(3, ResourceType_Instance - ResourceType_Patient);
+  
+  for (int i = ResourceType_Patient; i <= ResourceType_Instance; i++)
+  {
+    ResourceType level = static_cast<ResourceType>(i);
+
+    std::set<DicomTag> tags;
+    DicomMap::GetMainDicomTags(tags, level);
+
+    for (std::set<DicomTag>::const_iterator it = tags.begin(); it != tags.end(); ++it)
+    {
+      DicomMap a;
+      a.SetValue(*it, "TEST", false);
+
+      Json::Value json;
+      a.DumpMainDicomTags(json, level);
+
+      ASSERT_EQ(Json::objectValue, json.type());
+      ASSERT_EQ(1u, json.getMemberNames().size());
+
+      std::string name = json.getMemberNames() [0];
+      EXPECT_EQ(name, FromDcmtkBridge::GetTagName(*it, ""));
+
+      DicomMap b;
+      b.ParseMainDicomTags(json, level);
+
+      ASSERT_EQ(1u, b.GetSize());
+      ASSERT_EQ("TEST", b.GetStringValue(*it, "", false));
+
+      std::string main = it->GetMainTagsName();
+      if (!main.empty())
+      {
+        ASSERT_EQ(main, name);
+      }
+    }
+  }
+}
--- a/UnitTestsSources/FileStorageTests.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/FileStorageTests.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/FromDcmtkTests.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/FromDcmtkTests.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -34,6 +34,7 @@
 #include "PrecompiledHeadersUnitTests.h"
 #include "gtest/gtest.h"
 
+#include "../Core/Compatibility.h"
 #include "../Core/DicomNetworking/DicomFindAnswers.h"
 #include "../Core/DicomParsing/DicomModification.h"
 #include "../Core/DicomParsing/DicomWebJsonVisitor.h"
@@ -95,7 +96,7 @@
   {
     char b[1024];
     sprintf(b, "UnitTestsResults/anon%06d.dcm", i);
-    std::auto_ptr<ParsedDicomFile> f(o.Clone(false));
+    std::unique_ptr<ParsedDicomFile> f(o.Clone(false));
     if (i > 4)
       o.ReplacePlainString(DICOM_TAG_SERIES_INSTANCE_UID, "coucou");
     m.Apply(*f);
@@ -108,7 +109,7 @@
 {
   ASSERT_EQ(DICOM_TAG_PATIENT_NAME, FromDcmtkBridge::ParseTag("PatientName"));
 
-  const DicomTag privateTag(0x0045, 0x0010);
+  const DicomTag privateTag(0x0045, 0x1010);
   const DicomTag privateTag2(FromDcmtkBridge::ParseTag("0031-1020"));
   ASSERT_TRUE(privateTag.IsPrivate());
   ASSERT_TRUE(privateTag2.IsPrivate());
@@ -119,19 +120,19 @@
   ParsedDicomFile o(true);
   o.ReplacePlainString(DICOM_TAG_PATIENT_NAME, "coucou");
   ASSERT_FALSE(o.GetTagValue(s, privateTag));
-  o.Insert(privateTag, "private tag", false);
+  o.Insert(privateTag, "private tag", false, "OrthancCreator");
   ASSERT_TRUE(o.GetTagValue(s, privateTag));
   ASSERT_STREQ("private tag", s.c_str());
 
   ASSERT_FALSE(o.GetTagValue(s, privateTag2));
-  ASSERT_THROW(o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_ThrowIfAbsent), OrthancException);
+  ASSERT_THROW(o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_ThrowIfAbsent, "OrthancCreator"), OrthancException);
   ASSERT_FALSE(o.GetTagValue(s, privateTag2));
-  o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_IgnoreIfAbsent);
+  o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_IgnoreIfAbsent, "OrthancCreator");
   ASSERT_FALSE(o.GetTagValue(s, privateTag2));
-  o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_InsertIfAbsent);
+  o.Replace(privateTag2, std::string("hello"), false, DicomReplaceMode_InsertIfAbsent, "OrthancCreator");
   ASSERT_TRUE(o.GetTagValue(s, privateTag2));
   ASSERT_STREQ("hello", s.c_str());
-  o.ReplacePlainString(privateTag2, "hello world");
+  o.Replace(privateTag2, std::string("hello world"), false, DicomReplaceMode_InsertIfAbsent, "OrthancCreator");
   ASSERT_TRUE(o.GetTagValue(s, privateTag2));
   ASSERT_STREQ("hello world", s.c_str());
 
@@ -290,7 +291,7 @@
       f.SetEncoding(testEncodings[i]);
 
       std::string s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i], false);
-      f.Insert(DICOM_TAG_PATIENT_NAME, s, false);
+      f.Insert(DICOM_TAG_PATIENT_NAME, s, false, "");
       f.SaveToMemoryBuffer(dicom);
     }
 
@@ -402,12 +403,12 @@
   // https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md#private-class-members
   TEST(FromDcmtkBridge, FromJson)
   {
-    std::auto_ptr<DcmElement> element;
+    std::unique_ptr<DcmElement> element;
 
     {
       Json::Value a;
       a = "Hello";
-      element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, false, Encoding_Utf8));
+      element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, false, Encoding_Utf8, ""));
 
       Json::Value b;
       std::set<DicomTag> ignoreTagLength;
@@ -439,20 +440,20 @@
       Json::Value a;
       a = "Hello";
       // Cannot assign a string to a sequence
-      ASSERT_THROW(element.reset(FromDcmtkBridge::FromJson(REFERENCED_STUDY_SEQUENCE, a, false, Encoding_Utf8)), OrthancException);
+      ASSERT_THROW(element.reset(FromDcmtkBridge::FromJson(REFERENCED_STUDY_SEQUENCE, a, false, Encoding_Utf8, "")), OrthancException);
     }
 
     {
       Json::Value a = Json::arrayValue;
       a.append("Hello");
       // Cannot assign an array to a string
-      ASSERT_THROW(element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, false, Encoding_Utf8)), OrthancException);
+      ASSERT_THROW(element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, false, Encoding_Utf8, "")), OrthancException);
     }
 
     {
       Json::Value a;
       a = "data:application/octet-stream;base64,SGVsbG8=";  // echo -n "Hello" | base64
-      element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, true, Encoding_Utf8));
+      element.reset(FromDcmtkBridge::FromJson(DICOM_TAG_PATIENT_NAME, a, true, Encoding_Utf8, ""));
 
       Json::Value b;
       std::set<DicomTag> ignoreTagLength;
@@ -464,7 +465,7 @@
     {
       Json::Value a = Json::arrayValue;
       CreateSampleJson(a);
-      element.reset(FromDcmtkBridge::FromJson(REFERENCED_STUDY_SEQUENCE, a, true, Encoding_Utf8));
+      element.reset(FromDcmtkBridge::FromJson(REFERENCED_STUDY_SEQUENCE, a, true, Encoding_Utf8, ""));
 
       {
         Json::Value b;
@@ -506,8 +507,8 @@
 {
   ParsedDicomFile f(true);
 
-  f.Insert(DICOM_TAG_PATIENT_NAME, "World", false);
-  ASSERT_THROW(f.Insert(DICOM_TAG_PATIENT_ID, "Hello", false), OrthancException);  // Already existing tag
+  f.Insert(DICOM_TAG_PATIENT_NAME, "World", false, "");
+  ASSERT_THROW(f.Insert(DICOM_TAG_PATIENT_ID, "Hello", false, ""), OrthancException);  // Already existing tag
   f.ReplacePlainString(DICOM_TAG_SOP_INSTANCE_UID, "Toto");  // (*)
   f.ReplacePlainString(DICOM_TAG_SOP_CLASS_UID, "Tata");  // (**)
 
@@ -515,16 +516,16 @@
   ASSERT_FALSE(f.LookupTransferSyntax(s));
 
   ASSERT_THROW(f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"),
-                         false, DicomReplaceMode_ThrowIfAbsent), OrthancException);
-  f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"), false, DicomReplaceMode_IgnoreIfAbsent);
+                         false, DicomReplaceMode_ThrowIfAbsent, ""), OrthancException);
+  f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"), false, DicomReplaceMode_IgnoreIfAbsent, "");
   ASSERT_FALSE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
-  f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"), false, DicomReplaceMode_InsertIfAbsent);
+  f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession"), false, DicomReplaceMode_InsertIfAbsent, "");
   ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
   ASSERT_EQ(s, "Accession");
-  f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession2"), false, DicomReplaceMode_IgnoreIfAbsent);
+  f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession2"), false, DicomReplaceMode_IgnoreIfAbsent, "");
   ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
   ASSERT_EQ(s, "Accession2");
-  f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession3"), false, DicomReplaceMode_ThrowIfAbsent);
+  f.Replace(DICOM_TAG_ACCESSION_NUMBER, std::string("Accession3"), false, DicomReplaceMode_ThrowIfAbsent, "");
   ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_ACCESSION_NUMBER));
   ASSERT_EQ(s, "Accession3");
 
@@ -552,20 +553,20 @@
 
   ASSERT_FALSE(f.HasTag(REFERENCED_STUDY_SEQUENCE));
   f.Remove(REFERENCED_STUDY_SEQUENCE);  // No effect
-  f.Insert(REFERENCED_STUDY_SEQUENCE, a, true);
+  f.Insert(REFERENCED_STUDY_SEQUENCE, a, true, "");
   ASSERT_TRUE(f.HasTag(REFERENCED_STUDY_SEQUENCE));
-  ASSERT_THROW(f.Insert(REFERENCED_STUDY_SEQUENCE, a, true), OrthancException);
+  ASSERT_THROW(f.Insert(REFERENCED_STUDY_SEQUENCE, a, true, ""), OrthancException);
   f.Remove(REFERENCED_STUDY_SEQUENCE);
   ASSERT_FALSE(f.HasTag(REFERENCED_STUDY_SEQUENCE));
-  f.Insert(REFERENCED_STUDY_SEQUENCE, a, true);
+  f.Insert(REFERENCED_STUDY_SEQUENCE, a, true, "");
   ASSERT_TRUE(f.HasTag(REFERENCED_STUDY_SEQUENCE));
 
   ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE));
-  ASSERT_THROW(f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_ThrowIfAbsent), OrthancException);
+  ASSERT_THROW(f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_ThrowIfAbsent, ""), OrthancException);
   ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE));
-  f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_IgnoreIfAbsent);
+  f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_IgnoreIfAbsent, "");
   ASSERT_FALSE(f.HasTag(REFERENCED_PATIENT_SEQUENCE));
-  f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_InsertIfAbsent);
+  f.Replace(REFERENCED_PATIENT_SEQUENCE, a, false, DicomReplaceMode_InsertIfAbsent, "");
   ASSERT_TRUE(f.HasTag(REFERENCED_PATIENT_SEQUENCE));
 
   {
@@ -580,8 +581,8 @@
   }
 
   a = "data:application/octet-stream;base64,VGF0YQ==";   // echo -n "Tata" | base64 
-  f.Replace(DICOM_TAG_SOP_INSTANCE_UID, a, false, DicomReplaceMode_InsertIfAbsent);  // (*)
-  f.Replace(DICOM_TAG_SOP_CLASS_UID, a, true, DicomReplaceMode_InsertIfAbsent);  // (**)
+  f.Replace(DICOM_TAG_SOP_INSTANCE_UID, a, false, DicomReplaceMode_InsertIfAbsent, "");  // (*)
+  f.Replace(DICOM_TAG_SOP_CLASS_UID, a, true, DicomReplaceMode_InsertIfAbsent, "");  // (**)
 
   std::string s;
   ASSERT_TRUE(f.GetTagValue(s, DICOM_TAG_SOP_INSTANCE_UID));
@@ -614,7 +615,7 @@
       }
 
       Json::Value s = Toolbox::ConvertToUtf8(testEncodingsEncoded[i], testEncodings[i], false);
-      f.Replace(DICOM_TAG_PATIENT_NAME, s, false, DicomReplaceMode_InsertIfAbsent);
+      f.Replace(DICOM_TAG_PATIENT_NAME, s, false, DicomReplaceMode_InsertIfAbsent, "");
 
       Json::Value v;
       f.DatasetToJson(v, DicomToJsonFormat_Human, DicomToJsonFlags_Default, 0);
@@ -626,13 +627,13 @@
 
 TEST(ParsedDicomFile, ToJsonFlags1)
 {
-  FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7053, 0x1000), ValueRepresentation_PersonName, "MyPrivateTag", 1, 1, "");
+  FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7053, 0x1000), ValueRepresentation_OtherByte, "MyPrivateTag", 1, 1, "OrthancCreator");
   FromDcmtkBridge::RegisterDictionaryTag(DicomTag(0x7050, 0x1000), ValueRepresentation_PersonName, "Declared public tag", 1, 1, "");
 
   ParsedDicomFile f(true);
-  f.Insert(DicomTag(0x7050, 0x1000), "Some public tag", false);  // Even group => public tag
-  f.Insert(DicomTag(0x7052, 0x1000), "Some unknown tag", false);  // Even group => public, unknown tag
-  f.Insert(DicomTag(0x7053, 0x1000), "Some private tag", false);  // Odd group => private tag
+  f.Insert(DicomTag(0x7050, 0x1000), "Some public tag", false, "");  // Even group => public tag
+  f.Insert(DicomTag(0x7052, 0x1000), "Some unknown tag", false, "");  // Even group => public, unknown tag
+  f.Insert(DicomTag(0x7053, 0x1000), "Some private tag", false, "OrthancCreator");  // Odd group => private tag
 
   Json::Value v;
   f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
@@ -644,7 +645,7 @@
   ASSERT_EQ(Json::stringValue, v["7050,1000"].type());
   ASSERT_EQ("Some public tag", v["7050,1000"].asString());
 
-  f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_ConvertBinaryToNull), 0);
+  f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_IncludeBinary | DicomToJsonFlags_ConvertBinaryToNull), 0);
   ASSERT_EQ(Json::objectValue, v.type());
   ASSERT_EQ(7u, v.getMemberNames().size());
   ASSERT_FALSE(v.isMember("7052,1000"));
@@ -653,7 +654,14 @@
   ASSERT_EQ("Some public tag", v["7050,1000"].asString());
   ASSERT_EQ(Json::nullValue, v["7053,1000"].type());
 
-  f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_IncludePrivateTags, 0);
+  f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePrivateTags), 0);
+  ASSERT_EQ(Json::objectValue, v.type());
+  ASSERT_EQ(6u, v.getMemberNames().size());
+  ASSERT_FALSE(v.isMember("7052,1000"));
+  ASSERT_TRUE(v.isMember("7050,1000"));
+  ASSERT_FALSE(v.isMember("7053,1000"));
+
+  f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_IncludeBinary), 0);
   ASSERT_EQ(Json::objectValue, v.type());
   ASSERT_EQ(7u, v.getMemberNames().size());
   ASSERT_FALSE(v.isMember("7052,1000"));
@@ -666,7 +674,7 @@
   ASSERT_EQ("application/octet-stream", mime);
   ASSERT_EQ("Some private tag", content);
 
-  f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_ConvertBinaryToNull), 0);
+  f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludeBinary | DicomToJsonFlags_ConvertBinaryToNull), 0);
   ASSERT_EQ(Json::objectValue, v.type());
   ASSERT_EQ(7u, v.getMemberNames().size());
   ASSERT_TRUE(v.isMember("7050,1000"));
@@ -675,7 +683,7 @@
   ASSERT_EQ("Some public tag", v["7050,1000"].asString());
   ASSERT_EQ(Json::nullValue, v["7052,1000"].type());
 
-  f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags), 0);
+  f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludeBinary), 0);
   ASSERT_EQ(Json::objectValue, v.type());
   ASSERT_EQ(7u, v.getMemberNames().size());
   ASSERT_TRUE(v.isMember("7050,1000"));
@@ -687,7 +695,7 @@
   ASSERT_EQ("application/octet-stream", mime);
   ASSERT_EQ("Some unknown tag", content);
 
-  f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_ConvertBinaryToNull), 0);
+  f.DatasetToJson(v, DicomToJsonFormat_Short, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludeUnknownTags | DicomToJsonFlags_IncludePrivateTags | DicomToJsonFlags_IncludeBinary | DicomToJsonFlags_ConvertBinaryToNull), 0);
   ASSERT_EQ(Json::objectValue, v.type());
   ASSERT_EQ(8u, v.getMemberNames().size());
   ASSERT_TRUE(v.isMember("7050,1000"));
@@ -702,7 +710,7 @@
 TEST(ParsedDicomFile, ToJsonFlags2)
 {
   ParsedDicomFile f(true);
-  f.Insert(DICOM_TAG_PIXEL_DATA, "Pixels", false);
+  f.Insert(DICOM_TAG_PIXEL_DATA, "Pixels", false, "");
 
   Json::Value v;
   f.DatasetToJson(v, DicomToJsonFormat_Short, DicomToJsonFlags_None, 0);
@@ -810,8 +818,8 @@
 
 
   {
-    std::auto_ptr<ParsedDicomFile> dicom
-      (ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_GenerateIdentifiers)));
+    std::unique_ptr<ParsedDicomFile> dicom
+      (ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_GenerateIdentifiers), ""));
 
     Json::Value vv;
     dicom->DatasetToJson(vv, DicomToJsonFormat_Human, toJsonFlags, 0);
@@ -826,8 +834,8 @@
 
 
   {
-    std::auto_ptr<ParsedDicomFile> dicom
-      (ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_GenerateIdentifiers)));
+    std::unique_ptr<ParsedDicomFile> dicom
+      (ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_GenerateIdentifiers), ""));
 
     Json::Value vv;
     dicom->DatasetToJson(vv, DicomToJsonFormat_Human, static_cast<DicomToJsonFlags>(DicomToJsonFlags_IncludePixelData), 0);
@@ -840,8 +848,8 @@
 
 
   {
-    std::auto_ptr<ParsedDicomFile> dicom
-      (ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_DecodeDataUriScheme)));
+    std::unique_ptr<ParsedDicomFile> dicom
+      (ParsedDicomFile::CreateFromJson(v, static_cast<DicomFromJsonFlags>(DicomFromJsonFlags_DecodeDataUriScheme), ""));
 
     Json::Value vv;
     dicom->DatasetToJson(vv, DicomToJsonFormat_Short, toJsonFlags, 0);
@@ -909,7 +917,7 @@
     Orthanc::SystemToolbox::ReadFile(s, PATH);
     Orthanc::ParsedDicomFile f(s);
     
-    std::auto_ptr<Orthanc::ImageAccessor> decoded(Orthanc::DicomImageDecoder::Decode(f, 0));
+    std::unique_ptr<Orthanc::ImageAccessor> decoded(Orthanc::DicomImageDecoder::Decode(f, 0));
     ASSERT_EQ(256u, decoded->GetWidth());
     ASSERT_EQ(256u, decoded->GetHeight());
     ASSERT_EQ(Orthanc::PixelFormat_Grayscale8, decoded->GetFormat());
@@ -971,7 +979,7 @@
     Orthanc::SystemToolbox::ReadFile(s, PATH);
     Orthanc::ParsedDicomFile f(s);
     
-    std::auto_ptr<Orthanc::ImageAccessor> decoded(Orthanc::DicomImageDecoder::Decode(f, 0));
+    std::unique_ptr<Orthanc::ImageAccessor> decoded(Orthanc::DicomImageDecoder::Decode(f, 0));
     ASSERT_EQ(384u, decoded->GetWidth());
     ASSERT_EQ(256u, decoded->GetHeight());
     ASSERT_EQ(Orthanc::PixelFormat_RGB24, decoded->GetFormat());
@@ -1028,7 +1036,7 @@
     Orthanc::SystemToolbox::ReadFile(s, PATH);
     Orthanc::ParsedDicomFile f(s);
     
-    std::auto_ptr<Orthanc::ImageAccessor> decoded(Orthanc::DicomImageDecoder::Decode(f, 0));
+    std::unique_ptr<Orthanc::ImageAccessor> decoded(Orthanc::DicomImageDecoder::Decode(f, 0));
     ASSERT_EQ(256u, decoded->GetWidth());
     ASSERT_EQ(256u, decoded->GetHeight());
     ASSERT_EQ(Orthanc::PixelFormat_Grayscale16, decoded->GetFormat());
@@ -1084,7 +1092,7 @@
     Orthanc::SystemToolbox::ReadFile(s, PATH);
     Orthanc::ParsedDicomFile f(s);
     
-    std::auto_ptr<Orthanc::ImageAccessor> decoded(Orthanc::DicomImageDecoder::Decode(f, 0));
+    std::unique_ptr<Orthanc::ImageAccessor> decoded(Orthanc::DicomImageDecoder::Decode(f, 0));
     ASSERT_EQ(256u, decoded->GetWidth());
     ASSERT_EQ(256u, decoded->GetHeight());
     ASSERT_EQ(Orthanc::PixelFormat_SignedGrayscale16, decoded->GetFormat());
@@ -1904,3 +1912,355 @@
   ASSERT_TRUE(lines[3].empty());
 }
 
+
+
+
+#if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
+
+#include "../Core/DicomParsing/Internals/DicomFrameIndex.h"
+
+#include <dcmtk/dcmdata/dcostrmb.h>
+#include <dcmtk/dcmdata/dcpixel.h>
+#include <dcmtk/dcmdata/dcpxitem.h>
+
+
+namespace Orthanc
+{
+  class IDicomTranscoder : public boost::noncopyable
+  {
+  public:
+    virtual ~IDicomTranscoder()
+    {
+    }
+
+    virtual DicomTransferSyntax GetTransferSyntax() = 0;
+
+    virtual std::string GetSopClassUid() = 0;
+
+    virtual std::string GetSopInstanceUid() = 0;
+
+    virtual unsigned int GetFramesCount() = 0;
+
+    virtual ImageAccessor* DecodeFrame(unsigned int frame) = 0;
+
+    virtual void GetCompressedFrame(std::string& target,
+                                    unsigned int frame) = 0;
+
+    virtual IDicomTranscoder* Transcode(std::set<DicomTransferSyntax> syntaxes,
+                                        bool allowNewSopInstanceUid) = 0;
+  };
+
+
+  class DcmtkTranscoder : public IDicomTranscoder
+  {
+  private:
+    std::unique_ptr<DcmFileFormat>    dicom_;
+    std::unique_ptr<DicomFrameIndex>  index_;
+    DicomTransferSyntax               transferSyntax_;
+    std::string                       sopClassUid_;
+    std::string                       sopInstanceUid_;
+
+    void Setup(DcmFileFormat* dicom)
+    {
+      dicom_.reset(dicom);
+      
+      if (dicom == NULL ||
+          dicom_->getDataset() == NULL)
+      {
+        throw OrthancException(ErrorCode_NullPointer);
+      }
+
+      DcmDataset& dataset = *dicom_->getDataset();
+      index_.reset(new DicomFrameIndex(dataset));
+
+      E_TransferSyntax xfer = dataset.getOriginalXfer();
+      if (xfer == EXS_Unknown)
+      {
+        dataset.updateOriginalXfer();
+        xfer = dataset.getOriginalXfer();
+        if (xfer == EXS_Unknown)
+        {
+          throw OrthancException(ErrorCode_BadFileFormat,
+                                 "Cannot determine the transfer syntax of the DICOM instance");
+        }
+      }
+
+      if (!FromDcmtkBridge::LookupOrthancTransferSyntax(transferSyntax_, xfer))
+      {
+        throw OrthancException(
+          ErrorCode_BadFileFormat,
+          "Unsupported transfer syntax: " + boost::lexical_cast<std::string>(xfer));
+      }
+
+      const char* a = NULL;
+      const char* b = NULL;
+
+      if (!dataset.findAndGetString(DCM_SOPClassUID, a).good() ||
+          !dataset.findAndGetString(DCM_SOPInstanceUID, b).good() ||
+          a == NULL ||
+          b == NULL)
+      {
+        throw OrthancException(ErrorCode_BadFileFormat,
+                               "Missing SOP class/instance UID in DICOM instance");
+      }
+
+      sopClassUid_.assign(a);
+      sopInstanceUid_.assign(b);
+    }
+    
+  public:
+    DcmtkTranscoder(DcmFileFormat* dicom)  // Takes ownership
+    {
+      Setup(dicom);
+    }
+
+    DcmtkTranscoder(const void* dicom,
+                    size_t size)
+    {
+      Setup(FromDcmtkBridge::LoadFromMemoryBuffer(dicom, size));
+    }
+
+    virtual DicomTransferSyntax GetTransferSyntax() ORTHANC_OVERRIDE
+    {
+      return transferSyntax_;
+    }
+
+    virtual std::string GetSopClassUid() ORTHANC_OVERRIDE
+    {
+      return sopClassUid_;
+    }
+    
+    virtual std::string GetSopInstanceUid() ORTHANC_OVERRIDE
+    {
+      return sopInstanceUid_;
+    }
+
+    virtual unsigned int GetFramesCount() ORTHANC_OVERRIDE
+    {
+      return index_->GetFramesCount();
+    }
+
+    virtual ImageAccessor* DecodeFrame(unsigned int frame) ORTHANC_OVERRIDE
+    {
+      assert(dicom_->getDataset() != NULL);
+      return DicomImageDecoder::Decode(*dicom_->getDataset(), frame);
+    }
+
+    virtual void GetCompressedFrame(std::string& target,
+                                    unsigned int frame) ORTHANC_OVERRIDE
+    {
+#if 1
+      index_->GetRawFrame(target, frame);
+      printf("%d: %d\n", frame, target.size());
+#endif
+
+#if 1
+      assert(dicom_->getDataset() != NULL);
+      DcmDataset& dataset = *dicom_->getDataset();
+      
+      DcmPixelSequence* pixelSequence = FromDcmtkBridge::GetPixelSequence(dataset);
+
+      if (pixelSequence != NULL &&
+          frame == 0 &&
+          pixelSequence->card() != GetFramesCount() + 1)
+      {
+        printf("COMPRESSED\n");
+        
+        // Check out "djcodecd.cc"
+        
+        printf("%d fragments\n", pixelSequence->card());
+        
+        // Skip the first fragment, that is the offset table
+        for (unsigned long i = 1; ;i++)
+        {
+          DcmPixelItem *fragment = NULL;
+          if (pixelSequence->getItem(fragment, i).good())
+          {
+            printf("fragment %d %d\n", i, fragment->getLength());
+          }
+          else
+          {
+            break;
+          }
+        }
+      }
+#endif
+    }
+
+    virtual IDicomTranscoder* Transcode(std::set<DicomTransferSyntax> syntaxes,
+                                        bool allowNewSopInstanceUid) ORTHANC_OVERRIDE
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+  };
+}
+
+
+
+
+static bool Transcode(std::string& buffer,
+                      DcmDataset& dataSet,
+                      E_TransferSyntax xfer)
+{
+  // Determine the transfer syntax which shall be used to write the
+  // information to the file. We always switch to the Little Endian
+  // syntax, with explicit length.
+
+  // http://support.dcmtk.org/docs/dcxfer_8h-source.html
+
+
+  /**
+   * Note that up to Orthanc 0.7.1 (inclusive), the
+   * "EXS_LittleEndianExplicit" was always used to save the DICOM
+   * dataset into memory. We now keep the original transfer syntax
+   * (if available).
+   **/
+  //E_TransferSyntax xfer = dataSet.getOriginalXfer();
+  if (xfer == EXS_Unknown)
+  {
+    // No information about the original transfer syntax: This is
+    // most probably a DICOM dataset that was read from memory.
+    xfer = EXS_LittleEndianExplicit;
+  }
+
+  E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength;
+
+  // Create the meta-header information
+  DcmFileFormat ff(&dataSet);
+  ff.validateMetaInfo(xfer);
+  ff.removeInvalidGroups();
+
+  // Create a memory buffer with the proper size
+  {
+    const uint32_t estimatedSize = ff.calcElementLength(xfer, encodingType);  // (*)
+    buffer.resize(estimatedSize);
+  }
+
+  DcmOutputBufferStream ob(&buffer[0], buffer.size());
+
+  // Fill the memory buffer with the meta-header and the dataset
+  ff.transferInit();
+  OFCondition c = ff.write(ob, xfer, encodingType, NULL,
+                           /*opt_groupLength*/ EGL_recalcGL,
+                           /*opt_paddingType*/ EPD_withoutPadding);
+  ff.transferEnd();
+
+  if (c.good())
+  {
+    // The DICOM file is successfully written, truncate the target
+    // buffer if its size was overestimated by (*)
+    ob.flush();
+
+    size_t effectiveSize = static_cast<size_t>(ob.tell());
+    if (effectiveSize < buffer.size())
+    {
+      buffer.resize(effectiveSize);
+    }
+
+    return true;
+  }
+  else
+  {
+    // Error
+    buffer.clear();
+    return false;
+  }
+}
+
+#include "dcmtk/dcmjpeg/djrploss.h"  /* for DJ_RPLossy */
+#include "dcmtk/dcmjpeg/djrplol.h"   /* for DJ_RPLossless */
+
+#include <boost/filesystem.hpp>
+
+
+static void TestFile(const std::string& path)
+{
+  printf("** %s\n", path.c_str());
+
+  std::string s;
+  SystemToolbox::ReadFile(s, path);
+
+  Orthanc::DcmtkTranscoder transcoder(s.c_str(), s.size());
+
+  printf("[%s] [%s] [%s] %d\n", GetTransferSyntaxUid(transcoder.GetTransferSyntax()),
+         transcoder.GetSopClassUid().c_str(), transcoder.GetSopInstanceUid().c_str(),
+         transcoder.GetFramesCount());
+
+  for (size_t i = 0; i < transcoder.GetFramesCount(); i++)
+  {
+    std::string f;
+    transcoder.GetCompressedFrame(f, i);
+
+    if (i == 0)
+    {
+      static unsigned int i = 0;
+      char buf[1024];
+      sprintf(buf, "/tmp/frame-%06d.dcm", i++);
+      printf(">> %s\n", buf);
+      Orthanc::SystemToolbox::WriteFile(f, buf);
+    }
+  }
+
+  printf("\n");
+}
+
+TEST(Toto, Transcode)
+{
+  if (0)
+  {
+    OFLog::configure(OFLogger::DEBUG_LOG_LEVEL);
+
+    std::string s;
+    //SystemToolbox::ReadFile(s, "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes/1.2.840.10008.1.2.4.50.dcm");
+    //SystemToolbox::ReadFile(s, "/home/jodogne/DICOM/Alain.dcm");
+    SystemToolbox::ReadFile(s, "/home/jodogne/Subversion/orthanc-tests/Database/Brainix/Epi/IM-0001-0002.dcm");
+
+    std::unique_ptr<DcmFileFormat> dicom(FromDcmtkBridge::LoadFromMemoryBuffer(s.c_str(), s.size()));
+
+    // less /home/jodogne/Downloads/dcmtk-3.6.4/dcmdata/include/dcmtk/dcmdata/dcxfer.h
+    printf(">> %d\n", dicom->getDataset()->getOriginalXfer());  // => 4 == EXS_JPEGProcess1
+
+    const DcmRepresentationParameter *p;
+
+#if 0
+    E_TransferSyntax target = EXS_LittleEndianExplicit;
+    p = NULL;
+#elif 1
+    E_TransferSyntax target = EXS_JPEGProcess14SV1;  
+    DJ_RPLossless rp_lossless(6, 0);
+    p = &rp_lossless;
+#else
+    E_TransferSyntax target = EXS_JPEGProcess1;
+    DJ_RPLossy rp_lossy(90);  // quality
+    p = &rp_lossy;
+#endif 
+  
+    ASSERT_TRUE(dicom->getDataset()->chooseRepresentation(target, p).good());
+    ASSERT_TRUE(dicom->getDataset()->canWriteXfer(target));
+
+    std::string t;
+    ASSERT_TRUE(Transcode(t, *dicom->getDataset(), target));
+
+    SystemToolbox::WriteFile(s, "source.dcm");
+    SystemToolbox::WriteFile(t, "target.dcm");
+  }
+
+  if (1)
+  {
+    const char* const PATH = "/home/jodogne/Subversion/orthanc-tests/Database/TransferSyntaxes";
+    
+    for (boost::filesystem::directory_iterator it(PATH);
+         it != boost::filesystem::directory_iterator(); ++it)
+    {
+      if (boost::filesystem::is_regular_file(it->status()))
+      {
+        TestFile(it->path().string());
+      }
+    }
+
+    TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Multiframe.dcm");
+    TestFile("/home/jodogne/Subversion/orthanc-tests/Database/Issue44/Monochrome1-Jpeg.dcm");
+  }
+}
+
+#endif
--- a/UnitTestsSources/ImageProcessingTests.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/ImageProcessingTests.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -34,6 +34,7 @@
 #include "PrecompiledHeadersUnitTests.h"
 #include "gtest/gtest.h"
 
+#include "../Core/Compatibility.h"
 #include "../Core/DicomFormat/DicomImageInformation.h"
 #include "../Core/Images/Image.h"
 #include "../Core/Images/ImageProcessing.h"
@@ -92,7 +93,7 @@
   class TestImageTraits : public ::testing::Test
   {
   private:
-    std::auto_ptr<Image>  image_;
+    std::unique_ptr<Image>  image_;
 
   protected:
     virtual void SetUp() ORTHANC_OVERRIDE
@@ -282,6 +283,44 @@
   return p == value;
 }
 
+static void SetGrayscale16Pixel(ImageAccessor& image,
+                                unsigned int x,
+                                unsigned int y,
+                                uint16_t value)
+{
+  ImageTraits<PixelFormat_Grayscale16>::SetPixel(image, value, x, y);
+}
+
+static bool TestGrayscale16Pixel(const ImageAccessor& image,
+                                 unsigned int x,
+                                 unsigned int y,
+                                 uint16_t value)
+{
+  PixelTraits<PixelFormat_Grayscale16>::PixelType p;
+  ImageTraits<PixelFormat_Grayscale16>::GetPixel(p, image, x, y);
+  if (p != value) printf("%d %d\n", p, value);
+  return p == value;
+}
+
+static void SetSignedGrayscale16Pixel(ImageAccessor& image,
+                                unsigned int x,
+                                unsigned int y,
+                                int16_t value)
+{
+  ImageTraits<PixelFormat_SignedGrayscale16>::SetPixel(image, value, x, y);
+}
+
+static bool TestSignedGrayscale16Pixel(const ImageAccessor& image,
+                                       unsigned int x,
+                                       unsigned int y,
+                                       int16_t value)
+{
+  PixelTraits<PixelFormat_SignedGrayscale16>::PixelType p;
+  ImageTraits<PixelFormat_SignedGrayscale16>::GetPixel(p, image, x, y);
+  if (p != value) printf("%d %d\n", p, value);
+  return p == value;
+}
+
 static void SetRGB24Pixel(ImageAccessor& image,
                           unsigned int x,
                           unsigned int y,
@@ -509,7 +548,7 @@
     SetGrayscale8Pixel(dirac, 4, 0, 100);
 
     {
-      std::auto_ptr<ImageAccessor> image(Image::Clone(dirac));
+      std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
       ImageProcessing::SeparableConvolution(*image, k1, 2, k2, 0);
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 0, 0));
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 1, 0, 0));
@@ -523,7 +562,7 @@
     }
 
     {
-      std::auto_ptr<ImageAccessor> image(Image::Clone(dirac));
+      std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
       ImageProcessing::SeparableConvolution(*image, k2, 0, k1, 2);
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 0, 0));
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 1, 0, 0));
@@ -537,7 +576,7 @@
     }
 
     {
-      std::auto_ptr<ImageAccessor> image(Image::Clone(dirac));
+      std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
       ImageProcessing::SeparableConvolution(*image, k2, 0, k2, 0);
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 0, 0));
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 1, 0, 0));
@@ -557,7 +596,7 @@
     SetGrayscale8Pixel(dirac, 0, 4, 100);
 
     {
-      std::auto_ptr<ImageAccessor> image(Image::Clone(dirac));
+      std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
       ImageProcessing::SeparableConvolution(*image, k2, 0, k1, 2);
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 0, 0));
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 1, 0));
@@ -571,7 +610,7 @@
     }
 
     {
-      std::auto_ptr<ImageAccessor> image(Image::Clone(dirac));
+      std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
       ImageProcessing::SeparableConvolution(*image, k1, 2, k2, 0);
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 0, 0));
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 1, 0));
@@ -585,7 +624,7 @@
     }
 
     {
-      std::auto_ptr<ImageAccessor> image(Image::Clone(dirac));
+      std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
       ImageProcessing::SeparableConvolution(*image, k2, 0, k2, 0);
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 0, 0));
       ASSERT_TRUE(TestGrayscale8Pixel(*image, 0, 1, 0));
@@ -605,7 +644,7 @@
     SetRGB24Pixel(dirac, 4, 0, 100, 120, 140);
 
     {
-      std::auto_ptr<ImageAccessor> image(Image::Clone(dirac));
+      std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
       ImageProcessing::SeparableConvolution(*image, k1, 2, k2, 0);
       ASSERT_TRUE(TestRGB24Pixel(*image, 0, 0, 0, 0, 0));
       ASSERT_TRUE(TestRGB24Pixel(*image, 1, 0, 0, 0, 0));
@@ -619,7 +658,7 @@
     }
 
     {
-      std::auto_ptr<ImageAccessor> image(Image::Clone(dirac));
+      std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
       ImageProcessing::SeparableConvolution(*image, k2, 0, k1, 2);
       ASSERT_TRUE(TestRGB24Pixel(*image, 0, 0, 0, 0, 0));
       ASSERT_TRUE(TestRGB24Pixel(*image, 1, 0, 0, 0, 0));
@@ -633,7 +672,7 @@
     }
 
     {
-      std::auto_ptr<ImageAccessor> image(Image::Clone(dirac));
+      std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
       ImageProcessing::SeparableConvolution(*image, k2, 0, k2, 0);
       ASSERT_TRUE(TestRGB24Pixel(*image, 0, 0, 0, 0, 0));
       ASSERT_TRUE(TestRGB24Pixel(*image, 1, 0, 0, 0, 0));
@@ -653,7 +692,7 @@
     SetRGB24Pixel(dirac, 0, 4, 100, 120, 140);
 
     {
-      std::auto_ptr<ImageAccessor> image(Image::Clone(dirac));
+      std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
       ImageProcessing::SeparableConvolution(*image, k2, 0, k1, 2);
       ASSERT_TRUE(TestRGB24Pixel(*image, 0, 0, 0, 0, 0));
       ASSERT_TRUE(TestRGB24Pixel(*image, 0, 1, 0, 0, 0));
@@ -667,7 +706,7 @@
     }
 
     {
-      std::auto_ptr<ImageAccessor> image(Image::Clone(dirac));
+      std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
       ImageProcessing::SeparableConvolution(*image, k1, 2, k2, 0);
       ASSERT_TRUE(TestRGB24Pixel(*image, 0, 0, 0, 0, 0));
       ASSERT_TRUE(TestRGB24Pixel(*image, 0, 1, 0, 0, 0));
@@ -681,7 +720,7 @@
     }
 
     {
-      std::auto_ptr<ImageAccessor> image(Image::Clone(dirac));
+      std::unique_ptr<ImageAccessor> image(Image::Clone(dirac));
       ImageProcessing::SeparableConvolution(*image, k2, 0, k2, 0);
       ASSERT_TRUE(TestRGB24Pixel(*image, 0, 0, 0, 0, 0));
       ASSERT_TRUE(TestRGB24Pixel(*image, 0, 1, 0, 0, 0));
@@ -774,3 +813,202 @@
     ASSERT_TRUE(TestRGB24Pixel(image, 4, 4, 0, 0, 0));
   }
 }
+
+TEST(ImageProcessing, ApplyWindowingFloatToGrayScale8)
+{
+  {
+    Image image(PixelFormat_Float32, 6, 1, false);
+    ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, -5.0f, 0, 0);
+    ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 0.0f, 1, 0);
+    ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 5.0f, 2, 0);
+    ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 10.0f, 3, 0);
+    ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 1000.0f, 4, 0);
+    ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 2.0f, 5, 0);
+
+    {
+      Image target(PixelFormat_Grayscale8, 6, 1, false);
+      ImageProcessing::ApplyWindowing_Deprecated(target, image, 5.0f, 10.0f, 1.0f, 0.0f, false);
+
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 0, 0, 0));
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 1, 0, 0));
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 2, 0, 128));
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 3, 0, 255));
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 4, 0, 255));
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 5, 0, 255*2/10));
+    }
+
+    {
+      Image target(PixelFormat_Grayscale8, 6, 1, false);
+      ImageProcessing::ApplyWindowing_Deprecated(target, image, 5.0f, 10.0f, 1.0f, 0.0f, true);
+
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 0, 0, 255));
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 1, 0, 255));
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 2, 0, 127));
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 3, 0, 0));
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 4, 0, 0));
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 5, 0, 255 - 255*2/10));
+    }
+
+    {
+      Image target(PixelFormat_Grayscale8, 6, 1, false);
+      ImageProcessing::ApplyWindowing_Deprecated(target, image, 5000.0f, 10000.01f, 1000.0f, 0.0f, false);
+
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 0, 0, 0));
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 1, 0, 0));
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 2, 0, 128));
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 3, 0, 255));
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 4, 0, 255));
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 5, 0, 255*2/10));
+    }
+
+    {
+      Image target(PixelFormat_Grayscale8, 6, 1, false);
+      ImageProcessing::ApplyWindowing_Deprecated(target, image, 5000.0f, 10000.01f, 1000.0f, 0.0f, true);
+
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 0, 0, 255));
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 1, 0, 255));
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 2, 0, 127));
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 3, 0, 0));
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 4, 0, 0));
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 5, 0, 255 - 256*2/10));
+    }
+
+    {
+      Image target(PixelFormat_Grayscale8, 6, 1, false);
+      ImageProcessing::ApplyWindowing_Deprecated(target, image, 50.0f, 100.1f, 10.0f, 30.0f, false);
+
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 0, 0, 0));  // (-5 * 10) + 30 => pixel value = -20 => 0
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 1, 0, 256*30/100));  // ((0 * 10) + 30 => pixel value = 30 => 30%
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 2, 0, 256*80/100)); // ((5 * 10) + 30 => pixel value = 80 => 80%
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 3, 0, 255)); // ((10 * 10) + 30 => pixel value = 130 => 100%
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 4, 0, 255)); // ((1000 * 10) + 30 => pixel value = 10030 => 100%
+      ASSERT_TRUE(TestGrayscale8Pixel(target, 5, 0, 128)); // ((2 * 10) + 30 => pixel value = 50 => 50%
+    }
+
+  }
+}
+
+TEST(ImageProcessing, ApplyWindowingFloatToGrayScale16)
+{
+  {
+    Image image(PixelFormat_Float32, 6, 1, false);
+    ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, -5.0f, 0, 0);
+    ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 0.0f, 1, 0);
+    ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 5.0f, 2, 0);
+    ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 10.0f, 3, 0);
+    ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 1000.0f, 4, 0);
+    ImageTraits<PixelFormat_Float32>::SetFloatPixel(image, 2.0f, 5, 0);
+
+    {
+      Image target(PixelFormat_Grayscale16, 6, 1, false);
+      ImageProcessing::ApplyWindowing_Deprecated(target, image, 5.0f, 10.0f, 1.0f, 0.0f, false);
+
+      ASSERT_TRUE(TestGrayscale16Pixel(target, 0, 0, 0));
+      ASSERT_TRUE(TestGrayscale16Pixel(target, 1, 0, 0));
+      ASSERT_TRUE(TestGrayscale16Pixel(target, 2, 0, 32768));
+      ASSERT_TRUE(TestGrayscale16Pixel(target, 3, 0, 65535));
+      ASSERT_TRUE(TestGrayscale16Pixel(target, 4, 0, 65535));
+      ASSERT_TRUE(TestGrayscale16Pixel(target, 5, 0, 65536*2/10));
+    }
+  }
+}
+
+TEST(ImageProcessing, ApplyWindowingGrayScale8ToGrayScale16)
+{
+  {
+    Image image(PixelFormat_Grayscale8, 5, 1, false);
+    SetGrayscale8Pixel(image, 0, 0, 0);
+    SetGrayscale8Pixel(image, 1, 0, 2);
+    SetGrayscale8Pixel(image, 2, 0, 5);
+    SetGrayscale8Pixel(image, 3, 0, 10);
+    SetGrayscale8Pixel(image, 4, 0, 255);
+
+    {
+      Image target(PixelFormat_Grayscale16, 5, 1, false);
+      ImageProcessing::ApplyWindowing_Deprecated(target, image, 5.0f, 10.0f, 1.0f, 0.0f, false);
+
+      ASSERT_TRUE(TestGrayscale16Pixel(target, 0, 0, 0));
+      ASSERT_TRUE(TestGrayscale16Pixel(target, 1, 0, 65536*2/10));
+      ASSERT_TRUE(TestGrayscale16Pixel(target, 2, 0, 65536*5/10));
+      ASSERT_TRUE(TestGrayscale16Pixel(target, 3, 0, 65535));
+      ASSERT_TRUE(TestGrayscale16Pixel(target, 4, 0, 65535));
+    }
+  }
+}
+
+TEST(ImageProcessing, ApplyWindowingGrayScale16ToGrayScale16)
+{
+  {
+    Image image(PixelFormat_Grayscale16, 5, 1, false);
+    SetGrayscale16Pixel(image, 0, 0, 0);
+    SetGrayscale16Pixel(image, 1, 0, 2);
+    SetGrayscale16Pixel(image, 2, 0, 5);
+    SetGrayscale16Pixel(image, 3, 0, 10);
+    SetGrayscale16Pixel(image, 4, 0, 255);
+
+    {
+      Image target(PixelFormat_Grayscale16, 5, 1, false);
+      ImageProcessing::ApplyWindowing_Deprecated(target, image, 5.0f, 10.0f, 1.0f, 0.0f, false);
+
+      ASSERT_TRUE(TestGrayscale16Pixel(target, 0, 0, 0));
+      ASSERT_TRUE(TestGrayscale16Pixel(target, 1, 0, 65536*2/10));
+      ASSERT_TRUE(TestGrayscale16Pixel(target, 2, 0, 65536*5/10));
+      ASSERT_TRUE(TestGrayscale16Pixel(target, 3, 0, 65535));
+      ASSERT_TRUE(TestGrayscale16Pixel(target, 4, 0, 65535));
+    }
+  }
+}
+
+
+TEST(ImageProcessing, ShiftScaleGrayscale8)
+{
+  Image image(PixelFormat_Grayscale8, 5, 1, false);
+  SetGrayscale8Pixel(image, 0, 0, 0);
+  SetGrayscale8Pixel(image, 1, 0, 2);
+  SetGrayscale8Pixel(image, 2, 0, 5);
+  SetGrayscale8Pixel(image, 3, 0, 10);
+  SetGrayscale8Pixel(image, 4, 0, 255);
+
+  ImageProcessing::ShiftScale(image, -1.1, 1.5, true);
+  ASSERT_TRUE(TestGrayscale8Pixel(image, 0, 0, 0));
+  ASSERT_TRUE(TestGrayscale8Pixel(image, 1, 0, 1));
+  ASSERT_TRUE(TestGrayscale8Pixel(image, 2, 0, 6));
+  ASSERT_TRUE(TestGrayscale8Pixel(image, 3, 0, 13));
+  ASSERT_TRUE(TestGrayscale8Pixel(image, 4, 0, 255));
+}
+
+
+TEST(ImageProcessing, ShiftScaleGrayscale16)
+{
+  Image image(PixelFormat_Grayscale16, 5, 1, false);
+  SetGrayscale16Pixel(image, 0, 0, 0);
+  SetGrayscale16Pixel(image, 1, 0, 2);
+  SetGrayscale16Pixel(image, 2, 0, 5);
+  SetGrayscale16Pixel(image, 3, 0, 10);
+  SetGrayscale16Pixel(image, 4, 0, 255);
+
+  ImageProcessing::ShiftScale(image, -1.1, 1.5, true);
+  ASSERT_TRUE(TestGrayscale16Pixel(image, 0, 0, 0));
+  ASSERT_TRUE(TestGrayscale16Pixel(image, 1, 0, 1));
+  ASSERT_TRUE(TestGrayscale16Pixel(image, 2, 0, 6));
+  ASSERT_TRUE(TestGrayscale16Pixel(image, 3, 0, 13));
+  ASSERT_TRUE(TestGrayscale16Pixel(image, 4, 0, 381));
+}
+
+
+TEST(ImageProcessing, ShiftScaleSignedGrayscale16)
+{
+  Image image(PixelFormat_SignedGrayscale16, 5, 1, false);
+  SetSignedGrayscale16Pixel(image, 0, 0, 0);
+  SetSignedGrayscale16Pixel(image, 1, 0, 2);
+  SetSignedGrayscale16Pixel(image, 2, 0, 5);
+  SetSignedGrayscale16Pixel(image, 3, 0, 10);
+  SetSignedGrayscale16Pixel(image, 4, 0, 255);
+
+  ImageProcessing::ShiftScale(image, -17.1, 11.5, true);
+  ASSERT_TRUE(TestSignedGrayscale16Pixel(image, 0, 0, -197));
+  ASSERT_TRUE(TestSignedGrayscale16Pixel(image, 1, 0, -174));
+  ASSERT_TRUE(TestSignedGrayscale16Pixel(image, 2, 0, -139));
+  ASSERT_TRUE(TestSignedGrayscale16Pixel(image, 3, 0, -82));
+  ASSERT_TRUE(TestSignedGrayscale16Pixel(image, 4, 0, 2736));
+}
--- a/UnitTestsSources/ImageTests.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/ImageTests.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/JpegLosslessTests.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/JpegLosslessTests.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/LoggingTests.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/LoggingTests.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/LuaTests.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/LuaTests.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/MemoryCacheTests.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/MemoryCacheTests.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -40,9 +40,11 @@
 #include <boost/lexical_cast.hpp>
 
 #include "../Core/Cache/MemoryCache.h"
+#include "../Core/Cache/MemoryStringCache.h"
 #include "../Core/Cache/SharedArchive.h"
 #include "../Core/IDynamicObject.h"
 #include "../Core/Logging.h"
+#include "../OrthancServer/StorageCommitmentReports.h"
 
 
 TEST(LRU, Basic)
@@ -212,7 +214,7 @@
     }
   };
 
-  class IntegerProvider : public Orthanc::ICachePageProvider
+  class IntegerProvider : public Orthanc::Deprecated::ICachePageProvider
   {
   public:
     std::string log_;
@@ -231,7 +233,7 @@
   IntegerProvider provider;
 
   {
-    Orthanc::MemoryCache cache(provider, 3);
+    Orthanc::Deprecated::MemoryCache cache(provider, 3);
     cache.Access("42");  // 42 -> exit
     cache.Access("43");  // 43, 42 -> exit
     cache.Access("45");  // 45, 43, 42 -> exit
@@ -317,3 +319,142 @@
 
   ASSERT_EQ(2u, count);
 }
+
+
+TEST(MemoryStringCache, Basic)
+{
+  Orthanc::MemoryStringCache c;
+  ASSERT_THROW(c.SetMaximumSize(0), Orthanc::OrthancException);
+  
+  c.SetMaximumSize(2);
+
+  std::string v;
+  ASSERT_FALSE(c.Fetch(v, "hello"));
+
+  c.Add("hello", "a");
+  ASSERT_TRUE(c.Fetch(v, "hello"));   ASSERT_EQ("a", v);
+  ASSERT_FALSE(c.Fetch(v, "hello2"));
+  ASSERT_FALSE(c.Fetch(v, "hello3"));
+
+  c.Add("hello2", "b");
+  ASSERT_TRUE(c.Fetch(v, "hello"));   ASSERT_EQ("a", v);
+  ASSERT_TRUE(c.Fetch(v, "hello2"));  ASSERT_EQ("b", v);
+  ASSERT_FALSE(c.Fetch(v, "hello3"));
+
+  c.Add("hello3", "too large value");
+  ASSERT_TRUE(c.Fetch(v, "hello"));   ASSERT_EQ("a", v);
+  ASSERT_TRUE(c.Fetch(v, "hello2"));  ASSERT_EQ("b", v);
+  ASSERT_FALSE(c.Fetch(v, "hello3"));
+  
+  c.Add("hello3", "c");
+  ASSERT_FALSE(c.Fetch(v, "hello"));  // Recycled
+  ASSERT_TRUE(c.Fetch(v, "hello2"));  ASSERT_EQ("b", v);
+  ASSERT_TRUE(c.Fetch(v, "hello3"));  ASSERT_EQ("c", v);
+}
+
+
+TEST(MemoryStringCache, Invalidate)
+{
+  Orthanc::MemoryStringCache c;
+  c.Add("hello", "a");
+  c.Add("hello2", "b");
+
+  std::string v;
+  ASSERT_TRUE(c.Fetch(v, "hello"));   ASSERT_EQ("a", v);
+  ASSERT_TRUE(c.Fetch(v, "hello2"));  ASSERT_EQ("b", v);
+
+  c.Invalidate("hello");
+  ASSERT_FALSE(c.Fetch(v, "hello"));
+  ASSERT_TRUE(c.Fetch(v, "hello2"));  ASSERT_EQ("b", v);
+}
+
+
+TEST(StorageCommitmentReports, Basic)
+{
+  Orthanc::StorageCommitmentReports reports(2);
+  ASSERT_EQ(2u, reports.GetMaxSize());
+
+  {
+    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "nope");
+    ASSERT_EQ("nope", accessor.GetTransactionUid());
+    ASSERT_FALSE(accessor.IsValid());
+    ASSERT_THROW(accessor.GetReport(), Orthanc::OrthancException);
+  }
+
+  reports.Store("a", new Orthanc::StorageCommitmentReports::Report("aet_a"));
+  reports.Store("b", new Orthanc::StorageCommitmentReports::Report("aet_b"));
+  reports.Store("c", new Orthanc::StorageCommitmentReports::Report("aet_c"));
+
+  {
+    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "a");
+    ASSERT_FALSE(accessor.IsValid());
+  }
+
+  {
+    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "b");
+    ASSERT_TRUE(accessor.IsValid());
+    ASSERT_EQ("aet_b", accessor.GetReport().GetRemoteAet());
+    ASSERT_EQ(Orthanc::StorageCommitmentReports::Report::Status_Pending,
+              accessor.GetReport().GetStatus());
+  }
+
+  {
+    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "c");
+    ASSERT_EQ("aet_c", accessor.GetReport().GetRemoteAet());
+    ASSERT_TRUE(accessor.IsValid());
+  }
+
+  {
+    std::unique_ptr<Orthanc::StorageCommitmentReports::Report> report
+      (new Orthanc::StorageCommitmentReports::Report("aet"));
+    report->AddSuccess("class1", "instance1");
+    report->AddFailure("class2", "instance2",
+                       Orthanc::StorageCommitmentFailureReason_ReferencedSOPClassNotSupported);
+    report->MarkAsComplete();
+    reports.Store("a", report.release());
+  }
+
+  {
+    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "a");
+    ASSERT_TRUE(accessor.IsValid());
+    ASSERT_EQ("aet", accessor.GetReport().GetRemoteAet());
+    ASSERT_EQ(Orthanc::StorageCommitmentReports::Report::Status_Failure,
+              accessor.GetReport().GetStatus());
+  }
+
+  {
+    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "b");
+    ASSERT_FALSE(accessor.IsValid());
+  }
+
+  {
+    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "c");
+    ASSERT_TRUE(accessor.IsValid());
+  }
+
+  {
+    std::unique_ptr<Orthanc::StorageCommitmentReports::Report> report
+      (new Orthanc::StorageCommitmentReports::Report("aet"));
+    report->AddSuccess("class1", "instance1");
+    report->MarkAsComplete();
+    reports.Store("a", report.release());
+  }
+
+  {
+    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "a");
+    ASSERT_TRUE(accessor.IsValid());
+    ASSERT_EQ("aet", accessor.GetReport().GetRemoteAet());
+    ASSERT_EQ(Orthanc::StorageCommitmentReports::Report::Status_Success,
+              accessor.GetReport().GetStatus());
+  }
+
+  {
+    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "b");
+    ASSERT_FALSE(accessor.IsValid());
+  }
+
+  {
+    Orthanc::StorageCommitmentReports::Accessor accessor(reports, "c");
+    ASSERT_TRUE(accessor.IsValid());
+  }
+}
--- a/UnitTestsSources/MultiThreadingTests.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/MultiThreadingTests.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -34,6 +34,7 @@
 #include "PrecompiledHeadersUnitTests.h"
 #include "gtest/gtest.h"
 
+#include "../Core/Compatibility.h"
 #include "../Core/FileStorage/MemoryStorageArea.h"
 #include "../Core/JobsEngine/JobsEngine.h"
 #include "../Core/Logging.h"
@@ -101,7 +102,7 @@
     {
     }
     
-    virtual JobStepResult Step() ORTHANC_OVERRIDE
+    virtual JobStepResult Step(const std::string& jobId) ORTHANC_OVERRIDE
     {
       if (fails_)
       {
@@ -272,7 +273,7 @@
   q.Enqueue(new DynamicInteger(30, s));
   q.Enqueue(new DynamicInteger(40, s));
 
-  std::auto_ptr<DynamicInteger> i;
+  std::unique_ptr<DynamicInteger> i;
   i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(10, i->GetValue());
   i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(20, i->GetValue());
   i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(30, i->GetValue());
@@ -747,7 +748,7 @@
   SequenceOfOperationsJob* job = NULL;
 
   {
-    std::auto_ptr<SequenceOfOperationsJob> a(new SequenceOfOperationsJob);
+    std::unique_ptr<SequenceOfOperationsJob> a(new SequenceOfOperationsJob);
     job = a.get();
     engine.GetRegistry().Submit(id, a.release(), 0);
   }
@@ -837,7 +838,7 @@
   }
   else
   {
-    std::auto_ptr<IJob> unserialized(unserializer.UnserializeJob(a));
+    std::unique_ptr<IJob> unserialized(unserializer.UnserializeJob(a));
   
     Json::Value b = 43;
     if (unserialized->Serialize(b))
@@ -863,7 +864,7 @@
   }
   else
   {
-    std::auto_ptr<SetOfInstancesJob> unserialized
+    std::unique_ptr<SetOfInstancesJob> unserialized
       (dynamic_cast<SetOfInstancesJob*>(unserializer.UnserializeJob(a)));
   
     Json::Value b = 43;
@@ -889,7 +890,7 @@
   Json::Value a = 42;
   operation.Serialize(a);
   
-  std::auto_ptr<IJobOperation> unserialized(unserializer.UnserializeOperation(a));
+  std::unique_ptr<IJobOperation> unserialized(unserializer.UnserializeOperation(a));
   
   Json::Value b = 43;
   unserialized->Serialize(b);
@@ -904,7 +905,7 @@
   Json::Value a = 42;
   value.Serialize(a);
   
-  std::auto_ptr<JobOperationValue> unserialized(unserializer.UnserializeValue(a));
+  std::unique_ptr<JobOperationValue> unserialized(unserializer.UnserializeValue(a));
   
   Json::Value b = 43;
   unserialized->Serialize(b);
@@ -957,7 +958,7 @@
 
   {
     GenericJobUnserializer unserializer;
-    std::auto_ptr<JobOperationValues> values(JobOperationValues::Unserialize(unserializer, s));
+    std::unique_ptr<JobOperationValues> values(JobOperationValues::Unserialize(unserializer, s));
     ASSERT_EQ(3u, values->GetSize());
     ASSERT_EQ(JobOperationValue::Type_Null, values->GetValue(0).GetType());
     ASSERT_EQ(JobOperationValue::Type_String, values->GetValue(1).GetType());
@@ -984,7 +985,7 @@
   ASSERT_THROW(unserializer.UnserializeJob(s), OrthancException);
   ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException);
 
-  std::auto_ptr<JobOperationValue> value;
+  std::unique_ptr<JobOperationValue> value;
   value.reset(unserializer.UnserializeValue(s));
   
   ASSERT_EQ(JobOperationValue::Type_Null, value->GetType());
@@ -1021,7 +1022,7 @@
   ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException);
 
   {
-    std::auto_ptr<IJobOperation> operation;
+    std::unique_ptr<IJobOperation> operation;
     operation.reset(unserializer.UnserializeOperation(s));
 
     // Make sure that we have indeed unserialized a log operation
@@ -1045,12 +1046,12 @@
     job.AddInstance("nope");
     job.AddInstance("world");
     job.SetPermissive(true);
-    ASSERT_THROW(job.Step(), OrthancException);  // Not started yet
+    ASSERT_THROW(job.Step("jobId"), OrthancException);  // Not started yet
     ASSERT_FALSE(job.HasTrailingStep());
     ASSERT_FALSE(job.IsTrailingStepDone());
     job.Start();
-    ASSERT_EQ(JobStepCode_Continue, job.Step().GetCode());
-    ASSERT_EQ(JobStepCode_Continue, job.Step().GetCode());
+    ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode());
+    ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode());
 
     {
       DummyUnserializer unserializer;
@@ -1065,7 +1066,7 @@
     ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException);
     ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException);
 
-    std::auto_ptr<IJob> job;
+    std::unique_ptr<IJob> job;
     job.reset(unserializer.UnserializeJob(s));
 
     const DummyInstancesJob& tmp = dynamic_cast<const DummyInstancesJob&>(*job);
@@ -1101,7 +1102,7 @@
       lock.SetTrailingOperationTimeout(300);
     }
 
-    ASSERT_EQ(JobStepCode_Continue, job.Step().GetCode());
+    ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode());
 
     {
       GenericJobUnserializer unserializer;
@@ -1116,7 +1117,7 @@
     ASSERT_THROW(unserializer.UnserializeValue(s), OrthancException);
     ASSERT_THROW(unserializer.UnserializeOperation(s), OrthancException);
 
-    std::auto_ptr<IJob> job;
+    std::unique_ptr<IJob> job;
     job.reset(unserializer.UnserializeJob(s));
 
     std::string tmp;
@@ -1143,11 +1144,11 @@
   Json::Value s;
 
   ParsedDicomFile source(true);
-  source.Insert(DICOM_TAG_STUDY_DESCRIPTION, "Test 1", false);
-  source.Insert(DICOM_TAG_SERIES_DESCRIPTION, "Test 2", false);
-  source.Insert(DICOM_TAG_PATIENT_NAME, "Test 3", false);
+  source.Insert(DICOM_TAG_STUDY_DESCRIPTION, "Test 1", false, "");
+  source.Insert(DICOM_TAG_SERIES_DESCRIPTION, "Test 2", false, "");
+  source.Insert(DICOM_TAG_PATIENT_NAME, "Test 3", false, "");
 
-  std::auto_ptr<ParsedDicomFile> modified(source.Clone(true));
+  std::unique_ptr<ParsedDicomFile> modified(source.Clone(true));
 
   {
     DicomModification modification;
@@ -1166,7 +1167,7 @@
     DicomModification modification(s);
     ASSERT_EQ(ResourceType_Series, modification.GetLevel());
     
-    std::auto_ptr<ParsedDicomFile> second(source.Clone(true));
+    std::unique_ptr<ParsedDicomFile> second(source.Clone(true));
     modification.Apply(*second);
 
     std::string s;
@@ -1282,7 +1283,7 @@
   private:
     MemoryStorageArea              storage_;
     SQLiteDatabaseWrapper          db_;   // The SQLite DB is in memory
-    std::auto_ptr<ServerContext>   context_;
+    std::unique_ptr<ServerContext>   context_;
     TimeoutDicomConnectionManager  manager_;
 
   public:
@@ -1310,7 +1311,7 @@
       // Create a sample DICOM file
       ParsedDicomFile dicom(true);
       dicom.Replace(DICOM_TAG_PATIENT_NAME, std::string("JODOGNE"),
-                    false, DicomReplaceMode_InsertIfAbsent);
+                    false, DicomReplaceMode_InsertIfAbsent, "");
 
       DicomInstanceToStore toStore;
       toStore.SetParsedDicomFile(dicom);
@@ -1336,7 +1337,7 @@
     instance.Serialize(s);
   }
 
-  std::auto_ptr<JobOperationValue> value;
+  std::unique_ptr<JobOperationValue> value;
   value.reset(unserializer.UnserializeValue(s));
   ASSERT_EQ(JobOperationValue::Type_DicomInstance, value->GetType());
   ASSERT_EQ(id, dynamic_cast<DicomInstanceOperationValue&>(*value).GetId());
@@ -1369,7 +1370,7 @@
     operation.Serialize(s);
   }
 
-  std::auto_ptr<IJobOperation> operation;
+  std::unique_ptr<IJobOperation> operation;
 
   {
     operation.reset(unserializer.UnserializeOperation(s));
@@ -1411,7 +1412,9 @@
     modality.SetHost("192.168.1.1");
     modality.SetPortNumber(1000);
     modality.SetManufacturer(ModalityManufacturer_StoreScp);
-    modality.SetPreferredTransferSyntax("1.2.840.10008.1.2");
+    ASSERT_EQ("1.2.840.10008.1.2", modality.GetPreferredTransferSyntax());
+    modality.SetPreferredTransferSyntax("1.2.840.10008.1.2.1");
+    ASSERT_EQ("1.2.840.10008.1.2.1", modality.GetPreferredTransferSyntax());
 
     StoreScuOperation operation("TEST", modality);
 
@@ -1457,7 +1460,7 @@
   // ModifyInstanceOperation
 
   {
-    std::auto_ptr<DicomModification> modification(new DicomModification);
+    std::unique_ptr<DicomModification> modification(new DicomModification);
     modification->SetupAnonymization(DicomVersion_2008);
     
     ModifyInstanceOperation operation(GetContext(), RequestOrigin_Lua, modification.release());
@@ -1497,7 +1500,9 @@
     modality.SetHost("192.168.1.1");
     modality.SetPortNumber(1000);
     modality.SetManufacturer(ModalityManufacturer_StoreScp);
-    modality.SetPreferredTransferSyntax("1.2.840.10008.1.2");
+    ASSERT_EQ("1.2.840.10008.1.2", modality.GetPreferredTransferSyntax());
+    modality.SetPreferredTransferSyntax("1.2.840.10008.1.2.1");
+    ASSERT_EQ("1.2.840.10008.1.2.1", modality.GetPreferredTransferSyntax());
 
     DicomModalityStoreJob job(GetContext());
     job.SetLocalAet("LOCAL");
@@ -1509,7 +1514,7 @@
   }
 
   {
-    std::auto_ptr<IJob> job;
+    std::unique_ptr<IJob> job;
     job.reset(unserializer.UnserializeJob(s));
 
     DicomModalityStoreJob& tmp = dynamic_cast<DicomModalityStoreJob&>(*job);
@@ -1539,7 +1544,7 @@
   }
 
   {
-    std::auto_ptr<IJob> job;
+    std::unique_ptr<IJob> job;
     job.reset(unserializer.UnserializeJob(s));
 
     OrthancPeerStoreJob& tmp = dynamic_cast<OrthancPeerStoreJob&>(*job);
@@ -1552,7 +1557,7 @@
   // ResourceModificationJob
 
   {
-    std::auto_ptr<DicomModification> modification(new DicomModification);
+    std::unique_ptr<DicomModification> modification(new DicomModification);
     modification->SetupAnonymization(DicomVersion_2008);    
 
     ResourceModificationJob job(GetContext());
@@ -1564,7 +1569,7 @@
   }
 
   {
-    std::auto_ptr<IJob> job;
+    std::unique_ptr<IJob> job;
     job.reset(unserializer.UnserializeJob(s));
 
     ResourceModificationJob& tmp = dynamic_cast<ResourceModificationJob&>(*job);
@@ -1620,8 +1625,8 @@
 
       job.AddTrailingStep();
       job.Start();
-      ASSERT_EQ(JobStepCode_Continue, job.Step().GetCode());
-      ASSERT_EQ(JobStepCode_Success, job.Step().GetCode());
+      ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode());
+      ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode());
 
       study2 = job.GetTargetStudy();
       ASSERT_FALSE(study2.empty());
@@ -1631,7 +1636,7 @@
     }
 
     {
-      std::auto_ptr<IJob> job;
+      std::unique_ptr<IJob> job;
       job.reset(unserializer.UnserializeJob(s));
 
       SplitStudyJob& tmp = dynamic_cast<SplitStudyJob&>(*job);
@@ -1679,8 +1684,8 @@
 
     job.AddTrailingStep();
     job.Start();
-    ASSERT_EQ(JobStepCode_Continue, job.Step().GetCode());
-    ASSERT_EQ(JobStepCode_Success, job.Step().GetCode());
+    ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode());
+    ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode());
 
     ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
     ASSERT_TRUE(job.Serialize(s));
@@ -1695,7 +1700,7 @@
   }
 
   {
-    std::auto_ptr<IJob> job;
+    std::unique_ptr<IJob> job;
     job.reset(unserializer.UnserializeJob(s));
 
     MergeStudyJob& tmp = dynamic_cast<MergeStudyJob&>(*job);
@@ -1748,7 +1753,7 @@
       ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
     }
     
-    ASSERT_EQ(JobStepCode_Success, job.Step().GetCode());
+    ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode());
     ASSERT_EQ(1u, job.GetPosition());
     ASSERT_FALSE(job.IsTrailingStepDone());
     
@@ -1757,7 +1762,7 @@
       ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
     }
 
-    ASSERT_THROW(job.Step(), OrthancException);
+    ASSERT_THROW(job.Step("jobId"), OrthancException);
   }
 
   {
@@ -1779,7 +1784,7 @@
       ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
     }
     
-    ASSERT_EQ(JobStepCode_Continue, job.Step().GetCode());
+    ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode());
     ASSERT_EQ(1u, job.GetPosition());
     ASSERT_FALSE(job.IsTrailingStepDone());
     
@@ -1788,7 +1793,7 @@
       ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
     }
 
-    ASSERT_EQ(JobStepCode_Success, job.Step().GetCode());
+    ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode());
     ASSERT_EQ(2u, job.GetPosition());
     ASSERT_FALSE(job.IsTrailingStepDone());
     
@@ -1797,7 +1802,7 @@
       ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
     }
 
-    ASSERT_THROW(job.Step(), OrthancException);
+    ASSERT_THROW(job.Step("jobId"), OrthancException);
   }
 
   {
@@ -1820,7 +1825,7 @@
       ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
     }
     
-    ASSERT_EQ(JobStepCode_Success, job.Step().GetCode());
+    ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode());
     ASSERT_EQ(1u, job.GetPosition());
     ASSERT_TRUE(job.IsTrailingStepDone());
     
@@ -1829,7 +1834,7 @@
       ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
     }
 
-    ASSERT_THROW(job.Step(), OrthancException);
+    ASSERT_THROW(job.Step("jobId"), OrthancException);
   }
 
   {
@@ -1854,7 +1859,7 @@
       ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
     }
     
-    ASSERT_EQ(JobStepCode_Continue, job.Step().GetCode());
+    ASSERT_EQ(JobStepCode_Continue, job.Step("jobId").GetCode());
     ASSERT_EQ(1u, job.GetPosition());
     ASSERT_FALSE(job.IsTrailingStepDone());
     
@@ -1863,7 +1868,7 @@
       ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
     }
 
-    ASSERT_EQ(JobStepCode_Success, job.Step().GetCode());
+    ASSERT_EQ(JobStepCode_Success, job.Step("jobId").GetCode());
     ASSERT_EQ(2u, job.GetPosition());
     ASSERT_TRUE(job.IsTrailingStepDone());
     
@@ -1872,7 +1877,7 @@
       ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job));
     }
 
-    ASSERT_THROW(job.Step(), OrthancException);
+    ASSERT_THROW(job.Step("jobId"), OrthancException);
   }
 }
 
@@ -1899,6 +1904,8 @@
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Get));
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Store));
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Move));
+    ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction));
+    ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
   }
 
   s = Json::nullValue;
@@ -1911,8 +1918,10 @@
     modality.SetApplicationEntityTitle("HELLO");
     modality.SetHost("world");
     modality.SetPortNumber(45);
-    modality.SetManufacturer(ModalityManufacturer_Dcm4Chee);
-    modality.SetPreferredTransferSyntax("1.2.840.10008.1.2");
+    modality.SetManufacturer(ModalityManufacturer_GenericNoWildcardInDates);
+    ASSERT_EQ("1.2.840.10008.1.2", modality.GetPreferredTransferSyntax());
+    modality.SetPreferredTransferSyntax("1.2.840.10008.1.2.1");
+    ASSERT_EQ("1.2.840.10008.1.2.1", modality.GetPreferredTransferSyntax());
     modality.Serialize(s, true);
     ASSERT_EQ(Json::objectValue, s.type());
   }
@@ -1922,12 +1931,14 @@
     ASSERT_EQ("HELLO", modality.GetApplicationEntityTitle());
     ASSERT_EQ("world", modality.GetHost());
     ASSERT_EQ(45u, modality.GetPortNumber());
-    ASSERT_EQ(ModalityManufacturer_Dcm4Chee, modality.GetManufacturer());
+    ASSERT_EQ(ModalityManufacturer_GenericNoWildcardInDates, modality.GetManufacturer());
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Echo));
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Find));
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Get));
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Store));
     ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_Move));
+    ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction));
+    ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
   }
 
   s["Port"] = "46";
@@ -1947,8 +1958,10 @@
   operations.insert(DicomRequestType_Get);
   operations.insert(DicomRequestType_Move);
   operations.insert(DicomRequestType_Store);
+  operations.insert(DicomRequestType_NAction);
+  operations.insert(DicomRequestType_NEventReport);
 
-  ASSERT_EQ(5u, operations.size());
+  ASSERT_EQ(7u, operations.size());
 
   for (std::set<DicomRequestType>::const_iterator 
          it = operations.begin(); it != operations.end(); ++it)
@@ -1977,4 +1990,54 @@
       }
     }
   }
+
+  {
+    Json::Value s;
+    s["AllowStorageCommitment"] = false;
+    s["AET"] = "AET";
+    s["Host"] = "host";
+    s["Port"] = "104";
+    
+    RemoteModalityParameters modality(s);
+    ASSERT_TRUE(modality.IsAdvancedFormatNeeded());
+    ASSERT_EQ("AET", modality.GetApplicationEntityTitle());
+    ASSERT_EQ("host", modality.GetHost());
+    ASSERT_EQ(104u, modality.GetPortNumber());
+    ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction));
+    ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
+  }
+
+  {
+    Json::Value s;
+    s["AllowNAction"] = false;
+    s["AllowNEventReport"] = true;
+    s["AET"] = "AET";
+    s["Host"] = "host";
+    s["Port"] = "104";
+    
+    RemoteModalityParameters modality(s);
+    ASSERT_TRUE(modality.IsAdvancedFormatNeeded());
+    ASSERT_EQ("AET", modality.GetApplicationEntityTitle());
+    ASSERT_EQ("host", modality.GetHost());
+    ASSERT_EQ(104u, modality.GetPortNumber());
+    ASSERT_FALSE(modality.IsRequestAllowed(DicomRequestType_NAction));
+    ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
+  }
+
+  {
+    Json::Value s;
+    s["AllowNAction"] = true;
+    s["AllowNEventReport"] = true;
+    s["AET"] = "AET";
+    s["Host"] = "host";
+    s["Port"] = "104";
+    
+    RemoteModalityParameters modality(s);
+    ASSERT_FALSE(modality.IsAdvancedFormatNeeded());
+    ASSERT_EQ("AET", modality.GetApplicationEntityTitle());
+    ASSERT_EQ("host", modality.GetHost());
+    ASSERT_EQ(104u, modality.GetPortNumber());
+    ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NAction));
+    ASSERT_TRUE(modality.IsRequestAllowed(DicomRequestType_NEventReport));
+  }
 }
--- a/UnitTestsSources/PluginsTests.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/PluginsTests.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/PrecompiledHeadersUnitTests.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/PrecompiledHeadersUnitTests.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/PrecompiledHeadersUnitTests.h	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/PrecompiledHeadersUnitTests.h	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/RestApiTests.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/RestApiTests.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/SQLiteChromiumTests.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/SQLiteChromiumTests.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/SQLiteTests.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/SQLiteTests.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/ServerIndexTests.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/ServerIndexTests.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -34,6 +34,7 @@
 #include "PrecompiledHeadersUnitTests.h"
 #include "gtest/gtest.h"
 
+#include "../Core/Compatibility.h"
 #include "../Core/FileStorage/FilesystemStorage.h"
 #include "../Core/FileStorage/MemoryStorageArea.h"
 #include "../Core/Logging.h"
@@ -95,8 +96,8 @@
   class DatabaseWrapperTest : public ::testing::Test
   {
   protected:
-    std::auto_ptr<TestDatabaseListener>  listener_;
-    std::auto_ptr<SQLiteDatabaseWrapper> index_;
+    std::unique_ptr<TestDatabaseListener>  listener_;
+    std::unique_ptr<SQLiteDatabaseWrapper> index_;
 
   public:
     DatabaseWrapperTest()
--- a/UnitTestsSources/StreamTests.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/StreamTests.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/UnitTestsSources/ToolboxTests.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/ToolboxTests.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -33,6 +33,8 @@
 
 #include "PrecompiledHeadersUnitTests.h"
 #include "gtest/gtest.h"
+#include "../Core/Compatibility.h"
+#include "../Core/IDynamicObject.h"
 #include "../Core/OrthancException.h"
 #include "../Core/Toolbox.h"
 
@@ -134,3 +136,36 @@
   printf("decoding took %zu ms\n", (std::chrono::duration_cast<std::chrono::milliseconds>(afterDecoding - afterEncoding)));
 }
 #endif
+
+
+TEST(Toolbox, LargeHexadecimalToDecimal)
+{
+  // https://stackoverflow.com/a/16967286/881731
+  ASSERT_EQ(
+    "166089946137986168535368849184301740204613753693156360462575217560130904921953976324839782808018277000296027060873747803291797869684516494894741699267674246881622658654267131250470956587908385447044319923040838072975636163137212887824248575510341104029461758594855159174329892125993844566497176102668262139513",
+    Toolbox::LargeHexadecimalToDecimal("EC851A69B8ACD843164E10CFF70CF9E86DC2FEE3CF6F374B43C854E3342A2F1AC3E30C741CC41E679DF6D07CE6FA3A66083EC9B8C8BF3AF05D8BDBB0AA6Cb3ef8c5baa2a5e531ba9e28592f99e0fe4f95169a6c63f635d0197e325c5ec76219b907e4ebdcd401fb1986e4e3ca661ff73e7e2b8fd9988e753b7042b2bbca76679"));
+
+  ASSERT_EQ("0", Toolbox::LargeHexadecimalToDecimal(""));
+  ASSERT_EQ("0", Toolbox::LargeHexadecimalToDecimal("0"));
+  ASSERT_EQ("0", Toolbox::LargeHexadecimalToDecimal("0000"));
+  ASSERT_EQ("255", Toolbox::LargeHexadecimalToDecimal("00000ff"));
+
+  ASSERT_THROW(Toolbox::LargeHexadecimalToDecimal("g"), Orthanc::OrthancException);
+}
+
+
+TEST(Toolbox, GenerateDicomPrivateUniqueIdentifier)
+{
+  std::string s = Toolbox::GenerateDicomPrivateUniqueIdentifier();
+  ASSERT_EQ("2.25.", s.substr(0, 5));
+}
+
+
+TEST(Toolbox, UniquePtr)
+{
+  std::unique_ptr<int> i(new int(42));
+  ASSERT_EQ(42, *i);
+
+  std::unique_ptr<SingleValueObject<int> > j(new SingleValueObject<int>(42));
+  ASSERT_EQ(42, j->GetValue());
+}
--- a/UnitTestsSources/UnitTestsMain.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/UnitTestsMain.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -630,13 +630,13 @@
   ASSERT_STREQ("GenericNoWildcardInDates", EnumerationToString(StringToModalityManufacturer("GenericNoWildcardInDates")));
   ASSERT_STREQ("GenericNoUniversalWildcard", EnumerationToString(StringToModalityManufacturer("GenericNoUniversalWildcard")));
   ASSERT_STREQ("StoreScp", EnumerationToString(StringToModalityManufacturer("StoreScp")));
-  ASSERT_STREQ("ClearCanvas", EnumerationToString(StringToModalityManufacturer("ClearCanvas")));
-  ASSERT_STREQ("Dcm4Chee", EnumerationToString(StringToModalityManufacturer("Dcm4Chee")));
   ASSERT_STREQ("Vitrea", EnumerationToString(StringToModalityManufacturer("Vitrea")));
   ASSERT_STREQ("GE", EnumerationToString(StringToModalityManufacturer("GE")));
   // backward compatibility tests (to remove once we make these manufacturer really obsolete)
   ASSERT_STREQ("Generic", EnumerationToString(StringToModalityManufacturer("MedInria")));
   ASSERT_STREQ("Generic", EnumerationToString(StringToModalityManufacturer("EFilm2")));
+  ASSERT_STREQ("Generic", EnumerationToString(StringToModalityManufacturer("ClearCanvas")));
+  ASSERT_STREQ("Generic", EnumerationToString(StringToModalityManufacturer("Dcm4Chee")));
   ASSERT_STREQ("GenericNoWildcardInDates", EnumerationToString(StringToModalityManufacturer("SyngoVia")));
   ASSERT_STREQ("GenericNoWildcardInDates", EnumerationToString(StringToModalityManufacturer("AgfaImpax")));
 }
@@ -1414,6 +1414,7 @@
 int main(int argc, char **argv)
 {
   Logging::Initialize();
+  Toolbox::InitializeGlobalLocale(NULL);
   Logging::EnableInfoLevel(true);
   Toolbox::DetectEndianness();
   SystemToolbox::MakeDirectory("UnitTestsResults");
--- a/UnitTestsSources/VersionsTests.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/VersionsTests.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -88,8 +88,15 @@
 #else
   // http://www.sqlite.org/capi3ref.html#sqlite3_libversion
   EXPECT_EQ(sqlite3_libversion_number(), SQLITE_VERSION_NUMBER);
-  EXPECT_STREQ(sqlite3_sourceid(), SQLITE_SOURCE_ID);
   EXPECT_STREQ(sqlite3_libversion(), SQLITE_VERSION);
+  
+  /**
+   * On Orthanc > 1.5.8, we comment out the following test, that is
+   * too strict for some GNU/Linux distributions to apply their own
+   * security fixes. Checking the main version macros is sufficient.
+   * https://bugzilla.suse.com/show_bug.cgi?id=1154550#c2
+   **/
+  // EXPECT_STREQ(sqlite3_sourceid(), SQLITE_SOURCE_ID);
 #endif
 
   // Ensure that the SQLite version is above 3.7.0.
@@ -106,6 +113,14 @@
 }
 
 
+#if ORTHANC_ENABLE_CIVETWEB == 1
+TEST(Version, CivetwebCompression)
+{
+  ASSERT_TRUE(mg_check_feature(MG_FEATURES_COMPRESSION));
+}
+#endif
+
+
 #if ORTHANC_STATIC == 1
 
 TEST(Versions, ZlibStatic)
@@ -169,7 +184,8 @@
 #if ORTHANC_ENABLE_SSL == 1
 TEST(Version, OpenSslStatic)
 {
-  ASSERT_EQ(0x1000210fL /* openssl-1.0.2p */, OPENSSL_VERSION_NUMBER);
+  ASSERT_TRUE(OPENSSL_VERSION_NUMBER == 0x1000210fL /* openssl-1.0.2p */ ||
+              OPENSSL_VERSION_NUMBER == 0x1010104fL /* openssl-1.1.1d */);
 }
 #endif
 
--- a/UnitTestsSources/ZipTests.cpp	Wed Mar 18 08:59:06 2020 +0100
+++ b/UnitTestsSources/ZipTests.cpp	Thu Mar 19 11:48:30 2020 +0100
@@ -2,7 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
- * Copyright (C) 2017-2019 Osimis S.A., Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as