changeset 200:03afbee0cc7b

integration of Orthanc core into Stone
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 23 Mar 2018 11:04:03 +0100
parents dabe9982fca3
children e9c7a78a3e77
files Resources/CMake/OrthancStoneParameters.cmake Resources/Orthanc/Core/Cache/ICachePageProvider.h Resources/Orthanc/Core/Cache/LeastRecentlyUsedIndex.h Resources/Orthanc/Core/Cache/MemoryCache.cpp Resources/Orthanc/Core/Cache/MemoryCache.h Resources/Orthanc/Core/Cache/SharedArchive.cpp Resources/Orthanc/Core/Cache/SharedArchive.h Resources/Orthanc/Core/ChunkedBuffer.cpp Resources/Orthanc/Core/ChunkedBuffer.h Resources/Orthanc/Core/Compression/DeflateBaseCompressor.cpp Resources/Orthanc/Core/Compression/DeflateBaseCompressor.h Resources/Orthanc/Core/Compression/GzipCompressor.cpp Resources/Orthanc/Core/Compression/GzipCompressor.h Resources/Orthanc/Core/Compression/HierarchicalZipWriter.cpp Resources/Orthanc/Core/Compression/HierarchicalZipWriter.h Resources/Orthanc/Core/Compression/IBufferCompressor.h Resources/Orthanc/Core/Compression/ZipWriter.cpp Resources/Orthanc/Core/Compression/ZipWriter.h Resources/Orthanc/Core/Compression/ZlibCompressor.cpp Resources/Orthanc/Core/Compression/ZlibCompressor.h Resources/Orthanc/Core/DicomFormat/DicomArray.cpp Resources/Orthanc/Core/DicomFormat/DicomArray.h Resources/Orthanc/Core/DicomFormat/DicomElement.h Resources/Orthanc/Core/DicomFormat/DicomImageInformation.cpp Resources/Orthanc/Core/DicomFormat/DicomImageInformation.h Resources/Orthanc/Core/DicomFormat/DicomInstanceHasher.cpp Resources/Orthanc/Core/DicomFormat/DicomInstanceHasher.h Resources/Orthanc/Core/DicomFormat/DicomIntegerPixelAccessor.cpp Resources/Orthanc/Core/DicomFormat/DicomIntegerPixelAccessor.h Resources/Orthanc/Core/DicomFormat/DicomMap.cpp Resources/Orthanc/Core/DicomFormat/DicomMap.h Resources/Orthanc/Core/DicomFormat/DicomTag.cpp Resources/Orthanc/Core/DicomFormat/DicomTag.h Resources/Orthanc/Core/DicomFormat/DicomValue.cpp Resources/Orthanc/Core/DicomFormat/DicomValue.h Resources/Orthanc/Core/Endianness.h Resources/Orthanc/Core/Enumerations.cpp Resources/Orthanc/Core/Enumerations.h Resources/Orthanc/Core/FileStorage/FileInfo.h Resources/Orthanc/Core/FileStorage/FilesystemStorage.cpp Resources/Orthanc/Core/FileStorage/FilesystemStorage.h Resources/Orthanc/Core/FileStorage/IStorageArea.h Resources/Orthanc/Core/FileStorage/StorageAccessor.cpp Resources/Orthanc/Core/FileStorage/StorageAccessor.h Resources/Orthanc/Core/HttpClient.cpp Resources/Orthanc/Core/HttpClient.h Resources/Orthanc/Core/ICommand.h Resources/Orthanc/Core/IDynamicObject.h Resources/Orthanc/Core/Images/Font.cpp Resources/Orthanc/Core/Images/Font.h Resources/Orthanc/Core/Images/FontRegistry.cpp Resources/Orthanc/Core/Images/FontRegistry.h Resources/Orthanc/Core/Images/IImageWriter.cpp Resources/Orthanc/Core/Images/IImageWriter.h Resources/Orthanc/Core/Images/Image.cpp Resources/Orthanc/Core/Images/Image.h Resources/Orthanc/Core/Images/ImageAccessor.cpp Resources/Orthanc/Core/Images/ImageAccessor.h Resources/Orthanc/Core/Images/ImageBuffer.cpp Resources/Orthanc/Core/Images/ImageBuffer.h Resources/Orthanc/Core/Images/ImageProcessing.cpp Resources/Orthanc/Core/Images/ImageProcessing.h Resources/Orthanc/Core/Images/ImageTraits.h Resources/Orthanc/Core/Images/JpegErrorManager.cpp Resources/Orthanc/Core/Images/JpegErrorManager.h Resources/Orthanc/Core/Images/JpegReader.cpp Resources/Orthanc/Core/Images/JpegReader.h Resources/Orthanc/Core/Images/JpegWriter.cpp Resources/Orthanc/Core/Images/JpegWriter.h Resources/Orthanc/Core/Images/PixelTraits.h Resources/Orthanc/Core/Images/PngReader.cpp Resources/Orthanc/Core/Images/PngReader.h Resources/Orthanc/Core/Images/PngWriter.cpp Resources/Orthanc/Core/Images/PngWriter.h Resources/Orthanc/Core/Logging.cpp Resources/Orthanc/Core/Logging.h Resources/Orthanc/Core/MultiThreading/BagOfTasks.h Resources/Orthanc/Core/MultiThreading/BagOfTasksProcessor.cpp Resources/Orthanc/Core/MultiThreading/BagOfTasksProcessor.h Resources/Orthanc/Core/MultiThreading/ILockable.h Resources/Orthanc/Core/MultiThreading/IRunnableBySteps.h Resources/Orthanc/Core/MultiThreading/Mutex.cpp Resources/Orthanc/Core/MultiThreading/Mutex.h Resources/Orthanc/Core/MultiThreading/ReaderWriterLock.cpp Resources/Orthanc/Core/MultiThreading/ReaderWriterLock.h Resources/Orthanc/Core/MultiThreading/RunnableWorkersPool.cpp Resources/Orthanc/Core/MultiThreading/RunnableWorkersPool.h Resources/Orthanc/Core/MultiThreading/Semaphore.cpp Resources/Orthanc/Core/MultiThreading/Semaphore.h Resources/Orthanc/Core/MultiThreading/SharedMessageQueue.cpp Resources/Orthanc/Core/MultiThreading/SharedMessageQueue.h Resources/Orthanc/Core/OrthancException.h Resources/Orthanc/Core/PrecompiledHeaders.cpp Resources/Orthanc/Core/PrecompiledHeaders.h Resources/Orthanc/Core/SharedLibrary.cpp Resources/Orthanc/Core/SharedLibrary.h Resources/Orthanc/Core/SystemToolbox.cpp Resources/Orthanc/Core/SystemToolbox.h Resources/Orthanc/Core/TemporaryFile.cpp Resources/Orthanc/Core/TemporaryFile.h Resources/Orthanc/Core/Toolbox.cpp Resources/Orthanc/Core/Toolbox.h Resources/Orthanc/Core/WebServiceParameters.cpp Resources/Orthanc/Core/WebServiceParameters.h Resources/Orthanc/NEWS Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.cpp Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.h Resources/Orthanc/Plugins/Samples/Common/DicomPath.cpp Resources/Orthanc/Plugins/Samples/Common/DicomPath.h Resources/Orthanc/Plugins/Samples/Common/DicomTag.h Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.cpp Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h Resources/Orthanc/Plugins/Samples/Common/IDicomDataset.h Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.cpp Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.h Resources/Orthanc/Plugins/Samples/Common/OrthancHttpConnection.cpp Resources/Orthanc/Plugins/Samples/Common/OrthancHttpConnection.h Resources/Orthanc/Plugins/Samples/Common/OrthancPluginException.h Resources/Orthanc/README.txt Resources/Orthanc/Resources/CMake/AutoGeneratedCode.cmake Resources/Orthanc/Resources/CMake/BoostConfiguration.cmake Resources/Orthanc/Resources/CMake/Compiler.cmake Resources/Orthanc/Resources/CMake/DownloadPackage.cmake Resources/Orthanc/Resources/CMake/GoogleTestConfiguration.cmake Resources/Orthanc/Resources/CMake/JsonCppConfiguration.cmake Resources/Orthanc/Resources/CMake/LibCurlConfiguration.cmake Resources/Orthanc/Resources/CMake/LibJpegConfiguration.cmake Resources/Orthanc/Resources/CMake/LibPngConfiguration.cmake Resources/Orthanc/Resources/CMake/OpenSslConfiguration.cmake Resources/Orthanc/Resources/CMake/OrthancFrameworkConfiguration.cmake Resources/Orthanc/Resources/CMake/OrthancFrameworkParameters.cmake Resources/Orthanc/Resources/CMake/UuidConfiguration.cmake Resources/Orthanc/Resources/CMake/ZlibConfiguration.cmake Resources/Orthanc/Resources/EmbedResources.py Resources/Orthanc/Resources/LinuxStandardBaseToolchain.cmake Resources/Orthanc/Resources/MinGW-W64-Toolchain32.cmake Resources/Orthanc/Resources/MinGW-W64-Toolchain64.cmake Resources/Orthanc/Resources/MinGWToolchain.cmake Resources/Orthanc/Resources/Patches/boost-1.65.1-linux-standard-base.patch Resources/Orthanc/Resources/ThirdParty/VisualStudio/stdint.h Resources/Orthanc/Resources/ThirdParty/base64/base64.cpp Resources/Orthanc/Resources/ThirdParty/base64/base64.h Resources/Orthanc/Resources/ThirdParty/md5/md5.c Resources/Orthanc/Resources/ThirdParty/md5/md5.h Resources/Orthanc/Resources/ThirdParty/minizip/NOTES Resources/Orthanc/Resources/ThirdParty/minizip/crypt.h Resources/Orthanc/Resources/ThirdParty/minizip/ioapi.c Resources/Orthanc/Resources/ThirdParty/minizip/ioapi.h Resources/Orthanc/Resources/ThirdParty/minizip/zip.c Resources/Orthanc/Resources/ThirdParty/minizip/zip.h Resources/Orthanc/Resources/ThirdParty/patch/NOTES.txt Resources/Orthanc/Resources/ThirdParty/patch/msys-1.0.dll Resources/Orthanc/Resources/ThirdParty/patch/patch.exe Resources/Orthanc/Resources/ThirdParty/patch/patch.exe.manifest Resources/Orthanc/Resources/WindowsResources.py Resources/Orthanc/Resources/WindowsResources.rc Resources/SyncOrthancFolder.py
diffstat 157 files changed, 30219 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/Resources/CMake/OrthancStoneParameters.cmake	Thu Mar 22 12:53:47 2018 +0100
+++ b/Resources/CMake/OrthancStoneParameters.cmake	Fri Mar 23 11:04:03 2018 +0100
@@ -22,22 +22,20 @@
 ## Import the parameters of the Orthanc Framework
 #####################################################################
 
-# TODO => Import
-SET(ORTHANC_ROOT /home/jodogne/Subversion/orthanc)
+set(ORTHANC_STONE_ROOT ${CMAKE_CURRENT_LIST_DIR}/../..)
+set(ORTHANC_ROOT ${ORTHANC_STONE_ROOT}/Resources/Orthanc)
 
 include(${ORTHANC_ROOT}/Resources/CMake/OrthancFrameworkParameters.cmake)
 
-SET(ORTHANC_STONE_ROOT ${CMAKE_CURRENT_LIST_DIR}/../..)
-
 
 #####################################################################
 ## CMake parameters tunable by the user
 #####################################################################
 
 # Advanced parameters to fine-tune linking against system libraries
-SET(USE_SYSTEM_CAIRO ON CACHE BOOL "Use the system version of Cairo")
-SET(USE_SYSTEM_PIXMAN ON CACHE BOOL "Use the system version of Pixman")
-SET(USE_SYSTEM_SDL ON CACHE BOOL "Use the system version of SDL2")
+set(USE_SYSTEM_CAIRO ON CACHE BOOL "Use the system version of Cairo")
+set(USE_SYSTEM_PIXMAN ON CACHE BOOL "Use the system version of Pixman")
+set(USE_SYSTEM_SDL ON CACHE BOOL "Use the system version of SDL2")
 
 
 #####################################################################
@@ -45,4 +43,4 @@
 ## the Stone of Orthanc
 #####################################################################
 
-SET(ENABLE_SDL ON CACHE INTERNAL "Include support for SDL")
+set(ENABLE_SDL ON CACHE INTERNAL "Include support for SDL")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Cache/ICachePageProvider.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,50 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "../IDynamicObject.h"
+
+namespace Orthanc
+{
+  class ICachePageProvider
+  {
+  public:
+    virtual ~ICachePageProvider()
+    {
+    }
+
+    virtual IDynamicObject* Provide(const std::string& id) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Cache/LeastRecentlyUsedIndex.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,347 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 <list>
+#include <map>
+#include <boost/noncopyable.hpp>
+#include <cassert>
+
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+
+namespace Orthanc
+{
+  /**
+   * This class implements the index of a cache with least recently
+   * used (LRU) recycling policy. All the items of the cache index
+   * can be associated with a payload.
+   * Reference: http://stackoverflow.com/a/2504317
+   **/
+  template <typename T, typename Payload = NullType>
+  class LeastRecentlyUsedIndex : public boost::noncopyable
+  {
+  private:
+    typedef std::list< std::pair<T, Payload> >  Queue;
+    typedef std::map<T, typename Queue::iterator>  Index;
+
+    Index  index_;
+    Queue  queue_;
+
+    /**
+     * Internal method for debug builds to check whether the internal
+     * data structures are not corrupted.
+     **/
+    void CheckInvariants() const;
+
+  public:
+    /**
+     * Add a new element to the cache index, and make it the most
+     * recent element.
+     * \param id The ID of the element.
+     * \param payload The payload of the element.
+     **/
+    void Add(T id, Payload payload = Payload());
+
+    void AddOrMakeMostRecent(T id, Payload payload = Payload());
+
+    /**
+     * When accessing an element of the cache, this method tags the
+     * element as the most recently used.
+     * \param id The most recently accessed item.
+     **/
+    void MakeMostRecent(T id);
+
+    void MakeMostRecent(T id, Payload updatedPayload);
+
+    /**
+     * Remove an element from the cache index.
+     * \param id The item to remove.
+     **/
+    Payload Invalidate(T id);
+
+    /**
+     * Get the oldest element in the cache and remove it.
+     * \return The oldest item.
+     **/
+    T RemoveOldest();
+
+    /**
+     * Get the oldest element in the cache, remove it and return the
+     * associated payload.
+     * \param payload Where to store the associated payload.
+     * \return The oldest item.
+     **/
+    T RemoveOldest(Payload& payload);
+
+    /**
+     * Check whether an element is contained in the cache.
+     * \param id The item.
+     * \return \c true iff the item is indexed by the cache.
+     **/
+    bool Contains(T id) const
+    {
+      return index_.find(id) != index_.end();
+    }
+
+    bool Contains(T id, Payload& payload) const
+    {
+      typename Index::const_iterator it = index_.find(id);
+      if (it == index_.end())
+      {
+        return false;
+      }
+      else
+      {
+        payload = it->second->second;
+        return true;
+      }
+    }
+
+    /**
+     * Return the number of elements in the cache.
+     * \return The number of elements.
+     **/
+    size_t GetSize() const
+    {
+      assert(index_.size() == queue_.size());
+      return queue_.size();
+    }
+
+    /**
+     * Check whether the cache index is empty.
+     * \return \c true iff the cache is empty.
+     **/
+    bool IsEmpty() const
+    {
+      return index_.empty();
+    }
+
+    const T& GetOldest() const;
+    
+    const Payload& GetOldestPayload() const;
+  };
+
+
+
+
+  /******************************************************************
+   ** Implementation of the template
+   ******************************************************************/
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::CheckInvariants() const
+  {
+#ifndef NDEBUG
+    assert(index_.size() == queue_.size());
+
+    for (typename Index::const_iterator 
+           it = index_.begin(); it != index_.end(); it++)
+    {
+      assert(it->second != queue_.end());
+      assert(it->second->first == it->first);
+    }
+#endif
+  }
+
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::Add(T id, Payload payload)
+  {
+    if (Contains(id))
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    queue_.push_front(std::make_pair(id, payload));
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::MakeMostRecent(T id)
+  {
+    if (!Contains(id))
+    {
+      throw OrthancException(ErrorCode_InexistentItem);
+    }
+
+    typename Index::iterator it = index_.find(id);
+    assert(it != index_.end());
+
+    std::pair<T, Payload> item = *(it->second);
+    
+    queue_.erase(it->second);
+    queue_.push_front(item);
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::AddOrMakeMostRecent(T id, Payload payload)
+  {
+    typename Index::iterator it = index_.find(id);
+
+    if (it != index_.end())
+    {
+      // Already existing. Make it most recent.
+      std::pair<T, Payload> item = *(it->second);
+      item.second = payload;
+      queue_.erase(it->second);
+      queue_.push_front(item);
+    }
+    else
+    {
+      // New item
+      queue_.push_front(std::make_pair(id, payload));
+    }
+
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::MakeMostRecent(T id, Payload updatedPayload)
+  {
+    if (!Contains(id))
+    {
+      throw OrthancException(ErrorCode_InexistentItem);
+    }
+
+    typename Index::iterator it = index_.find(id);
+    assert(it != index_.end());
+
+    std::pair<T, Payload> item = *(it->second);
+    item.second = updatedPayload;
+    
+    queue_.erase(it->second);
+    queue_.push_front(item);
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  Payload LeastRecentlyUsedIndex<T, Payload>::Invalidate(T id)
+  {
+    if (!Contains(id))
+    {
+      throw OrthancException(ErrorCode_InexistentItem);
+    }
+
+    typename Index::iterator it = index_.find(id);
+    assert(it != index_.end());
+
+    Payload payload = it->second->second;
+    queue_.erase(it->second);
+    index_.erase(it);
+
+    CheckInvariants();
+    return payload;
+  }
+
+
+  template <typename T, typename Payload>
+  T LeastRecentlyUsedIndex<T, Payload>::RemoveOldest(Payload& payload)
+  {
+    if (IsEmpty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    std::pair<T, Payload> item = queue_.back();
+    T oldest = item.first;
+    payload = item.second;
+
+    queue_.pop_back();
+    assert(index_.find(oldest) != index_.end());
+    index_.erase(oldest);
+
+    CheckInvariants();
+
+    return oldest;
+  }
+
+
+  template <typename T, typename Payload>
+  T LeastRecentlyUsedIndex<T, Payload>::RemoveOldest()
+  {
+    if (IsEmpty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    std::pair<T, Payload> item = queue_.back();
+    T oldest = item.first;
+
+    queue_.pop_back();
+    assert(index_.find(oldest) != index_.end());
+    index_.erase(oldest);
+
+    CheckInvariants();
+
+    return oldest;
+  }
+
+
+  template <typename T, typename Payload>
+  const T& LeastRecentlyUsedIndex<T, Payload>::GetOldest() const
+  {
+    if (IsEmpty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    return queue_.back().first;
+  }
+
+
+  template <typename T, typename Payload>
+  const Payload& LeastRecentlyUsedIndex<T, Payload>::GetOldestPayload() const
+  {
+    if (IsEmpty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    return queue_.back().second;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Cache/MemoryCache.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,96 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "MemoryCache.h"
+
+#include "../Logging.h"
+
+namespace Orthanc
+{
+  MemoryCache::Page& MemoryCache::Load(const std::string& 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::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;
+  }
+
+  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;
+    }
+  }
+
+  IDynamicObject& MemoryCache::Access(const std::string& id)
+  {
+    return *Load(id).content_;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Cache/MemoryCache.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,68 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 <memory>
+#include "LeastRecentlyUsedIndex.h"
+#include "ICachePageProvider.h"
+
+namespace Orthanc
+{
+  /**
+   * WARNING: This class is NOT thread-safe.
+   **/
+  class MemoryCache
+  {
+  private:
+    struct Page
+    {
+      std::string id_;
+      std::auto_ptr<IDynamicObject> content_;
+    };
+
+    ICachePageProvider& provider_;
+    size_t cacheSize_;
+    LeastRecentlyUsedIndex<std::string, Page*>  index_;
+
+    Page& Load(const std::string& id);
+
+  public:
+    MemoryCache(ICachePageProvider& provider,
+                size_t cacheSize);
+
+    ~MemoryCache();
+
+    IDynamicObject& Access(const std::string& id);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Cache/SharedArchive.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,134 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "SharedArchive.h"
+
+#include "../SystemToolbox.h"
+
+
+namespace Orthanc
+{
+  void SharedArchive::RemoveInternal(const std::string& id)
+  {
+    Archive::iterator it = archive_.find(id);
+
+    if (it != archive_.end())
+    {
+      delete it->second;
+      archive_.erase(it);
+    }
+  }
+
+
+  SharedArchive::Accessor::Accessor(SharedArchive& that,
+                                    const std::string& id) :
+    lock_(that.mutex_)
+  {
+    Archive::iterator it = that.archive_.find(id);
+
+    if (it == that.archive_.end())
+    {
+      throw OrthancException(ErrorCode_InexistentItem);
+    }
+    else
+    {
+      that.lru_.MakeMostRecent(id);
+      item_ = it->second;
+    }
+  }
+
+
+  SharedArchive::SharedArchive(size_t maxSize) : 
+    maxSize_(maxSize)
+  {
+    if (maxSize == 0)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  SharedArchive::~SharedArchive()
+  {
+    for (Archive::iterator it = archive_.begin();
+         it != archive_.end(); ++it)
+    {
+      delete it->second;
+    }
+  }
+
+
+  std::string SharedArchive::Add(IDynamicObject* obj)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (archive_.size() == maxSize_)
+    {
+      // The quota has been reached, remove the oldest element
+      std::string oldest = lru_.RemoveOldest();
+      RemoveInternal(oldest);
+    }
+
+    std::string id = SystemToolbox::GenerateUuid();
+    RemoveInternal(id);  // Should never be useful because of UUID
+    archive_[id] = obj;
+    lru_.Add(id);
+
+    return id;
+  }
+
+
+  void SharedArchive::Remove(const std::string& id)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    RemoveInternal(id);      
+    lru_.Invalidate(id);
+  }
+
+
+  void SharedArchive::List(std::list<std::string>& items)
+  {
+    items.clear();
+
+    boost::mutex::scoped_lock lock(mutex_);
+
+    for (Archive::const_iterator it = archive_.begin();
+         it != archive_.end(); ++it)
+    {
+      items.push_back(it->first);
+    }
+  }
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Cache/SharedArchive.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,94 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 !defined(ORTHANC_SANDBOXED)
+#  error The macro ORTHANC_SANDBOXED must be defined
+#endif
+
+#if ORTHANC_SANDBOXED == 1
+#  error The class SharedArchive cannot be used in sandboxed environments
+#endif
+
+#include "LeastRecentlyUsedIndex.h"
+#include "../IDynamicObject.h"
+
+#include <map>
+#include <boost/thread.hpp>
+
+namespace Orthanc
+{
+  class SharedArchive : public boost::noncopyable
+  {
+  private:
+    typedef std::map<std::string, IDynamicObject*>  Archive;
+
+    size_t         maxSize_;
+    boost::mutex   mutex_;
+    Archive        archive_;
+    Orthanc::LeastRecentlyUsedIndex<std::string> lru_;
+
+    void RemoveInternal(const std::string& id);
+
+  public:
+    class Accessor : public boost::noncopyable
+    {
+    private:
+      boost::mutex::scoped_lock  lock_;
+      IDynamicObject*            item_;
+
+    public:
+      Accessor(SharedArchive& that,
+               const std::string& id);
+
+      IDynamicObject& GetItem() const
+      {
+        return *item_;
+      }      
+    };
+
+
+    SharedArchive(size_t maxSize);
+
+    ~SharedArchive();
+
+    std::string Add(IDynamicObject* obj);  // Takes the ownership
+
+    void Remove(const std::string& id);
+
+    void List(std::list<std::string>& items);
+  };
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/ChunkedBuffer.cpp	Fri Mar 23 11:04:03 2018 +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-2018 Osimis S.A., 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 "ChunkedBuffer.h"
+
+#include <cassert>
+#include <string.h>
+
+
+namespace Orthanc
+{
+  void ChunkedBuffer::Clear()
+  {
+    numBytes_ = 0;
+
+    for (Chunks::iterator it = chunks_.begin(); 
+         it != chunks_.end(); ++it)
+    {
+      delete *it;
+    }
+  }
+
+
+  void ChunkedBuffer::AddChunk(const void* chunkData,
+                               size_t chunkSize)
+  {
+    if (chunkSize == 0)
+    {
+      return;
+    }
+    else
+    {
+      assert(chunkData != NULL);
+      chunks_.push_back(new std::string(reinterpret_cast<const char*>(chunkData), chunkSize));
+      numBytes_ += chunkSize;
+    }
+  }
+
+
+  void ChunkedBuffer::AddChunk(const std::string& chunk)
+  {
+    if (chunk.size() > 0)
+    {
+      AddChunk(&chunk[0], chunk.size());
+    }
+  }
+
+
+  void ChunkedBuffer::Flatten(std::string& result)
+  {
+    result.resize(numBytes_);
+
+    size_t pos = 0;
+    for (Chunks::iterator it = chunks_.begin(); 
+         it != chunks_.end(); ++it)
+    {
+      assert(*it != NULL);
+
+      size_t s = (*it)->size();
+      if (s != 0)
+      {
+        memcpy(&result[pos], (*it)->c_str(), s);
+        pos += s;
+      }
+
+      delete *it;
+    }
+
+    chunks_.clear();
+    numBytes_ = 0;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/ChunkedBuffer.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,72 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 <list>
+#include <string>
+
+namespace Orthanc
+{
+  class ChunkedBuffer
+  {
+  private:
+    typedef std::list<std::string*>  Chunks;
+    size_t numBytes_;
+    Chunks chunks_;
+  
+    void Clear();
+
+  public:
+    ChunkedBuffer() : numBytes_(0)
+    {
+    }
+
+    ~ChunkedBuffer()
+    {
+      Clear();
+    }
+
+    size_t GetNumBytes() const
+    {
+      return numBytes_;
+    }
+
+    void AddChunk(const void* chunkData,
+                  size_t chunkSize);
+
+    void AddChunk(const std::string& chunk);
+
+    void Flatten(std::string& result);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Compression/DeflateBaseCompressor.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,76 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "DeflateBaseCompressor.h"
+
+#include "../OrthancException.h"
+#include "../Logging.h"
+
+#include <string.h>
+
+namespace Orthanc
+{
+  void DeflateBaseCompressor::SetCompressionLevel(uint8_t level)
+  {
+    if (level >= 10)
+    {
+      LOG(ERROR) << "Zlib compression level must be between 0 (no compression) and 9 (highest compression)";
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    compressionLevel_ = level;
+  }
+
+
+  uint64_t DeflateBaseCompressor::ReadUncompressedSizePrefix(const void* compressed,
+                                                             size_t compressedSize)
+  {
+    if (compressedSize == 0)
+    {
+      return 0;
+    }
+
+    if (compressedSize < sizeof(uint64_t))
+    {
+      LOG(ERROR) << "The compressed buffer is ill-formed";
+      throw OrthancException(ErrorCode_CorruptedFile);
+    }
+
+    uint64_t size;
+    memcpy(&size, compressed, sizeof(uint64_t));
+
+    return size;
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Compression/DeflateBaseCompressor.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,76 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "IBufferCompressor.h"
+
+#include <stdint.h>
+
+namespace Orthanc
+{
+  class DeflateBaseCompressor : public IBufferCompressor
+  {
+  private:
+    uint8_t compressionLevel_;
+    bool    prefixWithUncompressedSize_;
+
+  protected:
+    uint64_t ReadUncompressedSizePrefix(const void* compressed,
+                                        size_t compressedSize);
+
+  public:
+    DeflateBaseCompressor() : 
+      compressionLevel_(6),
+      prefixWithUncompressedSize_(false)
+    {
+    }
+
+    void SetCompressionLevel(uint8_t level);
+    
+    void SetPrefixWithUncompressedSize(bool prefix)
+    {
+      prefixWithUncompressedSize_ = prefix;
+    }
+
+    bool HasPrefixWithUncompressedSize() const
+    {
+      return prefixWithUncompressedSize_;
+    }
+
+    uint8_t GetCompressionLevel() const
+    {
+      return compressionLevel_;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Compression/GzipCompressor.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,278 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "GzipCompressor.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <zlib.h>
+
+#include "../OrthancException.h"
+#include "../Logging.h"
+
+namespace Orthanc
+{
+  uint64_t GzipCompressor::GuessUncompressedSize(const void* compressed,
+                                                 size_t compressedSize)
+  {
+    /**
+     * "Is there a way to find out the size of the original file which
+     * is inside a GZIP file? [...] There is no truly reliable way,
+     * other than gunzipping the stream. You do not need to save the
+     * result of the decompression, so you can determine the size by
+     * simply reading and decoding the entire file without taking up
+     * space with the decompressed result.
+     *
+     * There is an unreliable way to determine the uncompressed size,
+     * which is to look at the last four bytes of the gzip file, which
+     * is the uncompressed length of that entry modulo 232 in little
+     * endian order.
+     * 
+     * It is unreliable because a) the uncompressed data may be longer
+     * than 2^32 bytes, and b) the gzip file may consist of multiple
+     * gzip streams, in which case you would find the length of only
+     * the last of those streams.
+     * 
+     * If you are in control of the source of the gzip files, you know
+     * that they consist of single gzip streams, and you know that
+     * they are less than 2^32 bytes uncompressed, then and only then
+     * can you use those last four bytes with confidence."
+     *
+     * http://stackoverflow.com/a/9727599/881731
+     **/
+
+    if (compressedSize < 4)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    const uint8_t* p = reinterpret_cast<const uint8_t*>(compressed) + compressedSize - 4;
+
+    return ((static_cast<uint32_t>(p[0]) << 0) +
+            (static_cast<uint32_t>(p[1]) << 8) +
+            (static_cast<uint32_t>(p[2]) << 16) +
+            (static_cast<uint32_t>(p[3]) << 24));            
+  }
+
+
+
+  void GzipCompressor::Compress(std::string& compressed,
+                                const void* uncompressed,
+                                size_t uncompressedSize)
+  {
+    uLongf compressedSize = compressBound(uncompressedSize) + 1024 /* security margin */;
+    if (compressedSize == 0)
+    {
+      compressedSize = 1;
+    }
+
+    uint8_t* target;
+    if (HasPrefixWithUncompressedSize())
+    {
+      compressed.resize(compressedSize + sizeof(uint64_t));
+      target = reinterpret_cast<uint8_t*>(&compressed[0]) + sizeof(uint64_t);
+    }
+    else
+    {
+      compressed.resize(compressedSize);
+      target = reinterpret_cast<uint8_t*>(&compressed[0]);
+    }
+
+    z_stream stream;
+    memset(&stream, 0, sizeof(stream));
+
+    stream.next_in = const_cast<Bytef*>(reinterpret_cast<const Bytef*>(uncompressed));
+    stream.next_out = reinterpret_cast<Bytef*>(target);
+
+    stream.avail_in = static_cast<uInt>(uncompressedSize);
+    stream.avail_out = static_cast<uInt>(compressedSize);
+
+    // Ensure no overflow (if the buffer is too large for the current archicture)
+    if (static_cast<size_t>(stream.avail_in) != uncompressedSize ||
+        static_cast<size_t>(stream.avail_out) != compressedSize)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+    
+    // Initialize the compression engine
+    int error = deflateInit2(&stream, 
+                             GetCompressionLevel(), 
+                             Z_DEFLATED,
+                             MAX_WBITS + 16,      // ask for gzip output
+                             8,                   // default memory level
+                             Z_DEFAULT_STRATEGY);
+
+    if (error != Z_OK)
+    {
+      // Cannot initialize zlib
+      compressed.clear();
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    // Compress the input buffer
+    error = deflate(&stream, Z_FINISH);
+
+    if (error != Z_STREAM_END)
+    {
+      deflateEnd(&stream);
+      compressed.clear();
+
+      switch (error)
+      {
+      case Z_MEM_ERROR:
+        throw OrthancException(ErrorCode_NotEnoughMemory);
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+      }  
+    }
+
+    size_t size = stream.total_out;
+
+    if (deflateEnd(&stream) != Z_OK)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    // The compression was successful
+    if (HasPrefixWithUncompressedSize())
+    {
+      uint64_t s = static_cast<uint64_t>(uncompressedSize);
+      memcpy(&compressed[0], &s, sizeof(uint64_t));
+      compressed.resize(size + sizeof(uint64_t));
+    }
+    else
+    {
+      compressed.resize(size);
+    }
+  }
+
+
+  void GzipCompressor::Uncompress(std::string& uncompressed,
+                                  const void* compressed,
+                                  size_t compressedSize)
+  {
+    uint64_t uncompressedSize;
+    const uint8_t* source = reinterpret_cast<const uint8_t*>(compressed);
+
+    if (HasPrefixWithUncompressedSize())
+    {
+      uncompressedSize = ReadUncompressedSizePrefix(compressed, compressedSize);
+      source += sizeof(uint64_t);
+      compressedSize -= sizeof(uint64_t);
+    }
+    else
+    {
+      uncompressedSize = GuessUncompressedSize(compressed, compressedSize);
+    }
+
+    try
+    {
+      uncompressed.resize(static_cast<size_t>(uncompressedSize));
+    }
+    catch (...)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+
+    z_stream stream;
+    memset(&stream, 0, sizeof(stream));
+
+    char dummy = '\0';  // zlib does not like NULL output buffers (even if the uncompressed data is empty)
+    stream.next_in = const_cast<Bytef*>(source);
+    stream.next_out = reinterpret_cast<Bytef*>(uncompressedSize == 0 ? &dummy : &uncompressed[0]);
+
+    stream.avail_in = static_cast<uInt>(compressedSize);
+    stream.avail_out = static_cast<uInt>(uncompressedSize);
+
+    // Ensure no overflow (if the buffer is too large for the current archicture)
+    if (static_cast<size_t>(stream.avail_in) != compressedSize ||
+        static_cast<size_t>(stream.avail_out) != uncompressedSize)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+
+    // Initialize the compression engine
+    int error = inflateInit2(&stream, 
+                             MAX_WBITS + 16);  // this is a gzip input
+
+    if (error != Z_OK)
+    {
+      // Cannot initialize zlib
+      uncompressed.clear();
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    // Uncompress the input buffer
+    error = inflate(&stream, Z_FINISH);
+
+    if (error != Z_STREAM_END)
+    {
+      inflateEnd(&stream);
+      uncompressed.clear();
+
+      switch (error)
+      {
+        case Z_MEM_ERROR:
+          throw OrthancException(ErrorCode_NotEnoughMemory);
+          
+        case Z_BUF_ERROR:
+        case Z_NEED_DICT:
+          throw OrthancException(ErrorCode_BadFileFormat);
+          
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
+    }
+
+    size_t size = stream.total_out;
+
+    if (inflateEnd(&stream) != Z_OK)
+    {
+      uncompressed.clear();
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    if (size != uncompressedSize)
+    {
+      uncompressed.clear();
+
+      // The uncompressed size was not that properly guess, presumably
+      // because of a file size over 4GB. Should fallback to
+      // stream-based decompression.
+      LOG(ERROR) << "The uncompressed size of a gzip-encoded buffer was not properly guessed";
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Compression/GzipCompressor.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,60 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "DeflateBaseCompressor.h"
+
+namespace Orthanc
+{
+  class GzipCompressor : public DeflateBaseCompressor
+  {
+  private:
+    uint64_t GuessUncompressedSize(const void* compressed,
+                                   size_t compressedSize);
+
+  public:
+    GzipCompressor()
+    {
+      SetPrefixWithUncompressedSize(false);
+    }
+
+    virtual void Compress(std::string& compressed,
+                          const void* uncompressed,
+                          size_t uncompressedSize);
+
+    virtual void Uncompress(std::string& uncompressed,
+                            const void* compressed,
+                            size_t compressedSize);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Compression/HierarchicalZipWriter.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,182 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "HierarchicalZipWriter.h"
+
+#include "../Toolbox.h"
+#include "../OrthancException.h"
+
+#include <boost/lexical_cast.hpp>
+
+namespace Orthanc
+{
+  std::string HierarchicalZipWriter::Index::KeepAlphanumeric(const std::string& source)
+  {
+    std::string result;
+
+    bool lastSpace = false;
+
+    result.reserve(source.size());
+    for (size_t i = 0; i < source.size(); i++)
+    {
+      char c = source[i];
+      if (c == '^')
+        c = ' ';
+
+      if (c <= 127 && 
+          c >= 0)
+      {
+        if (isspace(c)) 
+        {
+          if (!lastSpace)
+          {
+            lastSpace = true;
+            result.push_back(' ');
+          }
+        }
+        else if (isalnum(c) || 
+                 c == '.' || 
+                 c == '_')
+        {
+          result.push_back(c);
+          lastSpace = false;
+        }
+      }
+    }
+
+    return Toolbox::StripSpaces(result);
+  }
+
+  std::string HierarchicalZipWriter::Index::GetCurrentDirectoryPath() const
+  {
+    std::string result;
+
+    Stack::const_iterator it = stack_.begin();
+    ++it;  // Skip the root node (to avoid absolute paths)
+
+    while (it != stack_.end())
+    {
+      result += (*it)->name_ + "/";
+      ++it;
+    }
+
+    return result;
+  }
+
+  std::string HierarchicalZipWriter::Index::EnsureUniqueFilename(const char* filename)
+  {
+    std::string standardized = KeepAlphanumeric(filename);
+
+    Directory& d = *stack_.back();
+    Directory::Content::iterator it = d.content_.find(standardized);
+
+    if (it == d.content_.end())
+    {
+      d.content_[standardized] = 1;
+      return standardized;
+    }
+    else
+    {
+      it->second++;
+      return standardized + "-" + boost::lexical_cast<std::string>(it->second);
+    }    
+  }
+
+  HierarchicalZipWriter::Index::Index()
+  {
+    stack_.push_back(new Directory);
+  }
+
+  HierarchicalZipWriter::Index::~Index()
+  {
+    for (Stack::iterator it = stack_.begin(); it != stack_.end(); ++it)
+    {
+      delete *it;
+    }
+  }
+
+  std::string HierarchicalZipWriter::Index::OpenFile(const char* name)
+  {
+    return GetCurrentDirectoryPath() + EnsureUniqueFilename(name);
+  }
+
+  void HierarchicalZipWriter::Index::OpenDirectory(const char* name)
+  {
+    std::string d = EnsureUniqueFilename(name);
+
+    // Push the new directory onto the stack
+    stack_.push_back(new Directory);
+    stack_.back()->name_ = d;
+  }
+
+  void HierarchicalZipWriter::Index::CloseDirectory()
+  {
+    if (IsRoot())
+    {
+      // Cannot close the root node
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    delete stack_.back();
+    stack_.pop_back();
+  }
+
+
+  HierarchicalZipWriter::HierarchicalZipWriter(const char* path)
+  {
+    writer_.SetOutputPath(path);
+    writer_.Open();
+  }
+
+  HierarchicalZipWriter::~HierarchicalZipWriter()
+  {
+    writer_.Close();
+  }
+
+  void HierarchicalZipWriter::OpenFile(const char* name)
+  {
+    std::string p = indexer_.OpenFile(name);
+    writer_.OpenFile(p.c_str());
+  }
+
+  void HierarchicalZipWriter::OpenDirectory(const char* name)
+  {
+    indexer_.OpenDirectory(name);
+  }
+
+  void HierarchicalZipWriter::CloseDirectory()
+  {
+    indexer_.CloseDirectory();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Compression/HierarchicalZipWriter.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,153 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "ZipWriter.h"
+
+#include <map>
+#include <list>
+#include <boost/lexical_cast.hpp>
+
+#if ORTHANC_BUILD_UNIT_TESTS == 1
+#  include <gtest/gtest_prod.h>
+#endif
+
+namespace Orthanc
+{
+  class HierarchicalZipWriter
+  {
+#if ORTHANC_BUILD_UNIT_TESTS == 1
+    FRIEND_TEST(HierarchicalZipWriter, Index);
+    FRIEND_TEST(HierarchicalZipWriter, Filenames);
+#endif
+
+  private:
+    class Index
+    {
+    private:
+      struct Directory
+      {
+        typedef std::map<std::string, unsigned int>  Content;
+
+        std::string name_;
+        Content  content_;
+      };
+
+      typedef std::list<Directory*> Stack;
+  
+      Stack stack_;
+
+      std::string EnsureUniqueFilename(const char* filename);
+
+    public:
+      Index();
+
+      ~Index();
+
+      bool IsRoot() const
+      {
+        return stack_.size() == 1;
+      }
+
+      std::string OpenFile(const char* name);
+
+      void OpenDirectory(const char* name);
+
+      void CloseDirectory();
+
+      std::string GetCurrentDirectoryPath() const;
+
+      static std::string KeepAlphanumeric(const std::string& source);
+    };
+
+    Index indexer_;
+    ZipWriter writer_;
+
+  public:
+    HierarchicalZipWriter(const char* path);
+
+    ~HierarchicalZipWriter();
+
+    void SetZip64(bool isZip64)
+    {
+      writer_.SetZip64(isZip64);
+    }
+
+    bool IsZip64() const
+    {
+      return writer_.IsZip64();
+    }
+
+    void SetCompressionLevel(uint8_t level)
+    {
+      writer_.SetCompressionLevel(level);
+    }
+
+    uint8_t GetCompressionLevel() const
+    {
+      return writer_.GetCompressionLevel();
+    }
+
+    void SetAppendToExisting(bool append)
+    {
+      writer_.SetAppendToExisting(append);
+    }
+    
+    bool IsAppendToExisting() const
+    {
+      return writer_.IsAppendToExisting();
+    }
+    
+    void OpenFile(const char* name);
+
+    void OpenDirectory(const char* name);
+
+    void CloseDirectory();
+
+    std::string GetCurrentDirectoryPath() const
+    {
+      return indexer_.GetCurrentDirectoryPath();
+    }
+
+    void Write(const char* data, size_t length)
+    {
+      writer_.Write(data, length);
+    }
+
+    void Write(const std::string& data)
+    {
+      writer_.Write(data);
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Compression/IBufferCompressor.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,74 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class IBufferCompressor : public boost::noncopyable
+  {
+  public:
+    virtual ~IBufferCompressor()
+    {
+    }
+
+    virtual void Compress(std::string& compressed,
+                          const void* uncompressed,
+                          size_t uncompressedSize) = 0;
+
+    virtual void Uncompress(std::string& uncompressed,
+                            const void* compressed,
+                            size_t compressedSize) = 0;
+
+    static void Compress(std::string& compressed,
+                         IBufferCompressor& compressor,
+                         const std::string& uncompressed)
+    {
+      compressor.Compress(compressed, 
+                          uncompressed.size() == 0 ? NULL : uncompressed.c_str(), 
+                          uncompressed.size());
+    }
+
+    static void Uncompress(std::string& uncompressed,
+                           IBufferCompressor& compressor,
+                           const std::string& compressed)
+    {
+      compressor.Uncompress(uncompressed, 
+                            compressed.size() == 0 ? NULL : compressed.c_str(), 
+                            compressed.size());
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Compression/ZipWriter.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,259 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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"
+
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+
+#include "ZipWriter.h"
+
+#include <limits>
+#include <boost/filesystem.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include "../../Resources/ThirdParty/minizip/zip.h"
+#include "../OrthancException.h"
+#include "../Logging.h"
+
+
+static void PrepareFileInfo(zip_fileinfo& zfi)
+{
+  memset(&zfi, 0, sizeof(zfi));
+
+  using namespace boost::posix_time;
+  ptime now = second_clock::local_time();
+
+  boost::gregorian::date today = now.date();
+  ptime midnight(today);
+
+  time_duration sinceMidnight = now - midnight;
+  zfi.tmz_date.tm_sec = sinceMidnight.seconds();  // seconds after the minute - [0,59]
+  zfi.tmz_date.tm_min = sinceMidnight.minutes();  // minutes after the hour - [0,59]
+  zfi.tmz_date.tm_hour = sinceMidnight.hours();  // hours since midnight - [0,23]
+
+  // http://www.boost.org/doc/libs/1_35_0/doc/html/boost/gregorian/greg_day.html
+  zfi.tmz_date.tm_mday = today.day();  // day of the month - [1,31]
+
+  // http://www.boost.org/doc/libs/1_35_0/doc/html/boost/gregorian/greg_month.html
+  zfi.tmz_date.tm_mon = today.month() - 1;  // months since January - [0,11]
+
+  // http://www.boost.org/doc/libs/1_35_0/doc/html/boost/gregorian/greg_year.html
+  zfi.tmz_date.tm_year = today.year();  // years - [1980..2044]
+}
+
+
+
+namespace Orthanc
+{
+  struct ZipWriter::PImpl
+  {
+    zipFile file_;
+
+    PImpl() : file_(NULL)
+    {
+    }
+  };
+
+  ZipWriter::ZipWriter() :
+    pimpl_(new PImpl),
+    isZip64_(false),
+    hasFileInZip_(false),
+    append_(false),
+    compressionLevel_(6)
+  {
+  }
+
+  ZipWriter::~ZipWriter()
+  {
+    Close();
+  }
+
+  void ZipWriter::Close()
+  {
+    if (IsOpen())
+    {
+      zipClose(pimpl_->file_, "Created by Orthanc");
+      pimpl_->file_ = NULL;
+      hasFileInZip_ = false;
+    }
+  }
+
+  bool ZipWriter::IsOpen() const
+  {
+    return pimpl_->file_ != NULL;
+  }
+
+  void ZipWriter::Open()
+  {
+    if (IsOpen())
+    {
+      return;
+    }
+
+    if (path_.size() == 0)
+    {
+      LOG(ERROR) << "Please call SetOutputPath() before creating the file";
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    hasFileInZip_ = false;
+
+    int mode = APPEND_STATUS_CREATE;
+    if (append_ && 
+        boost::filesystem::exists(path_))
+    {
+      mode = APPEND_STATUS_ADDINZIP;
+    }
+
+    if (isZip64_)
+    {
+      pimpl_->file_ = zipOpen64(path_.c_str(), mode);
+    }
+    else
+    {
+      pimpl_->file_ = zipOpen(path_.c_str(), mode);
+    }
+
+    if (!pimpl_->file_)
+    {
+      throw OrthancException(ErrorCode_CannotWriteFile);
+    }
+  }
+
+  void ZipWriter::SetOutputPath(const char* path)
+  {
+    Close();
+    path_ = path;
+  }
+
+  void ZipWriter::SetZip64(bool isZip64)
+  {
+    Close();
+    isZip64_ = isZip64;
+  }
+
+  void ZipWriter::SetCompressionLevel(uint8_t level)
+  {
+    if (level >= 10)
+    {
+      LOG(ERROR) << "ZIP compression level must be between 0 (no compression) and 9 (highest compression)";
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    Close();
+    compressionLevel_ = level;
+  }
+
+  void ZipWriter::OpenFile(const char* path)
+  {
+    Open();
+
+    zip_fileinfo zfi;
+    PrepareFileInfo(zfi);
+
+    int result;
+
+    if (isZip64_)
+    {
+      result = zipOpenNewFileInZip64(pimpl_->file_, path,
+                                     &zfi,
+                                     NULL,   0,
+                                     NULL,   0,
+                                     "",  // Comment
+                                     Z_DEFLATED,
+                                     compressionLevel_, 1);
+    }
+    else
+    {
+      result = zipOpenNewFileInZip(pimpl_->file_, path,
+                                   &zfi,
+                                   NULL,   0,
+                                   NULL,   0,
+                                   "",  // Comment
+                                   Z_DEFLATED,
+                                   compressionLevel_);
+    }
+
+    if (result != 0)
+    {
+      throw OrthancException(ErrorCode_CannotWriteFile);
+    }
+
+    hasFileInZip_ = true;
+  }
+
+
+  void ZipWriter::Write(const std::string& data)
+  {
+    if (data.size())
+    {
+      Write(&data[0], data.size());
+    }
+  }
+
+
+  void ZipWriter::Write(const char* data, size_t length)
+  {
+    if (!hasFileInZip_)
+    {
+      LOG(ERROR) << "Call first OpenFile()";
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    const size_t maxBytesInAStep = std::numeric_limits<int32_t>::max();
+
+    while (length > 0)
+    {
+      int bytes = static_cast<int32_t>(length <= maxBytesInAStep ? length : maxBytesInAStep);
+
+      if (zipWriteInFileInZip(pimpl_->file_, data, bytes))
+      {
+        throw OrthancException(ErrorCode_CannotWriteFile);
+      }
+      
+      data += bytes;
+      length -= bytes;
+    }
+  }
+
+
+  void ZipWriter::SetAppendToExisting(bool append)
+  {
+    Close();
+    append_ = append;
+  }
+    
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Compression/ZipWriter.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,99 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 <stdint.h>
+#include <string>
+#include <boost/shared_ptr.hpp>
+
+namespace Orthanc
+{
+  class ZipWriter
+  {
+  private:
+    struct PImpl;
+    boost::shared_ptr<PImpl> pimpl_;
+
+    bool isZip64_;
+    bool hasFileInZip_;
+    bool append_;
+    uint8_t compressionLevel_;
+    std::string path_;
+
+  public:
+    ZipWriter();
+
+    ~ZipWriter();
+
+    void SetZip64(bool isZip64);
+
+    bool IsZip64() const
+    {
+      return isZip64_;
+    }
+
+    void SetCompressionLevel(uint8_t level);
+
+    uint8_t GetCompressionLevel() const
+    {
+      return compressionLevel_;
+    }
+
+    void SetAppendToExisting(bool append);
+    
+    bool IsAppendToExisting() const
+    {
+      return append_;
+    }
+    
+    void Open();
+
+    void Close();
+
+    bool IsOpen() const;
+
+    void SetOutputPath(const char* path);
+
+    const std::string& GetOutputPath() const
+    {
+      return path_;
+    }
+
+    void OpenFile(const char* path);
+
+    void Write(const char* data, size_t length);
+
+    void Write(const std::string& data);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Compression/ZlibCompressor.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,159 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "ZlibCompressor.h"
+
+#include "../OrthancException.h"
+#include "../Logging.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <zlib.h>
+
+namespace Orthanc
+{
+  void ZlibCompressor::Compress(std::string& compressed,
+                                const void* uncompressed,
+                                size_t uncompressedSize)
+  {
+    if (uncompressedSize == 0)
+    {
+      compressed.clear();
+      return;
+    }
+
+    uLongf compressedSize = compressBound(uncompressedSize) + 1024 /* security margin */;
+    if (compressedSize == 0)
+    {
+      compressedSize = 1;
+    }
+
+    uint8_t* target;
+    if (HasPrefixWithUncompressedSize())
+    {
+      compressed.resize(compressedSize + sizeof(uint64_t));
+      target = reinterpret_cast<uint8_t*>(&compressed[0]) + sizeof(uint64_t);
+    }
+    else
+    {
+      compressed.resize(compressedSize);
+      target = reinterpret_cast<uint8_t*>(&compressed[0]);
+    }
+
+    int error = compress2(target,
+                          &compressedSize,
+                          const_cast<Bytef *>(static_cast<const Bytef *>(uncompressed)), 
+                          uncompressedSize,
+                          GetCompressionLevel());
+
+    if (error != Z_OK)
+    {
+      compressed.clear();
+
+      switch (error)
+      {
+      case Z_MEM_ERROR:
+        throw OrthancException(ErrorCode_NotEnoughMemory);
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+      }  
+    }
+
+    // The compression was successful
+    if (HasPrefixWithUncompressedSize())
+    {
+      uint64_t s = static_cast<uint64_t>(uncompressedSize);
+      memcpy(&compressed[0], &s, sizeof(uint64_t));
+      compressed.resize(compressedSize + sizeof(uint64_t));
+    }
+    else
+    {
+      compressed.resize(compressedSize);
+    }
+  }
+
+
+  void ZlibCompressor::Uncompress(std::string& uncompressed,
+                                  const void* compressed,
+                                  size_t compressedSize)
+  {
+    if (compressedSize == 0)
+    {
+      uncompressed.clear();
+      return;
+    }
+
+    if (!HasPrefixWithUncompressedSize())
+    {
+      LOG(ERROR) << "Cannot guess the uncompressed size of a zlib-encoded buffer";
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    uint64_t uncompressedSize = ReadUncompressedSizePrefix(compressed, compressedSize);
+    
+    try
+    {
+      uncompressed.resize(static_cast<size_t>(uncompressedSize));
+    }
+    catch (...)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+
+    uLongf tmp = static_cast<uLongf>(uncompressedSize);
+    int error = uncompress
+      (reinterpret_cast<uint8_t*>(&uncompressed[0]), 
+       &tmp,
+       reinterpret_cast<const uint8_t*>(compressed) + sizeof(uint64_t),
+       compressedSize - sizeof(uint64_t));
+
+    if (error != Z_OK)
+    {
+      uncompressed.clear();
+
+      switch (error)
+      {
+      case Z_DATA_ERROR:
+        throw OrthancException(ErrorCode_CorruptedFile);
+
+      case Z_MEM_ERROR:
+        throw OrthancException(ErrorCode_NotEnoughMemory);
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+      }  
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Compression/ZlibCompressor.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,56 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "DeflateBaseCompressor.h"
+
+namespace Orthanc
+{
+  class ZlibCompressor : public DeflateBaseCompressor
+  {
+  public:
+    ZlibCompressor()
+    {
+      SetPrefixWithUncompressedSize(true);
+    }
+
+    virtual void Compress(std::string& compressed,
+                          const void* uncompressed,
+                          size_t uncompressedSize);
+
+    virtual void Uncompress(std::string& uncompressed,
+                            const void* compressed,
+                            size_t compressedSize);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/DicomFormat/DicomArray.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,72 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomArray.h"
+
+#include <stdio.h>
+
+namespace Orthanc
+{
+  DicomArray::DicomArray(const DicomMap& map)
+  {
+    elements_.reserve(map.map_.size());
+    
+    for (DicomMap::Map::const_iterator it = 
+           map.map_.begin(); it != map.map_.end(); ++it)
+    {
+      elements_.push_back(new DicomElement(it->first, *it->second));
+    }
+  }
+
+
+  DicomArray::~DicomArray()
+  {
+    for (size_t i = 0; i < elements_.size(); i++)
+    {
+      delete elements_[i];
+    }
+  }
+
+
+  void DicomArray::Print(FILE* fp) const
+  {
+    for (size_t  i = 0; i < elements_.size(); i++)
+    {
+      DicomTag t = elements_[i]->GetTag();
+      const DicomValue& v = elements_[i]->GetValue();
+      std::string s = v.IsNull() ? "(null)" : v.GetContent();
+      printf("0x%04x 0x%04x [%s]\n", t.GetGroup(), t.GetElement(), s.c_str());
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/DicomFormat/DicomArray.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,67 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomElement.h"
+#include "DicomMap.h"
+
+#include <vector>
+
+namespace Orthanc
+{
+  class DicomArray : public boost::noncopyable
+  {
+  private:
+    typedef std::vector<DicomElement*>  Elements;
+
+    Elements  elements_;
+
+  public:
+    explicit DicomArray(const DicomMap& map);
+
+    ~DicomArray();
+
+    size_t GetSize() const
+    {
+      return elements_.size();
+    }
+
+    const DicomElement& GetElement(size_t i) const
+    {
+      return *elements_[i];
+    }
+
+    void Print(FILE* fp) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/DicomFormat/DicomElement.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,93 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomValue.h"
+#include "DicomTag.h"
+
+namespace Orthanc
+{
+  class DicomElement : public boost::noncopyable
+  {
+  private:
+    DicomTag tag_;
+    DicomValue* value_;
+
+  public:
+    DicomElement(uint16_t group,
+                 uint16_t element,
+                 const DicomValue& value) :
+      tag_(group, element),
+      value_(value.Clone())
+    {
+    }
+
+    DicomElement(const DicomTag& tag,
+                 const DicomValue& value) :
+      tag_(tag),
+      value_(value.Clone())
+    {
+    }
+
+    ~DicomElement()
+    {
+      delete value_;
+    }
+
+    const DicomTag& GetTag() const
+    {
+      return tag_;
+    }
+
+    const DicomValue& GetValue() const
+    {
+      return *value_;
+    }
+
+    uint16_t GetTagGroup() const
+    {
+      return tag_.GetGroup();
+    }
+
+    uint16_t GetTagElement() const
+    {
+      return tag_.GetElement();
+    }
+
+    bool operator< (const DicomElement& other) const
+    {
+      return GetTag() < other.GetTag();
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/DicomFormat/DicomImageInformation.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,278 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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"
+
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+
+#include "DicomImageInformation.h"
+
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+#include <boost/lexical_cast.hpp>
+#include <limits>
+#include <cassert>
+#include <stdio.h>
+
+namespace Orthanc
+{
+  DicomImageInformation::DicomImageInformation(const DicomMap& values)
+  {
+    unsigned int pixelRepresentation;
+    unsigned int planarConfiguration = 0;
+
+    try
+    {
+      std::string p = values.GetValue(DICOM_TAG_PHOTOMETRIC_INTERPRETATION).GetContent();
+      Toolbox::ToUpperCase(p);
+
+      if (p == "RGB")
+      {
+        photometric_ = PhotometricInterpretation_RGB;
+      }
+      else if (p == "MONOCHROME1")
+      {
+        photometric_ = PhotometricInterpretation_Monochrome1;
+      }
+      else if (p == "MONOCHROME2")
+      {
+        photometric_ = PhotometricInterpretation_Monochrome2;
+      }
+      else if (p == "PALETTE COLOR")
+      {
+        photometric_ = PhotometricInterpretation_Palette;
+      }
+      else if (p == "HSV")
+      {
+        photometric_ = PhotometricInterpretation_HSV;
+      }
+      else if (p == "ARGB")
+      {
+        photometric_ = PhotometricInterpretation_ARGB;
+      }
+      else if (p == "CMYK")
+      {
+        photometric_ = PhotometricInterpretation_CMYK;
+      }
+      else if (p == "YBR_FULL")
+      {
+        photometric_ = PhotometricInterpretation_YBRFull;
+      }
+      else if (p == "YBR_FULL_422")
+      {
+        photometric_ = PhotometricInterpretation_YBRFull422;
+      }
+      else if (p == "YBR_PARTIAL_420")
+      {
+        photometric_ = PhotometricInterpretation_YBRPartial420;
+      }
+      else if (p == "YBR_PARTIAL_422")
+      {
+        photometric_ = PhotometricInterpretation_YBRPartial422;
+      }
+      else if (p == "YBR_ICT")
+      {
+        photometric_ = PhotometricInterpretation_YBR_ICT;
+      }
+      else if (p == "YBR_RCT")
+      {
+        photometric_ = PhotometricInterpretation_YBR_RCT;
+      }
+      else
+      {
+        photometric_ = PhotometricInterpretation_Unknown;
+      }
+
+      width_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_COLUMNS).GetContent());
+      height_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_ROWS).GetContent());
+      bitsAllocated_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_BITS_ALLOCATED).GetContent());
+
+      try
+      {
+        samplesPerPixel_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_SAMPLES_PER_PIXEL).GetContent());
+      }
+      catch (OrthancException&)
+      {
+        samplesPerPixel_ = 1;  // Assume 1 color channel
+      }
+
+      try
+      {
+        bitsStored_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_BITS_STORED).GetContent());
+      }
+      catch (OrthancException&)
+      {
+        bitsStored_ = bitsAllocated_;
+      }
+
+      try
+      {
+        highBit_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_HIGH_BIT).GetContent());
+      }
+      catch (OrthancException&)
+      {
+        highBit_ = bitsStored_ - 1;
+      }
+
+      try
+      {
+        pixelRepresentation = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_PIXEL_REPRESENTATION).GetContent());
+      }
+      catch (OrthancException&)
+      {
+        pixelRepresentation = 0;  // Assume unsigned pixels
+      }
+
+      if (samplesPerPixel_ > 1)
+      {
+        // The "Planar Configuration" is only set when "Samples per Pixels" is greater than 1
+        // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.7.6.3.1.3
+        try
+        {
+          planarConfiguration = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_PLANAR_CONFIGURATION).GetContent());
+        }
+        catch (OrthancException&)
+        {
+          planarConfiguration = 0;  // Assume interleaved color channels
+        }
+      }
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+    catch (OrthancException&)
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    if (values.HasTag(DICOM_TAG_NUMBER_OF_FRAMES))
+    {
+      try
+      {
+        numberOfFrames_ = boost::lexical_cast<unsigned int>(values.GetValue(DICOM_TAG_NUMBER_OF_FRAMES).GetContent());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        throw OrthancException(ErrorCode_NotImplemented);
+      }
+    }
+    else
+    {
+      numberOfFrames_ = 1;
+    }
+
+    if ((bitsAllocated_ != 8 && bitsAllocated_ != 16 && 
+         bitsAllocated_ != 24 && bitsAllocated_ != 32) ||
+        numberOfFrames_ == 0 ||
+        (planarConfiguration != 0 && planarConfiguration != 1))
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    if (samplesPerPixel_ == 0)
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    bytesPerValue_ = bitsAllocated_ / 8;
+
+    isPlanar_ = (planarConfiguration != 0 ? true : false);
+    isSigned_ = (pixelRepresentation != 0 ? true : false);
+  }
+
+
+  bool DicomImageInformation::ExtractPixelFormat(PixelFormat& format,
+                                                 bool ignorePhotometricInterpretation) const
+  {
+    if (photometric_ == PhotometricInterpretation_Palette)
+    {
+      if (GetBitsStored() == 8 && GetChannelCount() == 1 && !IsSigned())
+      {
+        format = PixelFormat_RGB24;
+        return true;
+      }
+
+      if (GetBitsStored() == 16 && GetChannelCount() == 1 && !IsSigned())
+      {
+        format = PixelFormat_RGB48;
+        return true;
+      }
+    }
+    
+    if (ignorePhotometricInterpretation ||
+        photometric_ == PhotometricInterpretation_Monochrome1 ||
+        photometric_ == PhotometricInterpretation_Monochrome2)
+    {
+      if (GetBitsStored() == 8 && GetChannelCount() == 1 && !IsSigned())
+      {
+        format = PixelFormat_Grayscale8;
+        return true;
+      }
+      
+      if (GetBitsAllocated() == 16 && GetChannelCount() == 1 && !IsSigned())
+      {
+        format = PixelFormat_Grayscale16;
+        return true;
+      }
+
+      if (GetBitsAllocated() == 16 && GetChannelCount() == 1 && IsSigned())
+      {
+        format = PixelFormat_SignedGrayscale16;
+        return true;
+      }
+    }
+
+    if (GetBitsStored() == 8 && 
+        GetChannelCount() == 3 && 
+        !IsSigned() &&
+        (ignorePhotometricInterpretation || photometric_ == PhotometricInterpretation_RGB))
+    {
+      format = PixelFormat_RGB24;
+      return true;
+    }
+
+    return false;
+  }
+
+
+  size_t DicomImageInformation::GetFrameSize() const
+  {
+    return (GetHeight() * 
+            GetWidth() * 
+            GetBytesPerValue() * 
+            GetChannelCount());
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/DicomFormat/DicomImageInformation.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,128 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomMap.h"
+
+#include <stdint.h>
+
+namespace Orthanc
+{
+  class DicomImageInformation
+  {
+  private:
+    unsigned int width_;
+    unsigned int height_;
+    unsigned int samplesPerPixel_;
+    unsigned int numberOfFrames_;
+
+    bool isPlanar_;
+    bool isSigned_;
+    size_t bytesPerValue_;
+
+    unsigned int bitsAllocated_;
+    unsigned int bitsStored_;
+    unsigned int highBit_;
+
+    PhotometricInterpretation  photometric_;
+
+  public:
+    explicit DicomImageInformation(const DicomMap& values);
+
+    unsigned int GetWidth() const
+    {
+      return width_;
+    }
+
+    unsigned int GetHeight() const
+    {
+      return height_;
+    }
+
+    unsigned int GetNumberOfFrames() const
+    {
+      return numberOfFrames_;
+    }
+
+    unsigned int GetChannelCount() const
+    {
+      return samplesPerPixel_;
+    }
+
+    unsigned int GetBitsStored() const
+    {
+      return bitsStored_;
+    }
+
+    size_t GetBytesPerValue() const
+    {
+      return bytesPerValue_;
+    }
+
+    bool IsSigned() const
+    {
+      return isSigned_;
+    }
+
+    unsigned int GetBitsAllocated() const
+    {
+      return bitsAllocated_;
+    }
+
+    unsigned int GetHighBit() const
+    {
+      return highBit_;
+    }
+
+    bool IsPlanar() const
+    {
+      return isPlanar_;
+    }
+
+    unsigned int GetShift() const
+    {
+      return highBit_ + 1 - bitsStored_;
+    }
+
+    PhotometricInterpretation GetPhotometricInterpretation() const
+    {
+      return photometric_;
+    }
+
+    bool ExtractPixelFormat(PixelFormat& format,
+                            bool ignorePhotometricInterpretation) const;
+
+    size_t GetFrameSize() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/DicomFormat/DicomInstanceHasher.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,109 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomInstanceHasher.h"
+
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+
+namespace Orthanc
+{
+  void DicomInstanceHasher::Setup(const std::string& patientId,
+                                  const std::string& studyUid,
+                                  const std::string& seriesUid,
+                                  const std::string& instanceUid)
+  {
+    patientId_ = patientId;
+    studyUid_ = studyUid;
+    seriesUid_ = seriesUid;
+    instanceUid_ = instanceUid;
+
+    if (studyUid_.size() == 0 ||
+        seriesUid_.size() == 0 ||
+        instanceUid_.size() == 0)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+
+  DicomInstanceHasher::DicomInstanceHasher(const DicomMap& instance)
+  {
+    const DicomValue* patientId = instance.TestAndGetValue(DICOM_TAG_PATIENT_ID);
+
+    Setup(patientId == NULL ? "" : patientId->GetContent(),
+          instance.GetValue(DICOM_TAG_STUDY_INSTANCE_UID).GetContent(),
+          instance.GetValue(DICOM_TAG_SERIES_INSTANCE_UID).GetContent(),
+          instance.GetValue(DICOM_TAG_SOP_INSTANCE_UID).GetContent());
+  }
+
+  const std::string& DicomInstanceHasher::HashPatient()
+  {
+    if (patientHash_.size() == 0)
+    {
+      Toolbox::ComputeSHA1(patientHash_, patientId_);
+    }
+
+    return patientHash_;
+  }
+
+  const std::string& DicomInstanceHasher::HashStudy()
+  {
+    if (studyHash_.size() == 0)
+    {
+      Toolbox::ComputeSHA1(studyHash_, patientId_ + "|" + studyUid_);
+    }
+
+    return studyHash_;
+  }
+
+  const std::string& DicomInstanceHasher::HashSeries()
+  {
+    if (seriesHash_.size() == 0)
+    {
+      Toolbox::ComputeSHA1(seriesHash_, patientId_ + "|" + studyUid_ + "|" + seriesUid_);
+    }
+
+    return seriesHash_;
+  }
+
+  const std::string& DicomInstanceHasher::HashInstance()
+  {
+    if (instanceHash_.size() == 0)
+    {
+      Toolbox::ComputeSHA1(instanceHash_, patientId_ + "|" + studyUid_ + "|" + seriesUid_ + "|" + instanceUid_);
+    }
+
+    return instanceHash_;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/DicomFormat/DicomInstanceHasher.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,107 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomMap.h"
+
+namespace Orthanc
+{
+  /**
+   * This class implements the hashing mechanism that is used to
+   * convert DICOM unique identifiers to Orthanc identifiers. Any
+   * Orthanc identifier for a DICOM resource corresponds to the SHA-1
+   * hash of the DICOM identifiers. 
+
+   * \note SHA-1 hash is used because it is less sensitive to
+   * collision attacks than MD5. <a
+   * href="http://en.wikipedia.org/wiki/SHA-256#Comparison_of_SHA_functions">[Reference]</a>
+   **/
+  class DicomInstanceHasher
+  {
+  private:
+    std::string patientId_;
+    std::string studyUid_;
+    std::string seriesUid_;
+    std::string instanceUid_;
+
+    std::string patientHash_;
+    std::string studyHash_;
+    std::string seriesHash_;
+    std::string instanceHash_;
+
+    void Setup(const std::string& patientId,
+               const std::string& studyUid,
+               const std::string& seriesUid,
+               const std::string& instanceUid);
+
+  public:
+    DicomInstanceHasher(const DicomMap& instance);
+
+    DicomInstanceHasher(const std::string& patientId,
+                        const std::string& studyUid,
+                        const std::string& seriesUid,
+                        const std::string& instanceUid)
+    {
+      Setup(patientId, studyUid, seriesUid, instanceUid);
+    }
+
+    const std::string& GetPatientId() const
+    {
+      return patientId_;
+    }
+
+    const std::string& GetStudyUid() const
+    {
+      return studyUid_;
+    }
+
+    const std::string& GetSeriesUid() const
+    {
+      return seriesUid_;
+    }
+
+    const std::string& GetInstanceUid() const
+    {
+      return instanceUid_;
+    }
+
+    const std::string& HashPatient();
+
+    const std::string& HashStudy();
+
+    const std::string& HashSeries();
+
+    const std::string& HashInstance();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/DicomFormat/DicomIntegerPixelAccessor.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,204 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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"
+
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+
+#include "DicomIntegerPixelAccessor.h"
+
+#include "../OrthancException.h"
+#include <boost/lexical_cast.hpp>
+#include <limits>
+#include <cassert>
+#include <stdio.h>
+
+namespace Orthanc
+{
+  DicomIntegerPixelAccessor::DicomIntegerPixelAccessor(const DicomMap& values,
+                                                       const void* pixelData,
+                                                       size_t size) :
+    information_(values),
+    pixelData_(pixelData),
+    size_(size)
+  {
+    if (information_.GetBitsAllocated() > 32 ||
+        information_.GetBitsStored() >= 32)
+    {
+      // Not available, as the accessor internally uses int32_t values
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    frame_ = 0;
+    frameOffset_ = information_.GetFrameSize();
+
+    if (information_.GetNumberOfFrames() * frameOffset_ > size)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    if (information_.IsSigned())
+    {
+      // Pixels are signed
+      mask_ = (1 << (information_.GetBitsStored() - 1)) - 1;
+      signMask_ = (1 << (information_.GetBitsStored() - 1));
+    }
+    else
+    {
+      // Pixels are unsigned
+      mask_ = (1 << information_.GetBitsStored()) - 1;
+      signMask_ = 0;
+    }
+
+    if (information_.IsPlanar())
+    {
+      /**
+       * Each color plane shall be sent contiguously. For RGB images,
+       * this means the order of the pixel values sent is R1, R2, R3,
+       * ..., G1, G2, G3, ..., B1, B2, B3, etc.
+       **/
+      rowOffset_ = information_.GetWidth() * information_.GetBytesPerValue();
+    }
+    else
+    {
+      /**
+       * The sample values for the first pixel are followed by the
+       * sample values for the second pixel, etc. For RGB images, this
+       * means the order of the pixel values sent shall be R1, G1, B1,
+       * R2, G2, B2, ..., etc.
+       **/
+      rowOffset_ = information_.GetWidth() * information_.GetBytesPerValue() * information_.GetChannelCount();
+    }
+  }
+
+
+  void DicomIntegerPixelAccessor::GetExtremeValues(int32_t& min, 
+                                                   int32_t& max) const
+  {
+    if (information_.GetHeight() == 0 || information_.GetWidth() == 0)
+    {
+      min = max = 0;
+      return;
+    }
+
+    min = std::numeric_limits<int32_t>::max();
+    max = std::numeric_limits<int32_t>::min();
+    
+    for (unsigned int y = 0; y < information_.GetHeight(); y++)
+    {
+      for (unsigned int x = 0; x < information_.GetWidth(); x++)
+      {
+        for (unsigned int c = 0; c < information_.GetChannelCount(); c++)
+        {
+          int32_t v = GetValue(x, y);
+          if (v < min)
+            min = v;
+          if (v > max)
+            max = v;
+        }
+      }
+    }
+  }
+
+
+  int32_t DicomIntegerPixelAccessor::GetValue(unsigned int x, 
+                                              unsigned int y,
+                                              unsigned int channel) const
+  {
+    assert(x < information_.GetWidth() && 
+           y < information_.GetHeight() && 
+           channel < information_.GetChannelCount());
+    
+    const uint8_t* pixel = reinterpret_cast<const uint8_t*>(pixelData_) + 
+      y * rowOffset_ + frame_ * frameOffset_;
+
+    // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.7.6.3.1.3
+    if (information_.IsPlanar())
+    {
+      /**
+       * Each color plane shall be sent contiguously. For RGB images,
+       * this means the order of the pixel values sent is R1, R2, R3,
+       * ..., G1, G2, G3, ..., B1, B2, B3, etc.
+       **/
+      assert(frameOffset_ % information_.GetChannelCount() == 0);
+      pixel += channel * frameOffset_ / information_.GetChannelCount() + x * information_.GetBytesPerValue();
+    }
+    else
+    {
+      /**
+       * The sample values for the first pixel are followed by the
+       * sample values for the second pixel, etc. For RGB images, this
+       * means the order of the pixel values sent shall be R1, G1, B1,
+       * R2, G2, B2, ..., etc.
+       **/
+      pixel += channel * information_.GetBytesPerValue() + x * information_.GetChannelCount() * information_.GetBytesPerValue();
+    }
+
+    uint32_t v;
+    v = pixel[0];
+    if (information_.GetBytesPerValue() >= 2)
+      v = v + (static_cast<uint32_t>(pixel[1]) << 8);
+    if (information_.GetBytesPerValue() >= 3)
+      v = v + (static_cast<uint32_t>(pixel[2]) << 16);
+    if (information_.GetBytesPerValue() >= 4)
+      v = v + (static_cast<uint32_t>(pixel[3]) << 24);
+
+    v = v >> information_.GetShift();
+
+    if (v & signMask_)
+    {
+      // Signed value
+      // http://en.wikipedia.org/wiki/Two%27s_complement#Subtraction_from_2N
+      return -static_cast<int32_t>(mask_) + static_cast<int32_t>(v & mask_) - 1;
+    }
+    else
+    {
+      // Unsigned value
+      return static_cast<int32_t>(v & mask_);
+    }
+  }
+
+
+  void DicomIntegerPixelAccessor::SetCurrentFrame(unsigned int frame)
+  {
+    if (frame >= information_.GetNumberOfFrames())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    frame_ = frame;
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/DicomFormat/DicomIntegerPixelAccessor.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,90 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomMap.h"
+
+#include "DicomImageInformation.h"
+
+#include <stdint.h>
+
+namespace Orthanc
+{
+  class DicomIntegerPixelAccessor
+  {
+  private:
+    DicomImageInformation information_;
+
+    uint32_t signMask_;
+    uint32_t mask_;
+
+    const void* pixelData_;
+    size_t size_;
+    unsigned int frame_;
+    size_t frameOffset_;
+    size_t rowOffset_;
+
+  public:
+    DicomIntegerPixelAccessor(const DicomMap& values,
+                              const void* pixelData,
+                              size_t size);
+
+    const DicomImageInformation GetInformation() const
+    {
+      return information_;
+    }
+
+    unsigned int GetCurrentFrame() const
+    {
+      return frame_;
+    }
+
+    void SetCurrentFrame(unsigned int frame);
+
+    void GetExtremeValues(int32_t& min, 
+                          int32_t& max) const;
+
+    int32_t GetValue(unsigned int x, unsigned int y, unsigned int channel = 0) const;
+
+    const void* GetPixelData() const
+    {
+      return pixelData_;
+    }
+
+    size_t GetSize() const
+    {
+      return size_;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/DicomFormat/DicomMap.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,975 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomMap.h"
+
+#include <stdio.h>
+#include <memory>
+
+#include "../Endianness.h"
+#include "../Logging.h"
+#include "../OrthancException.h"
+
+
+namespace Orthanc
+{
+  static DicomTag patientTags[] =
+  {
+    //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
+  };
+
+  static DicomTag studyTags[] =
+  {
+    //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
+  };
+
+  static DicomTag seriesTags[] =
+  {
+    //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
+  };
+
+  static DicomTag instanceTags[] =
+  {
+    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
+  };
+
+
+  void DicomMap::LoadMainDicomTags(const DicomTag*& tags,
+                                   size_t& size,
+                                   ResourceType level)
+  {
+    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);
+    }
+  }
+
+
+  void DicomMap::SetValue(uint16_t group, 
+                          uint16_t element, 
+                          DicomValue* value)
+  {
+    DicomTag tag(group, element);
+    Map::iterator it = map_.find(tag);
+
+    if (it != map_.end())
+    {
+      delete it->second;
+      it->second = value;
+    }
+    else
+    {
+      map_.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)
+    {
+      delete it->second;
+    }
+
+    map_.clear();
+  }
+
+
+  void DicomMap::ExtractTags(DicomMap& result,
+                             const DicomTag* tags,
+                             size_t count) const
+  {
+    result.Clear();
+
+    for (unsigned int i = 0; i < count; i++)
+    {
+      Map::const_iterator it = map_.find(tags[i]);
+      if (it != map_.end())
+      {
+        result.SetValue(it->first, it->second->Clone());
+      }
+    }
+  }
+
+
+  void DicomMap::ExtractPatientInformation(DicomMap& result) const
+  {
+    ExtractTags(result, patientTags, sizeof(patientTags) / sizeof(DicomTag));
+  }
+
+  void DicomMap::ExtractStudyInformation(DicomMap& result) const
+  {
+    ExtractTags(result, studyTags, sizeof(studyTags) / sizeof(DicomTag));
+  }
+
+  void DicomMap::ExtractSeriesInformation(DicomMap& result) const
+  {
+    ExtractTags(result, seriesTags, sizeof(seriesTags) / sizeof(DicomTag));
+  }
+
+  void DicomMap::ExtractInstanceInformation(DicomMap& result) const
+  {
+    ExtractTags(result, instanceTags, sizeof(instanceTags) / sizeof(DicomTag));
+  }
+
+
+
+  DicomMap* DicomMap::Clone() const
+  {
+    std::auto_ptr<DicomMap> result(new DicomMap);
+
+    for (Map::const_iterator it = map_.begin(); it != map_.end(); ++it)
+    {
+      result->map_.insert(std::make_pair(it->first, it->second->Clone()));
+    }
+
+    return result.release();
+  }
+
+
+  void DicomMap::Assign(const DicomMap& other)
+  {
+    Clear();
+
+    for (Map::const_iterator it = other.map_.begin(); it != other.map_.end(); ++it)
+    {
+      map_.insert(std::make_pair(it->first, it->second->Clone()));
+    }
+  }
+
+
+  const DicomValue& DicomMap::GetValue(const DicomTag& tag) const
+  {
+    const DicomValue* value = TestAndGetValue(tag);
+
+    if (value)
+    {
+      return *value;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_InexistentTag);
+    }
+  }
+
+
+  const DicomValue* DicomMap::TestAndGetValue(const DicomTag& tag) const
+  {
+    Map::const_iterator it = map_.find(tag);
+
+    if (it == map_.end())
+    {
+      return NULL;
+    }
+    else
+    {
+      return it->second;
+    }
+  }
+
+
+  void DicomMap::Remove(const DicomTag& tag) 
+  {
+    Map::iterator it = map_.find(tag);
+    if (it != map_.end())
+    {
+      delete it->second;
+      map_.erase(it);
+    }
+  }
+
+
+  static void SetupFindTemplate(DicomMap& result,
+                                const DicomTag* tags,
+                                size_t count) 
+  {
+    result.Clear();
+
+    for (size_t i = 0; i < count; i++)
+    {
+      result.SetValue(tags[i], "", false);
+    }
+  }
+
+  void DicomMap::SetupFindPatientTemplate(DicomMap& result)
+  {
+    SetupFindTemplate(result, patientTags, sizeof(patientTags) / sizeof(DicomTag));
+  }
+
+  void DicomMap::SetupFindStudyTemplate(DicomMap& result)
+  {
+    SetupFindTemplate(result, studyTags, sizeof(studyTags) / sizeof(DicomTag));
+    result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false);
+    result.SetValue(DICOM_TAG_PATIENT_ID, "", false);
+
+    // These main DICOM tags are only indirectly related to the
+    // General Study Module, remove them
+    result.Remove(DICOM_TAG_INSTITUTION_NAME);
+    result.Remove(DICOM_TAG_REQUESTING_PHYSICIAN);
+    result.Remove(DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION);
+  }
+
+  void DicomMap::SetupFindSeriesTemplate(DicomMap& result)
+  {
+    SetupFindTemplate(result, seriesTags, sizeof(seriesTags) / sizeof(DicomTag));
+    result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false);
+    result.SetValue(DICOM_TAG_PATIENT_ID, "", false);
+    result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false);
+
+    // These tags are considered as "main" by Orthanc, but are not in the Series module
+    result.Remove(DicomTag(0x0008, 0x0070));  // Manufacturer
+    result.Remove(DicomTag(0x0008, 0x1010));  // Station name
+    result.Remove(DicomTag(0x0018, 0x0024));  // Sequence name
+    result.Remove(DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES);
+    result.Remove(DICOM_TAG_IMAGES_IN_ACQUISITION);
+    result.Remove(DICOM_TAG_NUMBER_OF_SLICES);
+    result.Remove(DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS);
+    result.Remove(DICOM_TAG_NUMBER_OF_TIME_SLICES);
+    result.Remove(DICOM_TAG_IMAGE_ORIENTATION_PATIENT);
+    result.Remove(DICOM_TAG_SERIES_TYPE);
+    result.Remove(DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION);
+    result.Remove(DICOM_TAG_CONTRAST_BOLUS_AGENT);
+  }
+
+  void DicomMap::SetupFindInstanceTemplate(DicomMap& result)
+  {
+    SetupFindTemplate(result, instanceTags, sizeof(instanceTags) / sizeof(DicomTag));
+    result.SetValue(DICOM_TAG_ACCESSION_NUMBER, "", false);
+    result.SetValue(DICOM_TAG_PATIENT_ID, "", false);
+    result.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "", false);
+    result.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "", false);
+  }
+
+
+  void DicomMap::CopyTagIfExists(const DicomMap& source,
+                                 const DicomTag& tag)
+  {
+    if (source.HasTag(tag))
+    {
+      SetValue(tag, source.GetValue(tag));
+    }
+  }
+
+
+  bool DicomMap::IsMainDicomTag(const DicomTag& tag, ResourceType level)
+  {
+    DicomTag *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);
+    }
+
+    for (size_t i = 0; i < size; i++)
+    {
+      if (tags[i] == tag)
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  bool DicomMap::IsMainDicomTag(const DicomTag& tag)
+  {
+    return (IsMainDicomTag(tag, ResourceType_Patient) ||
+            IsMainDicomTag(tag, ResourceType_Study) ||
+            IsMainDicomTag(tag, ResourceType_Series) ||
+            IsMainDicomTag(tag, ResourceType_Instance));
+  }
+
+
+  void DicomMap::GetMainDicomTagsInternal(std::set<DicomTag>& result, ResourceType level)
+  {
+    DicomTag *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);
+    }
+
+    for (size_t i = 0; i < size; i++)
+    {
+      result.insert(tags[i]);
+    }
+  }
+
+
+  void DicomMap::GetMainDicomTags(std::set<DicomTag>& result, ResourceType level)
+  {
+    result.clear();
+    GetMainDicomTagsInternal(result, level);
+  }
+
+
+  void DicomMap::GetMainDicomTags(std::set<DicomTag>& result)
+  {
+    result.clear();
+    GetMainDicomTagsInternal(result, ResourceType_Patient);
+    GetMainDicomTagsInternal(result, ResourceType_Study);
+    GetMainDicomTagsInternal(result, ResourceType_Series);
+    GetMainDicomTagsInternal(result, ResourceType_Instance);
+  }
+
+
+  void DicomMap::GetTags(std::set<DicomTag>& tags) const
+  {
+    tags.clear();
+
+    for (Map::const_iterator it = map_.begin();
+         it != map_.end(); ++it)
+    {
+      tags.insert(it->first);
+    }
+  }
+
+
+  static uint16_t ReadUnsignedInteger16(const char* dicom)
+  {
+    return le16toh(*reinterpret_cast<const uint16_t*>(dicom));
+  }
+
+
+  static uint32_t ReadUnsignedInteger32(const char* dicom)
+  {
+    return le32toh(*reinterpret_cast<const uint32_t*>(dicom));
+  }
+
+
+  static bool ValidateTag(const ValueRepresentation& vr,
+                          const std::string& value)
+  {
+    switch (vr)
+    {
+      case ValueRepresentation_ApplicationEntity:
+        return value.size() <= 16;
+
+      case ValueRepresentation_AgeString:
+        return (value.size() == 4 &&
+                isdigit(value[0]) &&
+                isdigit(value[1]) &&
+                isdigit(value[2]) &&
+                (value[3] == 'D' || value[3] == 'W' || value[3] == 'M' || value[3] == 'Y'));
+
+      case ValueRepresentation_AttributeTag:
+        return value.size() == 4;
+
+      case ValueRepresentation_CodeString:
+        return value.size() <= 16;
+
+      case ValueRepresentation_Date:
+        return value.size() <= 18;
+
+      case ValueRepresentation_DecimalString:
+        return value.size() <= 16;
+
+      case ValueRepresentation_DateTime:
+        return value.size() <= 54;
+
+      case ValueRepresentation_FloatingPointSingle:
+        return value.size() == 4;
+
+      case ValueRepresentation_FloatingPointDouble:
+        return value.size() == 8;
+
+      case ValueRepresentation_IntegerString:
+        return value.size() <= 12;
+
+      case ValueRepresentation_LongString:
+        return value.size() <= 64;
+
+      case ValueRepresentation_LongText:
+        return value.size() <= 10240;
+
+      case ValueRepresentation_OtherByte:
+        return true;
+      
+      case ValueRepresentation_OtherDouble:
+        return value.size() <= (static_cast<uint64_t>(1) << 32) - 8;
+
+      case ValueRepresentation_OtherFloat:
+        return value.size() <= (static_cast<uint64_t>(1) << 32) - 4;
+
+      case ValueRepresentation_OtherLong:
+        return true;
+
+      case ValueRepresentation_OtherWord:
+        return true;
+
+      case ValueRepresentation_PersonName:
+        return true;
+
+      case ValueRepresentation_ShortString:
+        return value.size() <= 16;
+
+      case ValueRepresentation_SignedLong:
+        return value.size() == 4;
+
+      case ValueRepresentation_Sequence:
+        return true;
+
+      case ValueRepresentation_SignedShort:
+        return value.size() == 2;
+
+      case ValueRepresentation_ShortText:
+        return value.size() <= 1024;
+
+      case ValueRepresentation_Time:
+        return value.size() <= 28;
+
+      case ValueRepresentation_UnlimitedCharacters:
+        return value.size() <= (static_cast<uint64_t>(1) << 32) - 2;
+
+      case ValueRepresentation_UniqueIdentifier:
+        return value.size() <= 64;
+
+      case ValueRepresentation_UnsignedLong:
+        return value.size() == 4;
+
+      case ValueRepresentation_Unknown:
+        return true;
+
+      case ValueRepresentation_UniversalResource:
+        return value.size() <= (static_cast<uint64_t>(1) << 32) - 2;
+
+      case ValueRepresentation_UnsignedShort:
+        return value.size() == 2;
+
+      case ValueRepresentation_UnlimitedText:
+        return value.size() <= (static_cast<uint64_t>(1) << 32) - 2;
+
+      default:
+        // Assume unsupported tags are OK
+        return true;
+    }
+  }
+
+
+  static void RemoveTagPadding(std::string& value,
+                               const ValueRepresentation& vr)
+  {
+    /**
+     * Remove padding from character strings, if need be. For the time
+     * being, only the UI VR is supported.
+     * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html
+     **/
+
+    switch (vr)
+    {
+      case ValueRepresentation_UniqueIdentifier:
+      {
+        /**
+         * "Values with a VR of UI shall be padded with a single
+         * trailing NULL (00H) character when necessary to achieve even
+         * length."
+         **/
+
+        if (!value.empty() &&
+            value[value.size() - 1] == '\0')
+        {
+          value.resize(value.size() - 1);
+        }
+
+        break;
+      }
+
+      /**
+       * TODO implement other VR
+       **/
+
+      default:
+        // No padding is applicable to this VR
+        break;
+    }
+  }
+
+
+  static bool ReadNextTag(DicomTag& tag,
+                          ValueRepresentation& vr,
+                          std::string& value,
+                          const char* dicom,
+                          size_t size,
+                          size_t& position)
+  {
+    /**
+     * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/chapter_7.html#sect_7.1.2
+     * This function reads a data element with Explicit VR encoded using Little-Endian.
+     **/
+
+    if (position + 6 > size)
+    {
+      return false;
+    }
+
+    tag = DicomTag(ReadUnsignedInteger16(dicom + position),
+                   ReadUnsignedInteger16(dicom + position + 2));
+
+    vr = StringToValueRepresentation(std::string(dicom + position + 4, 2), true);
+    if (vr == ValueRepresentation_NotSupported)
+    {
+      return false;
+    }
+
+    if (vr == ValueRepresentation_OtherByte ||
+        vr == ValueRepresentation_OtherDouble ||
+        vr == ValueRepresentation_OtherFloat ||
+        vr == ValueRepresentation_OtherLong ||
+        vr == ValueRepresentation_OtherWord ||
+        vr == ValueRepresentation_Sequence ||
+        vr == ValueRepresentation_UnlimitedCharacters ||
+        vr == ValueRepresentation_UniversalResource ||
+        vr == ValueRepresentation_UnlimitedText ||
+        vr == ValueRepresentation_Unknown)    // Note that "UN" should never appear in the Meta Information
+    {
+      if (position + 12 > size)
+      {
+        return false;
+      }
+
+      uint32_t length = ReadUnsignedInteger32(dicom + position + 8);
+
+      if (position + 12 + length > size)
+      {
+        return false;
+      }
+
+      value.assign(dicom + position + 12, length);
+      position += (12 + length);
+    }
+    else
+    {
+      if (position + 8 > size)
+      {
+        return false;
+      }
+
+      uint16_t length = ReadUnsignedInteger16(dicom + position + 6);
+
+      if (position + 8 + length > size)
+      {
+        return false;
+      }
+
+      value.assign(dicom + position + 8, length);
+      position += (8 + length);
+    }
+
+    if (!ValidateTag(vr, value))
+    {
+      return false;
+    }
+
+    RemoveTagPadding(value, vr);
+
+    return true;
+  }
+
+
+  bool DicomMap::ParseDicomMetaInformation(DicomMap& result,
+                                           const char* dicom,
+                                           size_t size)
+  {
+    /**
+     * http://dicom.nema.org/medical/dicom/current/output/chtml/part10/chapter_7.html
+     * According to Table 7.1-1, besides the "DICM" DICOM prefix, the
+     * file preamble (i.e. dicom[0..127]) should not be taken into
+     * 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 false;
+    }
+
+
+    /**
+     * The DICOM File Meta Information must be encoded using the
+     * Explicit VR Little Endian Transfer Syntax
+     * (UID=1.2.840.10008.1.2.1).
+     **/
+
+    result.Clear();
+
+    // First, we read the "File Meta Information Group Length" tag
+    // (0002,0000) to know where to stop reading the meta header
+    size_t position = 132;
+
+    DicomTag tag(0x0000, 0x0000);  // Dummy initialization
+    ValueRepresentation vr;
+    std::string value;
+    if (!ReadNextTag(tag, vr, value, dicom, size, position) ||
+        tag.GetGroup() != 0x0002 ||
+        tag.GetElement() != 0x0000 ||
+        vr != ValueRepresentation_UnsignedLong ||
+        value.size() != 4)
+    {
+      return false;
+    }
+
+    size_t stopPosition = position + ReadUnsignedInteger32(value.c_str());
+    if (stopPosition > size)
+    {
+      return false;
+    }
+
+    while (position < stopPosition)
+    {
+      if (ReadNextTag(tag, vr, value, dicom, size, position))
+      {
+        result.SetValue(tag, value, IsBinaryValueRepresentation(vr));
+      }
+      else
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+  static std::string ValueAsString(const DicomMap& summary,
+                                   const DicomTag& tag)
+  {
+    const DicomValue& value = summary.GetValue(tag);
+    if (value.IsNull())
+    {
+      return "(null)";
+    }
+    else
+    {
+      return value.GetContent();
+    }
+  }
+
+
+  void DicomMap::LogMissingTagsForStore() const
+  {
+    std::string s, t;
+
+    if (HasTag(DICOM_TAG_PATIENT_ID))
+    {
+      if (t.size() > 0)
+        t += ", ";
+      t += "PatientID=" + ValueAsString(*this, DICOM_TAG_PATIENT_ID);
+    }
+    else
+    {
+      if (s.size() > 0)
+        s += ", ";
+      s += "PatientID";
+    }
+
+    if (HasTag(DICOM_TAG_STUDY_INSTANCE_UID))
+    {
+      if (t.size() > 0)
+        t += ", ";
+      t += "StudyInstanceUID=" + ValueAsString(*this, DICOM_TAG_STUDY_INSTANCE_UID);
+    }
+    else
+    {
+      if (s.size() > 0)
+        s += ", ";
+      s += "StudyInstanceUID";
+    }
+
+    if (HasTag(DICOM_TAG_SERIES_INSTANCE_UID))
+    {
+      if (t.size() > 0)
+        t += ", ";
+      t += "SeriesInstanceUID=" + ValueAsString(*this, DICOM_TAG_SERIES_INSTANCE_UID);
+    }
+    else
+    {
+      if (s.size() > 0)
+        s += ", ";
+      s += "SeriesInstanceUID";
+    }
+
+    if (HasTag(DICOM_TAG_SOP_INSTANCE_UID))
+    {
+      if (t.size() > 0)
+        t += ", ";
+      t += "SOPInstanceUID=" + ValueAsString(*this, DICOM_TAG_SOP_INSTANCE_UID);
+    }
+    else
+    {
+      if (s.size() > 0)
+        s += ", ";
+      s += "SOPInstanceUID";
+    }
+
+    if (t.size() == 0)
+    {
+      LOG(ERROR) << "Store has failed because all the required tags (" << s << ") are missing (is it a DICOMDIR file?)";
+    }
+    else
+    {
+      LOG(ERROR) << "Store has failed because required tags (" << s << ") are missing for the following instance: " << t;
+    }
+  }
+
+
+  bool DicomMap::CopyToString(std::string& result,
+                              const DicomTag& tag,
+                              bool allowBinary) const
+  {
+    const DicomValue* value = TestAndGetValue(tag);
+
+    if (value == NULL)
+    {
+      return false;
+    }
+    else
+    {
+      return value->CopyToString(result, allowBinary);
+    }
+  }
+    
+  bool DicomMap::ParseInteger32(int32_t& result,
+                                const DicomTag& tag) const
+  {
+    const DicomValue* value = TestAndGetValue(tag);
+
+    if (value == NULL)
+    {
+      return false;
+    }
+    else
+    {
+      return value->ParseInteger32(result);
+    }
+  }
+
+  bool DicomMap::ParseInteger64(int64_t& result,
+                                const DicomTag& tag) const
+  {
+    const DicomValue* value = TestAndGetValue(tag);
+
+    if (value == NULL)
+    {
+      return false;
+    }
+    else
+    {
+      return value->ParseInteger64(result);
+    }
+  }
+
+  bool DicomMap::ParseUnsignedInteger32(uint32_t& result,
+                                        const DicomTag& tag) const
+  {
+    const DicomValue* value = TestAndGetValue(tag);
+
+    if (value == NULL)
+    {
+      return false;
+    }
+    else
+    {
+      return value->ParseUnsignedInteger32(result);
+    }
+  }
+
+  bool DicomMap::ParseUnsignedInteger64(uint64_t& result,
+                                        const DicomTag& tag) const
+  {
+    const DicomValue* value = TestAndGetValue(tag);
+
+    if (value == NULL)
+    {
+      return false;
+    }
+    else
+    {
+      return value->ParseUnsignedInteger64(result);
+    }
+  }
+
+  bool DicomMap::ParseFloat(float& result,
+                            const DicomTag& tag) const
+  {
+    const DicomValue* value = TestAndGetValue(tag);
+
+    if (value == NULL)
+    {
+      return false;
+    }
+    else
+    {
+      return value->ParseFloat(result);
+    }
+  }
+
+  bool DicomMap::ParseDouble(double& result,
+                             const DicomTag& tag) const
+  {
+    const DicomValue* value = TestAndGetValue(tag);
+
+    if (value == NULL)
+    {
+      return false;
+    }
+    else
+    {
+      return value->ParseDouble(result);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/DicomFormat/DicomMap.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,209 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomTag.h"
+#include "DicomValue.h"
+#include "../Enumerations.h"
+
+#include <set>
+#include <map>
+#include <json/json.h>
+
+namespace Orthanc
+{
+  class DicomMap : public boost::noncopyable
+  {
+  private:
+    friend class DicomArray;
+    friend class FromDcmtkBridge;
+    friend class ParsedDicomFile;
+
+    typedef std::map<DicomTag, DicomValue*>  Map;
+
+    Map map_;
+
+    // Warning: This takes the ownership of "value"
+    void SetValue(uint16_t group, 
+                  uint16_t element, 
+                  DicomValue* value);
+
+    void SetValue(DicomTag tag, 
+                  DicomValue* value);
+
+    void ExtractTags(DicomMap& source,
+                     const DicomTag* tags,
+                     size_t count) const;
+   
+    static void GetMainDicomTagsInternal(std::set<DicomTag>& result, ResourceType level);
+
+  public:
+    DicomMap()
+    {
+    }
+
+    ~DicomMap()
+    {
+      Clear();
+    }
+
+    size_t GetSize() const
+    {
+      return map_.size();
+    }
+    
+    DicomMap* Clone() const;
+
+    void Assign(const DicomMap& other);
+
+    void Clear();
+
+    void SetValue(uint16_t group, 
+                  uint16_t element, 
+                  const DicomValue& value)
+    {
+      SetValue(group, element, value.Clone());
+    }
+
+    void SetValue(const DicomTag& tag,
+                  const DicomValue& value)
+    {
+      SetValue(tag, value.Clone());
+    }
+
+    void SetValue(const DicomTag& tag,
+                  const std::string& str,
+                  bool isBinary)
+    {
+      SetValue(tag, new DicomValue(str, isBinary));
+    }
+
+    void SetValue(uint16_t group, 
+                  uint16_t element, 
+                  const std::string& str,
+                  bool isBinary)
+    {
+      SetValue(group, element, new DicomValue(str, isBinary));
+    }
+
+    bool HasTag(uint16_t group, uint16_t element) const
+    {
+      return HasTag(DicomTag(group, element));
+    }
+
+    bool HasTag(const DicomTag& tag) const
+    {
+      return map_.find(tag) != map_.end();
+    }
+
+    const DicomValue& GetValue(uint16_t group, uint16_t element) const
+    {
+      return GetValue(DicomTag(group, element));
+    }
+
+    const DicomValue& GetValue(const DicomTag& tag) const;
+
+    // DO NOT delete the returned value!
+    const DicomValue* TestAndGetValue(uint16_t group, uint16_t element) const
+    {
+      return TestAndGetValue(DicomTag(group, element));
+    }       
+
+    // DO NOT delete the returned value!
+    const DicomValue* TestAndGetValue(const DicomTag& tag) const;
+
+    void Remove(const DicomTag& tag);
+
+    void ExtractPatientInformation(DicomMap& result) const;
+
+    void ExtractStudyInformation(DicomMap& result) const;
+
+    void ExtractSeriesInformation(DicomMap& result) const;
+
+    void ExtractInstanceInformation(DicomMap& result) const;
+
+    static void SetupFindPatientTemplate(DicomMap& result);
+
+    static void SetupFindStudyTemplate(DicomMap& result);
+
+    static void SetupFindSeriesTemplate(DicomMap& result);
+
+    static void SetupFindInstanceTemplate(DicomMap& result);
+
+    void CopyTagIfExists(const DicomMap& source,
+                         const DicomTag& tag);
+
+    static bool IsMainDicomTag(const DicomTag& tag, ResourceType level);
+
+    static bool IsMainDicomTag(const DicomTag& tag);
+
+    static void GetMainDicomTags(std::set<DicomTag>& result, ResourceType level);
+
+    static void GetMainDicomTags(std::set<DicomTag>& result);
+
+    void GetTags(std::set<DicomTag>& tags) const;
+
+    static void LoadMainDicomTags(const DicomTag*& tags,
+                                  size_t& size,
+                                  ResourceType level);
+
+    static bool ParseDicomMetaInformation(DicomMap& result,
+                                          const char* dicom,
+                                          size_t size);
+
+    void LogMissingTagsForStore() const;
+
+    bool CopyToString(std::string& result,
+                      const DicomTag& tag,
+                      bool allowBinary) const;
+    
+    bool ParseInteger32(int32_t& result,
+                        const DicomTag& tag) const;
+
+    bool ParseInteger64(int64_t& result,
+                        const DicomTag& tag) const;                                
+
+    bool ParseUnsignedInteger32(uint32_t& result,
+                                const DicomTag& tag) const;
+
+    bool ParseUnsignedInteger64(uint64_t& result,
+                                const DicomTag& tag) const;                                
+
+    bool ParseFloat(float& result,
+                    const DicomTag& tag) const;                                
+
+    bool ParseDouble(double& result,
+                     const DicomTag& tag) const;                                
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/DicomFormat/DicomTag.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,253 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomTag.h"
+
+#include "../OrthancException.h"
+
+#include <iostream>
+#include <iomanip>
+#include <stdio.h>
+
+namespace Orthanc
+{
+  bool DicomTag::operator< (const DicomTag& other) const
+  {
+    if (group_ < other.group_)
+      return true;
+
+    if (group_ > other.group_)
+      return false;
+
+    return element_ < other.element_;
+  }
+
+
+  std::ostream& operator<< (std::ostream& o, const DicomTag& tag)
+  {
+    using namespace std;
+    ios_base::fmtflags state = o.flags();
+    o.flags(ios::right | ios::hex);
+    o << "(" << setfill('0') << setw(4) << tag.GetGroup()
+      << "," << setw(4) << tag.GetElement() << ")";
+    o.flags(state);
+    return o;
+  }
+
+
+  std::string DicomTag::Format() const
+  {
+    char b[16];
+    sprintf(b, "%04x,%04x", group_, element_);
+    return std::string(b);
+  }
+
+
+  const char* DicomTag::GetMainTagsName() const
+  {
+    if (*this == DICOM_TAG_ACCESSION_NUMBER)
+      return "AccessionNumber";
+
+    if (*this == DICOM_TAG_SOP_INSTANCE_UID)
+      return "SOPInstanceUID";
+
+    if (*this == DICOM_TAG_PATIENT_ID)
+      return "PatientID";
+
+    if (*this == DICOM_TAG_SERIES_INSTANCE_UID)
+      return "SeriesInstanceUID";
+
+    if (*this == DICOM_TAG_STUDY_INSTANCE_UID)
+      return "StudyInstanceUID"; 
+
+    if (*this == DICOM_TAG_PIXEL_DATA)
+      return "PixelData";
+
+    if (*this == DICOM_TAG_IMAGE_INDEX)
+      return "ImageIndex";
+
+    if (*this == DICOM_TAG_INSTANCE_NUMBER)
+      return "InstanceNumber";
+
+    if (*this == DICOM_TAG_NUMBER_OF_SLICES)
+      return "NumberOfSlices";
+
+    if (*this == DICOM_TAG_NUMBER_OF_FRAMES)
+      return "NumberOfFrames";
+
+    if (*this == DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES)
+      return "CardiacNumberOfImages";
+
+    if (*this == DICOM_TAG_IMAGES_IN_ACQUISITION)
+      return "ImagesInAcquisition";
+
+    if (*this == DICOM_TAG_PATIENT_NAME)
+      return "PatientName";
+
+    if (*this == DICOM_TAG_IMAGE_POSITION_PATIENT)
+      return "ImagePositionPatient";
+
+    if (*this == DICOM_TAG_IMAGE_ORIENTATION_PATIENT)
+      return "ImageOrientationPatient";
+
+    return "";
+  }
+
+
+  void DicomTag::AddTagsForModule(std::set<DicomTag>& target,
+                                  DicomModule module)
+  {
+    // REFERENCE: 11_03pu.pdf, DICOM PS 3.3 2011 - Information Object Definitions
+
+    switch (module)
+    {
+      case DicomModule_Patient:
+        // This is Table C.7-1 "Patient Module Attributes" (p. 373)
+        target.insert(DicomTag(0x0010, 0x0010));   // Patient's name
+        target.insert(DicomTag(0x0010, 0x0020));   // Patient ID
+        target.insert(DicomTag(0x0010, 0x0030));   // Patient's birth date
+        target.insert(DicomTag(0x0010, 0x0040));   // Patient's sex
+        target.insert(DicomTag(0x0008, 0x1120));   // Referenced patient sequence
+        target.insert(DicomTag(0x0010, 0x0032));   // Patient's birth time
+        target.insert(DicomTag(0x0010, 0x1000));   // Other patient IDs
+        target.insert(DicomTag(0x0010, 0x1002));   // Other patient IDs sequence
+        target.insert(DicomTag(0x0010, 0x1001));   // Other patient names
+        target.insert(DicomTag(0x0010, 0x2160));   // Ethnic group
+        target.insert(DicomTag(0x0010, 0x4000));   // Patient comments
+        target.insert(DicomTag(0x0010, 0x2201));   // Patient species description
+        target.insert(DicomTag(0x0010, 0x2202));   // Patient species code sequence
+        target.insert(DicomTag(0x0010, 0x2292));   // Patient breed description
+        target.insert(DicomTag(0x0010, 0x2293));   // Patient breed code sequence
+        target.insert(DicomTag(0x0010, 0x2294));   // Breed registration sequence
+        target.insert(DicomTag(0x0010, 0x2297));   // Responsible person
+        target.insert(DicomTag(0x0010, 0x2298));   // Responsible person role
+        target.insert(DicomTag(0x0010, 0x2299));   // Responsible organization
+        target.insert(DicomTag(0x0012, 0x0062));   // Patient identity removed
+        target.insert(DicomTag(0x0012, 0x0063));   // De-identification method
+        target.insert(DicomTag(0x0012, 0x0064));   // De-identification method code sequence
+
+        // Table 10-18 ISSUER OF PATIENT ID MACRO (p. 112)
+        target.insert(DicomTag(0x0010, 0x0021));   // Issuer of Patient ID
+        target.insert(DicomTag(0x0010, 0x0024));   // Issuer of Patient ID qualifiers sequence
+        break;
+
+      case DicomModule_Study:
+        // This is Table C.7-3 "General Study Module Attributes" (p. 378)
+        target.insert(DicomTag(0x0020, 0x000d));   // Study instance UID
+        target.insert(DicomTag(0x0008, 0x0020));   // Study date
+        target.insert(DicomTag(0x0008, 0x0030));   // Study time
+        target.insert(DicomTag(0x0008, 0x0090));   // Referring physician's name
+        target.insert(DicomTag(0x0008, 0x0096));   // Referring physician identification sequence
+        target.insert(DicomTag(0x0020, 0x0010));   // Study ID
+        target.insert(DicomTag(0x0008, 0x0050));   // Accession number
+        target.insert(DicomTag(0x0008, 0x0051));   // Issuer of accession number sequence
+        target.insert(DicomTag(0x0008, 0x1030));   // Study description
+        target.insert(DicomTag(0x0008, 0x1048));   // Physician(s) of record
+        target.insert(DicomTag(0x0008, 0x1049));   // Physician(s) of record identification sequence
+        target.insert(DicomTag(0x0008, 0x1060));   // Name of physician(s) reading study
+        target.insert(DicomTag(0x0008, 0x1062));   // Physician(s) reading study identification sequence
+        target.insert(DicomTag(0x0032, 0x1034));   // Requesting service code sequence
+        target.insert(DicomTag(0x0008, 0x1110));   // Referenced study sequence
+        target.insert(DicomTag(0x0008, 0x1032));   // Procedure code sequence
+        target.insert(DicomTag(0x0040, 0x1012));   // Reason for performed procedure code sequence
+        break;
+
+      case DicomModule_Series:
+        // This is Table C.7-5 "General Series Module Attributes" (p. 385)
+        target.insert(DicomTag(0x0008, 0x0060));   // Modality 
+        target.insert(DicomTag(0x0020, 0x000e));   // Series Instance UID 
+        target.insert(DicomTag(0x0020, 0x0011));   // Series Number 
+        target.insert(DicomTag(0x0020, 0x0060));   // Laterality 
+        target.insert(DicomTag(0x0008, 0x0021));   // Series Date 
+        target.insert(DicomTag(0x0008, 0x0031));   // Series Time 
+        target.insert(DicomTag(0x0008, 0x1050));   // Performing Physicians’ Name 
+        target.insert(DicomTag(0x0008, 0x1052));   // Performing Physician Identification Sequence 
+        target.insert(DicomTag(0x0018, 0x1030));   // Protocol Name
+        target.insert(DicomTag(0x0008, 0x103e));   // Series Description 
+        target.insert(DicomTag(0x0008, 0x103f));   // Series Description Code Sequence 
+        target.insert(DicomTag(0x0008, 0x1070));   // Operators' Name 
+        target.insert(DicomTag(0x0008, 0x1072));   // Operator Identification Sequence 
+        target.insert(DicomTag(0x0008, 0x1111));   // Referenced Performed Procedure Step Sequence
+        target.insert(DicomTag(0x0008, 0x1250));   // Related Series Sequence
+        target.insert(DicomTag(0x0018, 0x0015));   // Body Part Examined
+        target.insert(DicomTag(0x0018, 0x5100));   // Patient Position
+        target.insert(DicomTag(0x0028, 0x0108));   // Smallest Pixel Value in Series 
+        target.insert(DicomTag(0x0029, 0x0109));   // Largest Pixel Value in Series 
+        target.insert(DicomTag(0x0040, 0x0275));   // Request Attributes Sequence 
+        target.insert(DicomTag(0x0010, 0x2210));   // Anatomical Orientation Type
+
+        // Table 10-16 PERFORMED PROCEDURE STEP SUMMARY MACRO ATTRIBUTES
+        target.insert(DicomTag(0x0040, 0x0253));   // Performed Procedure Step ID 
+        target.insert(DicomTag(0x0040, 0x0244));   // Performed Procedure Step Start Date 
+        target.insert(DicomTag(0x0040, 0x0245));   // Performed Procedure Step Start Time 
+        target.insert(DicomTag(0x0040, 0x0254));   // Performed Procedure Step Description 
+        target.insert(DicomTag(0x0040, 0x0260));   // Performed Protocol Code Sequence 
+        target.insert(DicomTag(0x0040, 0x0280));   // Comments on the Performed Procedure Step
+        break;
+
+      case DicomModule_Instance:
+        // This is Table C.12-1 "SOP Common Module Attributes" (p. 1207)
+        target.insert(DicomTag(0x0008, 0x0016));   // SOP Class UID
+        target.insert(DicomTag(0x0008, 0x0018));   // SOP Instance UID 
+        target.insert(DicomTag(0x0008, 0x0005));   // Specific Character Set 
+        target.insert(DicomTag(0x0008, 0x0012));   // Instance Creation Date 
+        target.insert(DicomTag(0x0008, 0x0013));   // Instance Creation Time 
+        target.insert(DicomTag(0x0008, 0x0014));   // Instance Creator UID 
+        target.insert(DicomTag(0x0008, 0x001a));   // Related General SOP Class UID 
+        target.insert(DicomTag(0x0008, 0x001b));   // Original Specialized SOP Class UID 
+        target.insert(DicomTag(0x0008, 0x0110));   // Coding Scheme Identification Sequence 
+        target.insert(DicomTag(0x0008, 0x0201));   // Timezone Offset From UTC 
+        target.insert(DicomTag(0x0018, 0xa001));   // Contributing Equipment Sequence
+        target.insert(DicomTag(0x0020, 0x0013));   // Instance Number 
+        target.insert(DicomTag(0x0100, 0x0410));   // SOP Instance Status 
+        target.insert(DicomTag(0x0100, 0x0420));   // SOP Authorization DateTime 
+        target.insert(DicomTag(0x0100, 0x0424));   // SOP Authorization Comment 
+        target.insert(DicomTag(0x0100, 0x0426));   // Authorization Equipment Certification Number
+        target.insert(DicomTag(0x0400, 0x0500));   // Encrypted Attributes Sequence
+        target.insert(DicomTag(0x0400, 0x0561));   // Original Attributes Sequence 
+        target.insert(DicomTag(0x0040, 0xa390));   // HL7 Structured Document Reference Sequence
+        target.insert(DicomTag(0x0028, 0x0303));   // Longitudinal Temporal Information Modified 
+
+        // Table C.12-6 "DIGITAL SIGNATURES MACRO ATTRIBUTES" (p. 1216)
+        target.insert(DicomTag(0x4ffe, 0x0001));   // MAC Parameters sequence
+        target.insert(DicomTag(0xfffa, 0xfffa));   // Digital signatures sequence
+        break;
+
+        // TODO IMAGE MODULE?
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/DicomFormat/DicomTag.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,196 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 <set>
+#include <stdint.h>
+
+#include "../Enumerations.h"
+
+namespace Orthanc
+{
+  class DicomTag
+  {
+    // This must stay a POD (plain old data structure) 
+
+  private:
+    uint16_t group_;
+    uint16_t element_;
+
+  public:
+    DicomTag(uint16_t group,
+             uint16_t element) :
+      group_(group),
+      element_(element)
+    {
+    }
+
+    uint16_t GetGroup() const
+    {
+      return group_;
+    }
+
+    uint16_t GetElement() const
+    {
+      return element_;
+    }
+
+    bool IsPrivate() const
+    {
+      return group_ % 2 == 1;
+    }
+
+    const char* GetMainTagsName() const;
+
+    bool operator< (const DicomTag& other) const;
+
+    bool operator== (const DicomTag& other) const
+    {
+      return group_ == other.group_ && element_ == other.element_;
+    }
+
+    bool operator!= (const DicomTag& other) const
+    {
+      return !(*this == other);
+    }
+
+    std::string Format() const;
+
+    friend std::ostream& operator<< (std::ostream& o, const DicomTag& tag);
+
+    static void AddTagsForModule(std::set<DicomTag>& target,
+                                 DicomModule module);
+  };
+
+  // Aliases for the most useful tags
+  static const DicomTag DICOM_TAG_ACCESSION_NUMBER(0x0008, 0x0050);
+  static const DicomTag DICOM_TAG_SOP_INSTANCE_UID(0x0008, 0x0018);
+  static const DicomTag DICOM_TAG_PATIENT_ID(0x0010, 0x0020);
+  static const DicomTag DICOM_TAG_SERIES_INSTANCE_UID(0x0020, 0x000e);
+  static const DicomTag DICOM_TAG_STUDY_INSTANCE_UID(0x0020, 0x000d);
+  static const DicomTag DICOM_TAG_PIXEL_DATA(0x7fe0, 0x0010);
+  static const DicomTag DICOM_TAG_TRANSFER_SYNTAX_UID(0x0002, 0x0010);
+
+  static const DicomTag DICOM_TAG_IMAGE_INDEX(0x0054, 0x1330);
+  static const DicomTag DICOM_TAG_INSTANCE_NUMBER(0x0020, 0x0013);
+
+  static const DicomTag DICOM_TAG_NUMBER_OF_SLICES(0x0054, 0x0081);
+  static const DicomTag DICOM_TAG_NUMBER_OF_TIME_SLICES(0x0054, 0x0101);
+  static const DicomTag DICOM_TAG_NUMBER_OF_FRAMES(0x0028, 0x0008);
+  static const DicomTag DICOM_TAG_CARDIAC_NUMBER_OF_IMAGES(0x0018, 0x1090);
+  static const DicomTag DICOM_TAG_IMAGES_IN_ACQUISITION(0x0020, 0x1002);
+  static const DicomTag DICOM_TAG_PATIENT_NAME(0x0010, 0x0010);
+  static const DicomTag DICOM_TAG_ENCAPSULATED_DOCUMENT(0x0042, 0x0011);
+
+  static const DicomTag DICOM_TAG_STUDY_DESCRIPTION(0x0008, 0x1030);
+  static const DicomTag DICOM_TAG_SERIES_DESCRIPTION(0x0008, 0x103e);
+  static const DicomTag DICOM_TAG_MODALITY(0x0008, 0x0060);
+
+  // The following is used for "modify/anonymize" operations
+  static const DicomTag DICOM_TAG_SOP_CLASS_UID(0x0008, 0x0016);
+  static const DicomTag DICOM_TAG_MEDIA_STORAGE_SOP_CLASS_UID(0x0002, 0x0002);
+  static const DicomTag DICOM_TAG_MEDIA_STORAGE_SOP_INSTANCE_UID(0x0002, 0x0003);
+  static const DicomTag DICOM_TAG_DEIDENTIFICATION_METHOD(0x0012, 0x0063);
+
+  // DICOM tags used for fMRI (thanks to Will Ryder)
+  static const DicomTag DICOM_TAG_NUMBER_OF_TEMPORAL_POSITIONS(0x0020, 0x0105);
+  static const DicomTag DICOM_TAG_TEMPORAL_POSITION_IDENTIFIER(0x0020, 0x0100);
+
+  // Tags for C-FIND and C-MOVE
+  static const DicomTag DICOM_TAG_MESSAGE_ID(0x0000, 0x0110);
+  static const DicomTag DICOM_TAG_SPECIFIC_CHARACTER_SET(0x0008, 0x0005);
+  static const DicomTag DICOM_TAG_QUERY_RETRIEVE_LEVEL(0x0008, 0x0052);
+  static const DicomTag DICOM_TAG_MODALITIES_IN_STUDY(0x0008, 0x0061);
+
+  // Tags for images
+  static const DicomTag DICOM_TAG_COLUMNS(0x0028, 0x0011);
+  static const DicomTag DICOM_TAG_ROWS(0x0028, 0x0010);
+  static const DicomTag DICOM_TAG_SAMPLES_PER_PIXEL(0x0028, 0x0002);
+  static const DicomTag DICOM_TAG_BITS_ALLOCATED(0x0028, 0x0100);
+  static const DicomTag DICOM_TAG_BITS_STORED(0x0028, 0x0101);
+  static const DicomTag DICOM_TAG_HIGH_BIT(0x0028, 0x0102);
+  static const DicomTag DICOM_TAG_PIXEL_REPRESENTATION(0x0028, 0x0103);
+  static const DicomTag DICOM_TAG_PLANAR_CONFIGURATION(0x0028, 0x0006);
+  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);
+
+  // Tags related to date and time
+  static const DicomTag DICOM_TAG_ACQUISITION_DATE(0x0008, 0x0022);
+  static const DicomTag DICOM_TAG_ACQUISITION_TIME(0x0008, 0x0032);
+  static const DicomTag DICOM_TAG_CONTENT_DATE(0x0008, 0x0023);
+  static const DicomTag DICOM_TAG_CONTENT_TIME(0x0008, 0x0033);
+  static const DicomTag DICOM_TAG_INSTANCE_CREATION_DATE(0x0008, 0x0012);
+  static const DicomTag DICOM_TAG_INSTANCE_CREATION_TIME(0x0008, 0x0013);
+  static const DicomTag DICOM_TAG_PATIENT_BIRTH_DATE(0x0010, 0x0030);
+  static const DicomTag DICOM_TAG_PATIENT_BIRTH_TIME(0x0010, 0x0032);
+  static const DicomTag DICOM_TAG_SERIES_DATE(0x0008, 0x0021);
+  static const DicomTag DICOM_TAG_SERIES_TIME(0x0008, 0x0031);
+  static const DicomTag DICOM_TAG_STUDY_DATE(0x0008, 0x0020);
+  static const DicomTag DICOM_TAG_STUDY_TIME(0x0008, 0x0030);
+
+  // Various tags
+  static const DicomTag DICOM_TAG_SERIES_TYPE(0x0054, 0x1000);
+  static const DicomTag DICOM_TAG_REQUESTED_PROCEDURE_DESCRIPTION(0x0032, 0x1060);
+  static const DicomTag DICOM_TAG_INSTITUTION_NAME(0x0008, 0x0080);
+  static const DicomTag DICOM_TAG_REQUESTING_PHYSICIAN(0x0032, 0x1032);
+  static const DicomTag DICOM_TAG_REFERRING_PHYSICIAN_NAME(0x0008, 0x0090);
+  static const DicomTag DICOM_TAG_OPERATOR_NAME(0x0008, 0x1070);
+  static const DicomTag DICOM_TAG_PERFORMED_PROCEDURE_STEP_DESCRIPTION(0x0040, 0x0254);
+  static const DicomTag DICOM_TAG_IMAGE_COMMENTS(0x0020, 0x4000);
+  static const DicomTag DICOM_TAG_ACQUISITION_DEVICE_PROCESSING_DESCRIPTION(0x0018, 0x1400);
+  static const DicomTag DICOM_TAG_CONTRAST_BOLUS_AGENT(0x0018, 0x0010);
+
+  // Tags used within the Stone of Orthanc
+  static const DicomTag DICOM_TAG_FRAME_INCREMENT_POINTER(0x0028, 0x0009);
+  static const DicomTag DICOM_TAG_GRID_FRAME_OFFSET_VECTOR(0x3004, 0x000c);
+  static const DicomTag DICOM_TAG_PIXEL_SPACING(0x0028, 0x0030);
+  static const DicomTag DICOM_TAG_RESCALE_INTERCEPT(0x0028, 0x1052);
+  static const DicomTag DICOM_TAG_RESCALE_SLOPE(0x0028, 0x1053);
+  static const DicomTag DICOM_TAG_SLICE_THICKNESS(0x0018, 0x0050);
+  static const DicomTag DICOM_TAG_WINDOW_CENTER(0x0028, 0x1050);
+  static const DicomTag DICOM_TAG_WINDOW_WIDTH(0x0028, 0x1051);
+  static const DicomTag DICOM_TAG_DOSE_GRID_SCALING(0x3004, 0x000e);
+                                    
+  // Counting patients, studies and series
+  // https://www.medicalconnections.co.uk/kb/Counting_Studies_Series_and_Instances
+  static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES(0x0020, 0x1200);  
+  static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES(0x0020, 0x1202);  
+  static const DicomTag DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES(0x0020, 0x1204);  
+  static const DicomTag DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES(0x0020, 0x1206);  
+  static const DicomTag DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES(0x0020, 0x1208);  
+  static const DicomTag DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES(0x0020, 0x1209);  
+  static const DicomTag DICOM_TAG_SOP_CLASSES_IN_STUDY(0x0008, 0x0062);  
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/DicomFormat/DicomValue.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,196 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomValue.h"
+
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+
+#include <boost/lexical_cast.hpp>
+
+namespace Orthanc
+{
+  DicomValue::DicomValue(const DicomValue& other) : 
+    type_(other.type_),
+    content_(other.content_)
+  {
+  }
+
+
+  DicomValue::DicomValue(const std::string& content,
+                         bool isBinary) :
+    type_(isBinary ? Type_Binary : Type_String),
+    content_(content)
+  {
+  }
+  
+  
+  DicomValue::DicomValue(const char* data,
+                         size_t size,
+                         bool isBinary) :
+    type_(isBinary ? Type_Binary : Type_String)
+  {
+    content_.assign(data, size);
+  }
+    
+  
+  const std::string& DicomValue::GetContent() const
+  {
+    if (type_ == Type_Null)
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+    else
+    {
+      return content_;
+    }
+  }
+
+
+  DicomValue* DicomValue::Clone() const
+  {
+    return new DicomValue(*this);
+  }
+
+  
+#if ORTHANC_ENABLE_BASE64 == 1
+  void DicomValue::FormatDataUriScheme(std::string& target,
+                                       const std::string& mime) const
+  {
+    Toolbox::EncodeBase64(target, GetContent());
+    target.insert(0, "data:" + mime + ";base64,");
+  }
+#endif
+
+
+  template <typename T,
+            bool allowSigned>
+  static bool ParseValue(T& result,
+                         const DicomValue& source)
+  {
+    if (source.IsBinary() ||
+        source.IsNull())
+    {
+      return false;
+    }
+    
+    try
+    {
+      std::string value = Toolbox::StripSpaces(source.GetContent());
+      if (value.empty())
+      {
+        return false;
+      }
+
+      if (!allowSigned &&
+          value[0] == '-')
+      {
+        return false;
+      }
+      
+      result = boost::lexical_cast<T>(value);
+      return true;
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      return false;
+    }
+  }
+
+  bool DicomValue::ParseInteger32(int32_t& result) const
+  {
+    int64_t tmp;
+    if (ParseValue<int64_t, true>(tmp, *this))
+    {
+      result = static_cast<int32_t>(tmp);
+      return (tmp == static_cast<int64_t>(result));  // Check no overflow occurs
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  bool DicomValue::ParseInteger64(int64_t& result) const
+  {
+    return ParseValue<int64_t, true>(result, *this);
+  }
+
+  bool DicomValue::ParseUnsignedInteger32(uint32_t& result) const
+  {
+    uint64_t tmp;
+    if (ParseValue<uint64_t, false>(tmp, *this))
+    {
+      result = static_cast<uint32_t>(tmp);
+      return (tmp == static_cast<uint64_t>(result));  // Check no overflow occurs
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  bool DicomValue::ParseUnsignedInteger64(uint64_t& result) const
+  {
+    return ParseValue<uint64_t, false>(result, *this);
+  }
+
+  bool DicomValue::ParseFloat(float& result) const
+  {
+    return ParseValue<float, true>(result, *this);
+  }
+
+  bool DicomValue::ParseDouble(double& result) const
+  {
+    return ParseValue<double, true>(result, *this);
+  }
+
+  bool DicomValue::CopyToString(std::string& result,
+                                bool allowBinary) const
+  {
+    if (IsNull())
+    {
+      return false;
+    }
+    else if (IsBinary() && !allowBinary)
+    {
+      return false;
+    }
+    else
+    {
+      result.assign(content_);
+      return true;
+    }
+  }    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/DicomFormat/DicomValue.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,113 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 <stdint.h>
+#include <string>
+#include <boost/noncopyable.hpp>
+
+#if !defined(ORTHANC_ENABLE_BASE64)
+#  error The macro ORTHANC_ENABLE_BASE64 must be defined
+#endif
+
+
+namespace Orthanc
+{
+  class DicomValue : public boost::noncopyable
+  {
+  private:
+    enum Type
+    {
+      Type_Null,
+      Type_String,
+      Type_Binary
+    };
+
+    Type         type_;
+    std::string  content_;
+
+    DicomValue(const DicomValue& other);
+
+  public:
+    DicomValue() : type_(Type_Null)
+    {
+    }
+    
+    DicomValue(const std::string& content,
+               bool isBinary);
+    
+    DicomValue(const char* data,
+               size_t size,
+               bool isBinary);
+    
+    const std::string& GetContent() const;
+
+    bool IsNull() const
+    {
+      return type_ == Type_Null;
+    }
+
+    bool IsBinary() const
+    {
+      return type_ == Type_Binary;
+    }
+    
+    DicomValue* Clone() const;
+
+#if ORTHANC_ENABLE_BASE64 == 1
+    void FormatDataUriScheme(std::string& target,
+                             const std::string& mime) const;
+
+    void FormatDataUriScheme(std::string& target) const
+    {
+      FormatDataUriScheme(target, "application/octet-stream");
+    }
+#endif
+
+    bool CopyToString(std::string& result,
+                      bool allowBinary) const;
+    
+    bool ParseInteger32(int32_t& result) const;
+
+    bool ParseInteger64(int64_t& result) const;                                
+
+    bool ParseUnsignedInteger32(uint32_t& result) const;
+
+    bool ParseUnsignedInteger64(uint64_t& result) const;                                
+
+    bool ParseFloat(float& result) const;                                
+
+    bool ParseDouble(double& result) const;                                
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Endianness.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,207 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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
+
+
+/********************************************************************
+ ** LINUX-LIKE ARCHITECTURES
+ ********************************************************************/
+
+#if defined(__LSB_VERSION__)
+// Linux Standard Base (LSB) does not come with be16toh, be32toh, and
+// be64toh
+#  define ORTHANC_HAS_BUILTIN_BYTE_SWAP 0
+#  include <endian.h>
+#elif defined(__linux__) || defined(__EMSCRIPTEN__)
+#  define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1
+#  include <endian.h>
+#endif
+
+
+/********************************************************************
+ ** WINDOWS ARCHITECTURES
+ **
+ ** On Windows x86, "host" will always be little-endian ("le").
+ ********************************************************************/
+
+#if defined(_WIN32)
+#  if defined(_MSC_VER)
+//   Visual Studio - http://msdn.microsoft.com/en-us/library/a3140177.aspx
+#    define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1
+#    define be16toh(x) _byteswap_ushort(x)
+#    define be32toh(x) _byteswap_ulong(x)
+#    define be64toh(x) _byteswap_uint64(x)
+#  elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))
+//   MinGW >= 4.3 - Use builtin intrinsic for byte swapping
+#    define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1
+#    define be16toh(x) __builtin_bswap16(x)
+#    define be32toh(x) __builtin_bswap32(x)
+#    define be64toh(x) __builtin_bswap64(x)
+#  else
+//   MinGW <= 4.2, we must manually implement the byte swapping
+#    define ORTHANC_HAS_BUILTIN_BYTE_SWAP 0
+#    define be16toh(x) __orthanc_bswap16(x)
+#    define be32toh(x) __orthanc_bswap32(x)
+#    define be64toh(x) __orthanc_bswap64(x)
+#  endif
+
+#  define htobe16(x) be16toh(x)
+#  define htobe32(x) be32toh(x)
+#  define htobe64(x) be64toh(x)
+
+#  define htole16(x) x
+#  define htole32(x) x
+#  define htole64(x) x
+
+#  define le16toh(x) x
+#  define le32toh(x) x
+#  define le64toh(x) x
+#endif
+
+
+/********************************************************************
+ ** FREEBSD ARCHITECTURES
+ ********************************************************************/
+
+#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+#  define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1
+#  include <arpa/inet.h>
+#endif
+
+
+/********************************************************************
+ ** OPENBSD ARCHITECTURES
+ ********************************************************************/
+
+#if defined(__OpenBSD__)
+#  define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1
+#  include <endian.h>
+#endif
+
+
+/********************************************************************
+ ** APPLE ARCHITECTURES (including OS X)
+ ********************************************************************/
+
+#if defined(__APPLE__)
+#  define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1
+#  include <libkern/OSByteOrder.h>
+#  define be16toh(x) OSSwapBigToHostInt16(x)
+#  define be32toh(x) OSSwapBigToHostInt32(x)
+#  define be64toh(x) OSSwapBigToHostInt64(x)
+
+#  define htobe16(x) OSSwapHostToBigInt16(x)
+#  define htobe32(x) OSSwapHostToBigInt32(x)
+#  define htobe64(x) OSSwapHostToBigInt64(x)
+
+#  define htole16(x) OSSwapHostToLittleInt16(x)
+#  define htole32(x) OSSwapHostToLittleInt32(x)
+#  define htole64(x) OSSwapHostToLittleInt64(x)
+
+#  define le16toh(x) OSSwapLittleToHostInt16(x)
+#  define le32toh(x) OSSwapLittleToHostInt32(x)
+#  define le64toh(x) OSSwapLittleToHostInt64(x)
+#endif
+
+
+/********************************************************************
+ ** PORTABLE (BUT SLOW) IMPLEMENTATION OF BYTE-SWAPPING
+ ********************************************************************/
+
+#if ORTHANC_HAS_BUILTIN_BYTE_SWAP != 1
+
+#include <stdint.h>
+
+static inline uint16_t __orthanc_bswap16(uint16_t a)
+{
+  return (a << 8) | (a >> 8);
+}
+
+static inline uint32_t __orthanc_bswap32(uint32_t a)
+{
+  const uint8_t* p = reinterpret_cast<const uint8_t*>(&a);
+  return (static_cast<uint32_t>(p[0]) << 24 |
+          static_cast<uint32_t>(p[1]) << 16 |
+          static_cast<uint32_t>(p[2]) << 8 |
+          static_cast<uint32_t>(p[3]));
+}
+
+static inline uint64_t __orthanc_bswap64(uint64_t a)
+{
+  const uint8_t* p = reinterpret_cast<const uint8_t*>(&a);
+  return (static_cast<uint64_t>(p[0]) << 56 |
+          static_cast<uint64_t>(p[1]) << 48 |
+          static_cast<uint64_t>(p[2]) << 40 |
+          static_cast<uint64_t>(p[3]) << 32 |
+          static_cast<uint64_t>(p[4]) << 24 |
+          static_cast<uint64_t>(p[5]) << 16 |
+          static_cast<uint64_t>(p[6]) << 8 |
+          static_cast<uint64_t>(p[7]));
+}
+
+#if defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && defined(__BIG_ENDIAN)
+#  if __BYTE_ORDER == __LITTLE_ENDIAN
+#    define be16toh(x) __orthanc_bswap16(x)
+#    define be32toh(x) __orthanc_bswap32(x)
+#    define be64toh(x) __orthanc_bswap64(x)
+#    define htobe16(x) __orthanc_bswap16(x)
+#    define htobe32(x) __orthanc_bswap32(x)
+#    define htobe64(x) __orthanc_bswap64(x)
+#    define htole16(x) x
+#    define htole32(x) x
+#    define htole64(x) x
+#    define le16toh(x) x
+#    define le32toh(x) x
+#    define le64toh(x) x
+#  elif __BYTE_ORDER == __BIG_ENDIAN
+#    define be16toh(x) x
+#    define be32toh(x) x
+#    define be64toh(x) x
+#    define htobe16(x) x
+#    define htobe32(x) x
+#    define htobe64(x) x
+#    define htole16(x) __orthanc_bswap16(x)
+#    define htole32(x) __orthanc_bswap32(x)
+#    define htole64(x) __orthanc_bswap64(x)
+#    define le16toh(x) __orthanc_bswap16(x)
+#    define le32toh(x) __orthanc_bswap32(x)
+#    define le64toh(x) __orthanc_bswap64(x)
+#  else
+#    error Please support your platform here
+#  endif
+#else
+#  error Please support your platform here
+#endif
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Enumerations.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,1716 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "Enumerations.h"
+
+#include "OrthancException.h"
+#include "Toolbox.h"
+#include "Logging.h"
+
+#include <boost/thread/mutex.hpp>
+#include <string.h>
+#include <cassert>
+
+namespace Orthanc
+{
+  // This function is autogenerated by the script
+  // "Resources/GenerateErrorCodes.py"
+  const char* EnumerationToString(ErrorCode error)
+  {
+    switch (error)
+    {
+      case ErrorCode_InternalError:
+        return "Internal error";
+
+      case ErrorCode_Success:
+        return "Success";
+
+      case ErrorCode_Plugin:
+        return "Error encountered within the plugin engine";
+
+      case ErrorCode_NotImplemented:
+        return "Not implemented yet";
+
+      case ErrorCode_ParameterOutOfRange:
+        return "Parameter out of range";
+
+      case ErrorCode_NotEnoughMemory:
+        return "The server hosting Orthanc is running out of memory";
+
+      case ErrorCode_BadParameterType:
+        return "Bad type for a parameter";
+
+      case ErrorCode_BadSequenceOfCalls:
+        return "Bad sequence of calls";
+
+      case ErrorCode_InexistentItem:
+        return "Accessing an inexistent item";
+
+      case ErrorCode_BadRequest:
+        return "Bad request";
+
+      case ErrorCode_NetworkProtocol:
+        return "Error in the network protocol";
+
+      case ErrorCode_SystemCommand:
+        return "Error while calling a system command";
+
+      case ErrorCode_Database:
+        return "Error with the database engine";
+
+      case ErrorCode_UriSyntax:
+        return "Badly formatted URI";
+
+      case ErrorCode_InexistentFile:
+        return "Inexistent file";
+
+      case ErrorCode_CannotWriteFile:
+        return "Cannot write to file";
+
+      case ErrorCode_BadFileFormat:
+        return "Bad file format";
+
+      case ErrorCode_Timeout:
+        return "Timeout";
+
+      case ErrorCode_UnknownResource:
+        return "Unknown resource";
+
+      case ErrorCode_IncompatibleDatabaseVersion:
+        return "Incompatible version of the database";
+
+      case ErrorCode_FullStorage:
+        return "The file storage is full";
+
+      case ErrorCode_CorruptedFile:
+        return "Corrupted file (e.g. inconsistent MD5 hash)";
+
+      case ErrorCode_InexistentTag:
+        return "Inexistent tag";
+
+      case ErrorCode_ReadOnly:
+        return "Cannot modify a read-only data structure";
+
+      case ErrorCode_IncompatibleImageFormat:
+        return "Incompatible format of the images";
+
+      case ErrorCode_IncompatibleImageSize:
+        return "Incompatible size of the images";
+
+      case ErrorCode_SharedLibrary:
+        return "Error while using a shared library (plugin)";
+
+      case ErrorCode_UnknownPluginService:
+        return "Plugin invoking an unknown service";
+
+      case ErrorCode_UnknownDicomTag:
+        return "Unknown DICOM tag";
+
+      case ErrorCode_BadJson:
+        return "Cannot parse a JSON document";
+
+      case ErrorCode_Unauthorized:
+        return "Bad credentials were provided to an HTTP request";
+
+      case ErrorCode_BadFont:
+        return "Badly formatted font file";
+
+      case ErrorCode_DatabasePlugin:
+        return "The plugin implementing a custom database back-end does not fulfill the proper interface";
+
+      case ErrorCode_StorageAreaPlugin:
+        return "Error in the plugin implementing a custom storage area";
+
+      case ErrorCode_EmptyRequest:
+        return "The request is empty";
+
+      case ErrorCode_NotAcceptable:
+        return "Cannot send a response which is acceptable according to the Accept HTTP header";
+
+      case ErrorCode_NullPointer:
+        return "Cannot handle a NULL pointer";
+
+      case ErrorCode_DatabaseUnavailable:
+        return "The database is currently not available (probably a transient situation)";
+
+      case ErrorCode_SQLiteNotOpened:
+        return "SQLite: The database is not opened";
+
+      case ErrorCode_SQLiteAlreadyOpened:
+        return "SQLite: Connection is already open";
+
+      case ErrorCode_SQLiteCannotOpen:
+        return "SQLite: Unable to open the database";
+
+      case ErrorCode_SQLiteStatementAlreadyUsed:
+        return "SQLite: This cached statement is already being referred to";
+
+      case ErrorCode_SQLiteExecute:
+        return "SQLite: Cannot execute a command";
+
+      case ErrorCode_SQLiteRollbackWithoutTransaction:
+        return "SQLite: Rolling back a nonexistent transaction (have you called Begin()?)";
+
+      case ErrorCode_SQLiteCommitWithoutTransaction:
+        return "SQLite: Committing a nonexistent transaction";
+
+      case ErrorCode_SQLiteRegisterFunction:
+        return "SQLite: Unable to register a function";
+
+      case ErrorCode_SQLiteFlush:
+        return "SQLite: Unable to flush the database";
+
+      case ErrorCode_SQLiteCannotRun:
+        return "SQLite: Cannot run a cached statement";
+
+      case ErrorCode_SQLiteCannotStep:
+        return "SQLite: Cannot step over a cached statement";
+
+      case ErrorCode_SQLiteBindOutOfRange:
+        return "SQLite: Bing a value while out of range (serious error)";
+
+      case ErrorCode_SQLitePrepareStatement:
+        return "SQLite: Cannot prepare a cached statement";
+
+      case ErrorCode_SQLiteTransactionAlreadyStarted:
+        return "SQLite: Beginning the same transaction twice";
+
+      case ErrorCode_SQLiteTransactionCommit:
+        return "SQLite: Failure when committing the transaction";
+
+      case ErrorCode_SQLiteTransactionBegin:
+        return "SQLite: Cannot start a transaction";
+
+      case ErrorCode_DirectoryOverFile:
+        return "The directory to be created is already occupied by a regular file";
+
+      case ErrorCode_FileStorageCannotWrite:
+        return "Unable to create a subdirectory or a file in the file storage";
+
+      case ErrorCode_DirectoryExpected:
+        return "The specified path does not point to a directory";
+
+      case ErrorCode_HttpPortInUse:
+        return "The TCP port of the HTTP server is privileged or already in use";
+
+      case ErrorCode_DicomPortInUse:
+        return "The TCP port of the DICOM server is privileged or already in use";
+
+      case ErrorCode_BadHttpStatusInRest:
+        return "This HTTP status is not allowed in a REST API";
+
+      case ErrorCode_RegularFileExpected:
+        return "The specified path does not point to a regular file";
+
+      case ErrorCode_PathToExecutable:
+        return "Unable to get the path to the executable";
+
+      case ErrorCode_MakeDirectory:
+        return "Cannot create a directory";
+
+      case ErrorCode_BadApplicationEntityTitle:
+        return "An application entity title (AET) cannot be empty or be longer than 16 characters";
+
+      case ErrorCode_NoCFindHandler:
+        return "No request handler factory for DICOM C-FIND SCP";
+
+      case ErrorCode_NoCMoveHandler:
+        return "No request handler factory for DICOM C-MOVE SCP";
+
+      case ErrorCode_NoCStoreHandler:
+        return "No request handler factory for DICOM C-STORE SCP";
+
+      case ErrorCode_NoApplicationEntityFilter:
+        return "No application entity filter";
+
+      case ErrorCode_NoSopClassOrInstance:
+        return "DicomUserConnection: Unable to find the SOP class and instance";
+
+      case ErrorCode_NoPresentationContext:
+        return "DicomUserConnection: No acceptable presentation context for modality";
+
+      case ErrorCode_DicomFindUnavailable:
+        return "DicomUserConnection: The C-FIND command is not supported by the remote SCP";
+
+      case ErrorCode_DicomMoveUnavailable:
+        return "DicomUserConnection: The C-MOVE command is not supported by the remote SCP";
+
+      case ErrorCode_CannotStoreInstance:
+        return "Cannot store an instance";
+
+      case ErrorCode_CreateDicomNotString:
+        return "Only string values are supported when creating DICOM instances";
+
+      case ErrorCode_CreateDicomOverrideTag:
+        return "Trying to override a value inherited from a parent module";
+
+      case ErrorCode_CreateDicomUseContent:
+        return "Use \"Content\" to inject an image into a new DICOM instance";
+
+      case ErrorCode_CreateDicomNoPayload:
+        return "No payload is present for one instance in the series";
+
+      case ErrorCode_CreateDicomUseDataUriScheme:
+        return "The payload of the DICOM instance must be specified according to Data URI scheme";
+
+      case ErrorCode_CreateDicomBadParent:
+        return "Trying to attach a new DICOM instance to an inexistent resource";
+
+      case ErrorCode_CreateDicomParentIsInstance:
+        return "Trying to attach a new DICOM instance to an instance (must be a series, study or patient)";
+
+      case ErrorCode_CreateDicomParentEncoding:
+        return "Unable to get the encoding of the parent resource";
+
+      case ErrorCode_UnknownModality:
+        return "Unknown modality";
+
+      case ErrorCode_BadJobOrdering:
+        return "Bad ordering of filters in a job";
+
+      case ErrorCode_JsonToLuaTable:
+        return "Cannot convert the given JSON object to a Lua table";
+
+      case ErrorCode_CannotCreateLua:
+        return "Cannot create the Lua context";
+
+      case ErrorCode_CannotExecuteLua:
+        return "Cannot execute a Lua command";
+
+      case ErrorCode_LuaAlreadyExecuted:
+        return "Arguments cannot be pushed after the Lua function is executed";
+
+      case ErrorCode_LuaBadOutput:
+        return "The Lua function does not give the expected number of outputs";
+
+      case ErrorCode_NotLuaPredicate:
+        return "The Lua function is not a predicate (only true/false outputs allowed)";
+
+      case ErrorCode_LuaReturnsNoString:
+        return "The Lua function does not return a string";
+
+      case ErrorCode_StorageAreaAlreadyRegistered:
+        return "Another plugin has already registered a custom storage area";
+
+      case ErrorCode_DatabaseBackendAlreadyRegistered:
+        return "Another plugin has already registered a custom database back-end";
+
+      case ErrorCode_DatabaseNotInitialized:
+        return "Plugin trying to call the database during its initialization";
+
+      case ErrorCode_SslDisabled:
+        return "Orthanc has been built without SSL support";
+
+      case ErrorCode_CannotOrderSlices:
+        return "Unable to order the slices of the series";
+
+      case ErrorCode_NoWorklistHandler:
+        return "No request handler factory for DICOM C-Find Modality SCP";
+
+      case ErrorCode_AlreadyExistingTag:
+        return "Cannot override the value of a tag that already exists";
+
+      default:
+        if (error >= ErrorCode_START_PLUGINS)
+        {
+          return "Error encountered within some plugin";
+        }
+        else
+        {
+          return "Unknown error code";
+        }
+    }
+  }
+
+
+  const char* EnumerationToString(HttpMethod method)
+  {
+    switch (method)
+    {
+      case HttpMethod_Get:
+        return "GET";
+
+      case HttpMethod_Post:
+        return "POST";
+
+      case HttpMethod_Delete:
+        return "DELETE";
+
+      case HttpMethod_Put:
+        return "PUT";
+
+      default:
+        return "?";
+    }
+  }
+
+
+  const char* EnumerationToString(HttpStatus status)
+  {
+    switch (status)
+    {
+    case HttpStatus_100_Continue:
+      return "Continue";
+
+    case HttpStatus_101_SwitchingProtocols:
+      return "Switching Protocols";
+
+    case HttpStatus_102_Processing:
+      return "Processing";
+
+    case HttpStatus_200_Ok:
+      return "OK";
+
+    case HttpStatus_201_Created:
+      return "Created";
+
+    case HttpStatus_202_Accepted:
+      return "Accepted";
+
+    case HttpStatus_203_NonAuthoritativeInformation:
+      return "Non-Authoritative Information";
+
+    case HttpStatus_204_NoContent:
+      return "No Content";
+
+    case HttpStatus_205_ResetContent:
+      return "Reset Content";
+
+    case HttpStatus_206_PartialContent:
+      return "Partial Content";
+
+    case HttpStatus_207_MultiStatus:
+      return "Multi-Status";
+
+    case HttpStatus_208_AlreadyReported:
+      return "Already Reported";
+
+    case HttpStatus_226_IMUsed:
+      return "IM Used";
+
+    case HttpStatus_300_MultipleChoices:
+      return "Multiple Choices";
+
+    case HttpStatus_301_MovedPermanently:
+      return "Moved Permanently";
+
+    case HttpStatus_302_Found:
+      return "Found";
+
+    case HttpStatus_303_SeeOther:
+      return "See Other";
+
+    case HttpStatus_304_NotModified:
+      return "Not Modified";
+
+    case HttpStatus_305_UseProxy:
+      return "Use Proxy";
+
+    case HttpStatus_307_TemporaryRedirect:
+      return "Temporary Redirect";
+
+    case HttpStatus_400_BadRequest:
+      return "Bad Request";
+
+    case HttpStatus_401_Unauthorized:
+      return "Unauthorized";
+
+    case HttpStatus_402_PaymentRequired:
+      return "Payment Required";
+
+    case HttpStatus_403_Forbidden:
+      return "Forbidden";
+
+    case HttpStatus_404_NotFound:
+      return "Not Found";
+
+    case HttpStatus_405_MethodNotAllowed:
+      return "Method Not Allowed";
+
+    case HttpStatus_406_NotAcceptable:
+      return "Not Acceptable";
+
+    case HttpStatus_407_ProxyAuthenticationRequired:
+      return "Proxy Authentication Required";
+
+    case HttpStatus_408_RequestTimeout:
+      return "Request Timeout";
+
+    case HttpStatus_409_Conflict:
+      return "Conflict";
+
+    case HttpStatus_410_Gone:
+      return "Gone";
+
+    case HttpStatus_411_LengthRequired:
+      return "Length Required";
+
+    case HttpStatus_412_PreconditionFailed:
+      return "Precondition Failed";
+
+    case HttpStatus_413_RequestEntityTooLarge:
+      return "Request Entity Too Large";
+
+    case HttpStatus_414_RequestUriTooLong:
+      return "Request-URI Too Long";
+
+    case HttpStatus_415_UnsupportedMediaType:
+      return "Unsupported Media Type";
+
+    case HttpStatus_416_RequestedRangeNotSatisfiable:
+      return "Requested Range Not Satisfiable";
+
+    case HttpStatus_417_ExpectationFailed:
+      return "Expectation Failed";
+
+    case HttpStatus_422_UnprocessableEntity:
+      return "Unprocessable Entity";
+
+    case HttpStatus_423_Locked:
+      return "Locked";
+
+    case HttpStatus_424_FailedDependency:
+      return "Failed Dependency";
+
+    case HttpStatus_426_UpgradeRequired:
+      return "Upgrade Required";
+
+    case HttpStatus_500_InternalServerError:
+      return "Internal Server Error";
+
+    case HttpStatus_501_NotImplemented:
+      return "Not Implemented";
+
+    case HttpStatus_502_BadGateway:
+      return "Bad Gateway";
+
+    case HttpStatus_503_ServiceUnavailable:
+      return "Service Unavailable";
+
+    case HttpStatus_504_GatewayTimeout:
+      return "Gateway Timeout";
+
+    case HttpStatus_505_HttpVersionNotSupported:
+      return "HTTP Version Not Supported";
+
+    case HttpStatus_506_VariantAlsoNegotiates:
+      return "Variant Also Negotiates";
+
+    case HttpStatus_507_InsufficientStorage:
+      return "Insufficient Storage";
+
+    case HttpStatus_509_BandwidthLimitExceeded:
+      return "Bandwidth Limit Exceeded";
+
+    case HttpStatus_510_NotExtended:
+      return "Not Extended";
+
+    default:
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(ResourceType type)
+  {
+    switch (type)
+    {
+      case ResourceType_Patient:
+        return "Patient";
+
+      case ResourceType_Study:
+        return "Study";
+
+      case ResourceType_Series:
+        return "Series";
+
+      case ResourceType_Instance:
+        return "Instance";
+      
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(ImageFormat format)
+  {
+    switch (format)
+    {
+      case ImageFormat_Png:
+        return "Png";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(Encoding encoding)
+  {
+    switch (encoding)
+    {
+      case Encoding_Ascii:
+        return "Ascii";
+
+      case Encoding_Utf8:
+        return "Utf8";
+
+      case Encoding_Latin1:
+        return "Latin1";
+
+      case Encoding_Latin2:
+        return "Latin2";
+
+      case Encoding_Latin3:
+        return "Latin3";
+
+      case Encoding_Latin4:
+        return "Latin4";
+
+      case Encoding_Latin5:
+        return "Latin5";
+
+      case Encoding_Cyrillic:
+        return "Cyrillic";
+
+      case Encoding_Windows1251:
+        return "Windows1251";
+
+      case Encoding_Arabic:
+        return "Arabic";
+
+      case Encoding_Greek:
+        return "Greek";
+
+      case Encoding_Hebrew:
+        return "Hebrew";
+
+      case Encoding_Thai:
+        return "Thai";
+
+      case Encoding_Japanese:
+        return "Japanese";
+
+      case Encoding_Chinese:
+        return "Chinese";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(PhotometricInterpretation photometric)
+  {
+    switch (photometric)
+    {
+      case PhotometricInterpretation_RGB:
+        return "RGB";
+
+      case PhotometricInterpretation_Monochrome1:
+        return "MONOCHROME1";
+
+      case PhotometricInterpretation_Monochrome2:
+        return "MONOCHROME2";
+
+      case PhotometricInterpretation_ARGB:
+        return "ARGB";
+
+      case PhotometricInterpretation_CMYK:
+        return "CMYK";
+
+      case PhotometricInterpretation_HSV:
+        return "HSV";
+
+      case PhotometricInterpretation_Palette:
+        return "PALETTE COLOR";
+
+      case PhotometricInterpretation_YBRFull:
+        return "YBR_FULL";
+
+      case PhotometricInterpretation_YBRFull422:
+        return "YBR_FULL_422";
+
+      case PhotometricInterpretation_YBRPartial420:
+        return "YBR_PARTIAL_420"; 
+
+      case PhotometricInterpretation_YBRPartial422:
+        return "YBR_PARTIAL_422"; 
+
+      case PhotometricInterpretation_YBR_ICT:
+        return "YBR_ICT"; 
+
+      case PhotometricInterpretation_YBR_RCT:
+        return "YBR_RCT"; 
+
+      case PhotometricInterpretation_Unknown:
+        return "Unknown";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(RequestOrigin origin)
+  {
+    switch (origin)
+    {
+      case RequestOrigin_Unknown:
+        return "Unknown";
+
+      case RequestOrigin_DicomProtocol:
+        return "DicomProtocol";
+
+      case RequestOrigin_RestApi:
+        return "RestApi";
+
+      case RequestOrigin_Plugins:
+        return "Plugins";
+
+      case RequestOrigin_Lua:
+        return "Lua";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(LogLevel level)
+  {
+    switch (level)
+    {
+      case LogLevel_Error:
+        return "ERROR";
+
+      case LogLevel_Warning:
+        return "WARNING";
+
+      case LogLevel_Info:
+        return "INFO";
+
+      case LogLevel_Trace:
+        return "TRACE";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(PixelFormat format)
+  {
+    switch (format)
+    {
+      case PixelFormat_RGB24:
+        return "RGB24";
+
+      case PixelFormat_RGBA32:
+        return "RGBA32";
+
+      case PixelFormat_BGRA32:
+        return "BGRA32";
+
+      case PixelFormat_Grayscale8:
+        return "Grayscale (unsigned 8bpp)";
+
+      case PixelFormat_Grayscale16:
+        return "Grayscale (unsigned 16bpp)";
+
+      case PixelFormat_SignedGrayscale16:
+        return "Grayscale (signed 16bpp)";
+
+      case PixelFormat_Float32:
+        return "Grayscale (float 32bpp)";
+
+      case PixelFormat_Grayscale32:
+        return "Grayscale (unsigned 32bpp)";
+
+      case PixelFormat_RGB48:
+        return "RGB48";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(ModalityManufacturer manufacturer)
+  {
+    switch (manufacturer)
+    {
+      case ModalityManufacturer_Generic:
+        return "Generic";
+
+      case ModalityManufacturer_GenericNoWildcardInDates:
+        return "GenericNoWildcardInDates";
+
+      case ModalityManufacturer_GenericNoUniversalWildcard:
+        return "GenericNoUniversalWildcard";
+
+      case ModalityManufacturer_StoreScp:
+        return "StoreScp";
+      
+      case ModalityManufacturer_ClearCanvas:
+        return "ClearCanvas";
+      
+      case ModalityManufacturer_Dcm4Chee:
+        return "Dcm4Chee";
+      
+      case ModalityManufacturer_Vitrea:
+        return "Vitrea";
+      
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(DicomRequestType type)
+  {
+    switch (type)
+    {
+      case DicomRequestType_Echo:
+        return "Echo";
+        break;
+
+      case DicomRequestType_Find:
+        return "Find";
+        break;
+
+      case DicomRequestType_Get:
+        return "Get";
+        break;
+
+      case DicomRequestType_Move:
+        return "Move";
+        break;
+
+      case DicomRequestType_Store:
+        return "Store";
+        break;
+
+      default: 
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(TransferSyntax syntax)
+  {
+    switch (syntax)
+    {
+      case TransferSyntax_Deflated:
+        return "Deflated";
+
+      case TransferSyntax_Jpeg:
+        return "JPEG";
+
+      case TransferSyntax_Jpeg2000:
+        return "JPEG2000";
+
+      case TransferSyntax_JpegLossless:
+        return "JPEG Lossless";
+
+      case TransferSyntax_Jpip:
+        return "JPIP";
+
+      case TransferSyntax_Mpeg2:
+        return "MPEG2";
+
+      case TransferSyntax_Rle:
+        return "RLE";
+
+      default: 
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(DicomVersion version)
+  {
+    switch (version)
+    {
+      case DicomVersion_2008:
+        return "2008";
+        break;
+
+      case DicomVersion_2017c:
+        return "2017c";
+        break;
+
+      default: 
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  Encoding StringToEncoding(const char* encoding)
+  {
+    std::string s(encoding);
+    Toolbox::ToUpperCase(s);
+
+    if (s == "UTF8")
+    {
+      return Encoding_Utf8;
+    }
+
+    if (s == "ASCII")
+    {
+      return Encoding_Ascii;
+    }
+
+    if (s == "LATIN1")
+    {
+      return Encoding_Latin1;
+    }
+
+    if (s == "LATIN2")
+    {
+      return Encoding_Latin2;
+    }
+
+    if (s == "LATIN3")
+    {
+      return Encoding_Latin3;
+    }
+
+    if (s == "LATIN4")
+    {
+      return Encoding_Latin4;
+    }
+
+    if (s == "LATIN5")
+    {
+      return Encoding_Latin5;
+    }
+
+    if (s == "CYRILLIC")
+    {
+      return Encoding_Cyrillic;
+    }
+
+    if (s == "WINDOWS1251")
+    {
+      return Encoding_Windows1251;
+    }
+
+    if (s == "ARABIC")
+    {
+      return Encoding_Arabic;
+    }
+
+    if (s == "GREEK")
+    {
+      return Encoding_Greek;
+    }
+
+    if (s == "HEBREW")
+    {
+      return Encoding_Hebrew;
+    }
+
+    if (s == "THAI")
+    {
+      return Encoding_Thai;
+    }
+
+    if (s == "JAPANESE")
+    {
+      return Encoding_Japanese;
+    }
+
+    if (s == "CHINESE")
+    {
+      return Encoding_Chinese;
+    }
+
+    throw OrthancException(ErrorCode_ParameterOutOfRange);
+  }
+
+
+  ResourceType StringToResourceType(const char* type)
+  {
+    std::string s(type);
+    Toolbox::ToUpperCase(s);
+
+    if (s == "PATIENT" || s == "PATIENTS")
+    {
+      return ResourceType_Patient;
+    }
+    else if (s == "STUDY" || s == "STUDIES")
+    {
+      return ResourceType_Study;
+    }
+    else if (s == "SERIES")
+    {
+      return ResourceType_Series;
+    }
+    else if (s == "INSTANCE"  || s == "IMAGE" || 
+             s == "INSTANCES" || s == "IMAGES")
+    {
+      return ResourceType_Instance;
+    }
+
+    throw OrthancException(ErrorCode_ParameterOutOfRange);
+  }
+
+
+  ImageFormat StringToImageFormat(const char* format)
+  {
+    std::string s(format);
+    Toolbox::ToUpperCase(s);
+
+    if (s == "PNG")
+    {
+      return ImageFormat_Png;
+    }
+
+    throw OrthancException(ErrorCode_ParameterOutOfRange);
+  }
+
+
+  LogLevel StringToLogLevel(const char *level)
+  {
+    if (strcmp(level, "ERROR") == 0)
+    {
+      return LogLevel_Error;
+    }
+    else if (strcmp(level, "WARNING") == 0)
+    {
+      return LogLevel_Warning;
+    }
+    else if (strcmp(level, "INFO") == 0)
+    {
+      return LogLevel_Info;
+    }
+    else if (strcmp(level, "TRACE") == 0)
+    {
+      return LogLevel_Trace;
+    }
+    else 
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
+  ValueRepresentation StringToValueRepresentation(const std::string& vr,
+                                                  bool throwIfUnsupported)
+  {
+    if (vr == "AE")
+    {
+      return ValueRepresentation_ApplicationEntity;
+    }
+    else if (vr == "AS")
+    {
+      return ValueRepresentation_AgeString;
+    }
+    else if (vr == "AT")
+    {
+      return ValueRepresentation_AttributeTag;
+    }
+    else if (vr == "CS")
+    {
+      return ValueRepresentation_CodeString;
+    }
+    else if (vr == "DA")
+    {
+      return ValueRepresentation_Date;
+    }
+    else if (vr == "DS")
+    {
+      return ValueRepresentation_DecimalString;
+    }
+    else if (vr == "DT")
+    {
+      return ValueRepresentation_DateTime;
+    }
+    else if (vr == "FL")
+    {
+      return ValueRepresentation_FloatingPointSingle;
+    }
+    else if (vr == "FD")
+    {
+      return ValueRepresentation_FloatingPointDouble;
+    }
+    else if (vr == "IS")
+    {
+      return ValueRepresentation_IntegerString;
+    }
+    else if (vr == "LO")
+    {
+      return ValueRepresentation_LongString;
+    }
+    else if (vr == "LT")
+    {
+      return ValueRepresentation_LongText;
+    }
+    else if (vr == "OB")
+    {
+      return ValueRepresentation_OtherByte;
+    }
+    else if (vr == "OD")
+    {
+      return ValueRepresentation_OtherDouble;
+    }
+    else if (vr == "OF")
+    {
+      return ValueRepresentation_OtherFloat;
+    }
+    else if (vr == "OL")
+    {
+      return ValueRepresentation_OtherLong;
+    }
+    else if (vr == "OW")
+    {
+      return ValueRepresentation_OtherWord;
+    }
+    else if (vr == "PN")
+    {
+      return ValueRepresentation_PersonName;
+    }
+    else if (vr == "SH")
+    {
+      return ValueRepresentation_ShortString;
+    }
+    else if (vr == "SL")
+    {
+      return ValueRepresentation_SignedLong;
+    }
+    else if (vr == "SQ")
+    {
+      return ValueRepresentation_Sequence;
+    }
+    else if (vr == "SS")
+    {
+      return ValueRepresentation_SignedShort;
+    }
+    else if (vr == "ST")
+    {
+      return ValueRepresentation_ShortText;
+    }
+    else if (vr == "TM")
+    {
+      return ValueRepresentation_Time;
+    }
+    else if (vr == "UC")
+    {
+      return ValueRepresentation_UnlimitedCharacters;
+    }
+    else if (vr == "UI")
+    {
+      return ValueRepresentation_UniqueIdentifier;
+    }
+    else if (vr == "UL")
+    {
+      return ValueRepresentation_UnsignedLong;
+    }
+    else if (vr == "UN")
+    {
+      return ValueRepresentation_Unknown;
+    }
+    else if (vr == "UR")
+    {
+      return ValueRepresentation_UniversalResource;
+    }
+    else if (vr == "US")
+    {
+      return ValueRepresentation_UnsignedShort;
+    }
+    else if (vr == "UT")
+    {
+      return ValueRepresentation_UnlimitedText;
+    }
+    else
+    {
+      std::string s = "Unsupported value representation encountered: " + vr;
+
+      if (throwIfUnsupported)
+      {
+        LOG(ERROR) << s;
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+      else
+      {
+        LOG(INFO) << s;
+        return ValueRepresentation_NotSupported;
+      }
+    }
+  }
+
+
+  PhotometricInterpretation StringToPhotometricInterpretation(const char* value)
+  {
+    // http://dicom.nema.org/medical/dicom/2017a/output/chtml/part03/sect_C.7.6.3.html#sect_C.7.6.3.1.2
+    std::string s(value);
+
+    if (s == "MONOCHROME1")
+    {
+      return PhotometricInterpretation_Monochrome1;
+    }
+    
+    if (s == "MONOCHROME2")
+    {
+      return PhotometricInterpretation_Monochrome2;
+    }
+
+    if (s == "PALETTE COLOR")
+    {
+      return PhotometricInterpretation_Palette;
+    }
+    
+    if (s == "RGB")
+    {
+      return PhotometricInterpretation_RGB;
+    }
+    
+    if (s == "HSV")
+    {
+      return PhotometricInterpretation_HSV;
+    }
+    
+    if (s == "ARGB")
+    {
+      return PhotometricInterpretation_ARGB;
+    }    
+
+    if (s == "CMYK")
+    {
+      return PhotometricInterpretation_CMYK;
+    }    
+
+    if (s == "YBR_FULL")
+    {
+      return PhotometricInterpretation_YBRFull;
+    }
+    
+    if (s == "YBR_FULL_422")
+    {
+      return PhotometricInterpretation_YBRFull422;
+    }
+    
+    if (s == "YBR_PARTIAL_422")
+    {
+      return PhotometricInterpretation_YBRPartial422;
+    }
+    
+    if (s == "YBR_PARTIAL_420")
+    {
+      return PhotometricInterpretation_YBRPartial420;
+    }
+    
+    if (s == "YBR_ICT")
+    {
+      return PhotometricInterpretation_YBR_ICT;
+    }
+    
+    if (s == "YBR_RCT")
+    {
+      return PhotometricInterpretation_YBR_RCT;
+    }
+
+    throw OrthancException(ErrorCode_ParameterOutOfRange);
+  }
+  
+
+  ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer)
+  {
+    ModalityManufacturer result;
+    bool obsolete = false;
+    
+    if (manufacturer == "Generic")
+    {
+      return ModalityManufacturer_Generic;
+    }
+    else if (manufacturer == "GenericNoWildcardInDates")
+    {
+      return ModalityManufacturer_GenericNoWildcardInDates;
+    }
+    else if (manufacturer == "GenericNoUniversalWildcard")
+    {
+      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;
+    }
+    else if (manufacturer == "AgfaImpax" ||
+             manufacturer == "SyngoVia")
+    {
+      result = ModalityManufacturer_GenericNoWildcardInDates;
+      obsolete = true;
+    }
+    else if (manufacturer == "EFilm2" ||
+             manufacturer == "MedInria")
+    {
+      result = ModalityManufacturer_Generic;
+      obsolete = true;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    if (obsolete)
+    {
+      LOG(WARNING) << "The \"" << manufacturer << "\" manufacturer is obsolete since "
+                   << "Orthanc 1.3.0. To guarantee compatibility with future Orthanc "
+                   << "releases, you should replace it by \""
+                   << EnumerationToString(result)
+                   << "\" in your configuration file.";
+    }
+
+    return result;
+  }
+
+
+  DicomVersion StringToDicomVersion(const std::string& version)
+  {
+    if (version == "2008")
+    {
+      return DicomVersion_2008;
+    }
+    else if (version == "2017c")
+    {
+      return DicomVersion_2017c;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  unsigned int GetBytesPerPixel(PixelFormat format)
+  {
+    switch (format)
+    {
+      case PixelFormat_Grayscale8:
+        return 1;
+
+      case PixelFormat_Grayscale16:
+      case PixelFormat_SignedGrayscale16:
+        return 2;
+
+      case PixelFormat_RGB24:
+        return 3;
+
+      case PixelFormat_RGBA32:
+      case PixelFormat_BGRA32:
+      case PixelFormat_Grayscale32:
+        return 4;
+
+      case PixelFormat_Float32:
+        assert(sizeof(float) == 4);
+        return 4;
+
+      case PixelFormat_RGB48:
+        return 6;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  bool GetDicomEncoding(Encoding& encoding,
+                        const char* specificCharacterSet)
+  {
+    std::string s = Toolbox::StripSpaces(specificCharacterSet);
+    Toolbox::ToUpperCase(s);
+
+    // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2
+    // https://github.com/dcm4che/dcm4che/blob/master/dcm4che-core/src/main/java/org/dcm4che3/data/SpecificCharacterSet.java
+    if (s == "ISO_IR 6" ||
+        s == "ISO 2022 IR 6")
+    {
+      encoding = Encoding_Ascii;
+    }
+    else if (s == "ISO_IR 192")
+    {
+      encoding = Encoding_Utf8;
+    }
+    else if (s == "ISO_IR 100" ||
+             s == "ISO 2022 IR 100")
+    {
+      encoding = Encoding_Latin1;
+    }
+    else if (s == "ISO_IR 101" ||
+             s == "ISO 2022 IR 101")
+    {
+      encoding = Encoding_Latin2;
+    }
+    else if (s == "ISO_IR 109" ||
+             s == "ISO 2022 IR 109")
+    {
+      encoding = Encoding_Latin3;
+    }
+    else if (s == "ISO_IR 110" ||
+             s == "ISO 2022 IR 110")
+    {
+      encoding = Encoding_Latin4;
+    }
+    else if (s == "ISO_IR 148" ||
+             s == "ISO 2022 IR 148")
+    {
+      encoding = Encoding_Latin5;
+    }
+    else if (s == "ISO_IR 144" ||
+             s == "ISO 2022 IR 144")
+    {
+      encoding = Encoding_Cyrillic;
+    }
+    else if (s == "ISO_IR 127" ||
+             s == "ISO 2022 IR 127")
+    {
+      encoding = Encoding_Arabic;
+    }
+    else if (s == "ISO_IR 126" ||
+             s == "ISO 2022 IR 126")
+    {
+      encoding = Encoding_Greek;
+    }
+    else if (s == "ISO_IR 138" ||
+             s == "ISO 2022 IR 138")
+    {
+      encoding = Encoding_Hebrew;
+    }
+    else if (s == "ISO_IR 166" || s == "ISO 2022 IR 166")
+    {
+      encoding = Encoding_Thai;
+    }
+    else if (s == "ISO_IR 13" || s == "ISO 2022 IR 13")
+    {
+      encoding = Encoding_Japanese;
+    }
+    else if (s == "GB18030" || s == "GBK")
+    {
+      /**
+       * According to tumashu@163.com, "In China, many dicom file's
+       * 0008,0005 tag is set as "GBK", instead of "GB18030", GBK is a
+       * subset of GB18030, and which is used frequently in China,
+       * suggest support it."
+       * https://groups.google.com/d/msg/orthanc-users/WMM8LMbjpUc/02-1f_yFCgAJ
+       **/
+      encoding = Encoding_Chinese;
+    }
+    /*
+      else if (s == "ISO 2022 IR 149")
+      {
+      TODO
+      }
+      else if (s == "ISO 2022 IR 159")
+      {
+      TODO
+      }
+      else if (s == "ISO 2022 IR 87")
+      {
+      TODO
+      }
+    */
+    else
+    {
+      return false;
+    }
+
+    // The encoding was properly detected
+    return true;
+  }
+
+
+  ResourceType GetChildResourceType(ResourceType type)
+  {
+    switch (type)
+    {
+      case ResourceType_Patient:
+        return ResourceType_Study;
+
+      case ResourceType_Study:
+        return ResourceType_Series;
+        
+      case ResourceType_Series:
+        return ResourceType_Instance;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  ResourceType GetParentResourceType(ResourceType type)
+  {
+    switch (type)
+    {
+      case ResourceType_Study:
+        return ResourceType_Patient;
+        
+      case ResourceType_Series:
+        return ResourceType_Study;
+
+      case ResourceType_Instance:
+        return ResourceType_Series;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  DicomModule GetModule(ResourceType type)
+  {
+    switch (type)
+    {
+      case ResourceType_Patient:
+        return DicomModule_Patient;
+
+      case ResourceType_Study:
+        return DicomModule_Study;
+        
+      case ResourceType_Series:
+        return DicomModule_Series;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+
+  const char* GetDicomSpecificCharacterSet(Encoding encoding)
+  {
+    // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2
+    switch (encoding)
+    {
+      case Encoding_Ascii:
+        return "ISO_IR 6";
+
+      case Encoding_Utf8:
+        return "ISO_IR 192";
+
+      case Encoding_Latin1:
+        return "ISO_IR 100";
+
+      case Encoding_Latin2:
+        return "ISO_IR 101";
+
+      case Encoding_Latin3:
+        return "ISO_IR 109";
+
+      case Encoding_Latin4:
+        return "ISO_IR 110";
+
+      case Encoding_Latin5:
+        return "ISO_IR 148";
+
+      case Encoding_Cyrillic:
+        return "ISO_IR 144";
+
+      case Encoding_Arabic:
+        return "ISO_IR 127";
+
+      case Encoding_Greek:
+        return "ISO_IR 126";
+
+      case Encoding_Hebrew:
+        return "ISO_IR 138";
+
+      case Encoding_Japanese:
+        return "ISO_IR 13";
+
+      case Encoding_Chinese:
+        return "GB18030";
+
+      case Encoding_Thai:
+        return "ISO_IR 166";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  // This function is autogenerated by the script
+  // "Resources/GenerateErrorCodes.py"
+  HttpStatus ConvertErrorCodeToHttpStatus(ErrorCode error)
+  {
+    switch (error)
+    {
+      case ErrorCode_Success:
+        return HttpStatus_200_Ok;
+
+      case ErrorCode_ParameterOutOfRange:
+        return HttpStatus_400_BadRequest;
+
+      case ErrorCode_BadParameterType:
+        return HttpStatus_400_BadRequest;
+
+      case ErrorCode_InexistentItem:
+        return HttpStatus_404_NotFound;
+
+      case ErrorCode_BadRequest:
+        return HttpStatus_400_BadRequest;
+
+      case ErrorCode_UriSyntax:
+        return HttpStatus_400_BadRequest;
+
+      case ErrorCode_InexistentFile:
+        return HttpStatus_404_NotFound;
+
+      case ErrorCode_BadFileFormat:
+        return HttpStatus_400_BadRequest;
+
+      case ErrorCode_UnknownResource:
+        return HttpStatus_404_NotFound;
+
+      case ErrorCode_InexistentTag:
+        return HttpStatus_404_NotFound;
+
+      case ErrorCode_BadJson:
+        return HttpStatus_400_BadRequest;
+
+      case ErrorCode_Unauthorized:
+        return HttpStatus_401_Unauthorized;
+
+      case ErrorCode_NotAcceptable:
+        return HttpStatus_406_NotAcceptable;
+
+      case ErrorCode_DatabaseUnavailable:
+        return HttpStatus_503_ServiceUnavailable;
+
+      default:
+        return HttpStatus_500_InternalServerError;
+    }
+  }
+
+
+  bool IsUserContentType(FileContentType type)
+  {
+    return (type >= FileContentType_StartUser &&
+            type <= FileContentType_EndUser);
+  }
+
+
+  bool IsBinaryValueRepresentation(ValueRepresentation vr)
+  {
+    // http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html
+
+    switch (vr)
+    {
+      case ValueRepresentation_ApplicationEntity:     // AE
+      case ValueRepresentation_AgeString:             // AS
+      case ValueRepresentation_CodeString:            // CS
+      case ValueRepresentation_Date:                  // DA
+      case ValueRepresentation_DecimalString:         // DS
+      case ValueRepresentation_DateTime:              // DT
+      case ValueRepresentation_IntegerString:         // IS
+      case ValueRepresentation_LongString:            // LO
+      case ValueRepresentation_LongText:              // LT
+      case ValueRepresentation_PersonName:            // PN
+      case ValueRepresentation_ShortString:           // SH
+      case ValueRepresentation_ShortText:             // ST
+      case ValueRepresentation_Time:                  // TM
+      case ValueRepresentation_UnlimitedCharacters:   // UC
+      case ValueRepresentation_UniqueIdentifier:      // UI (UID)
+      case ValueRepresentation_UniversalResource:     // UR (URI or URL)
+      case ValueRepresentation_UnlimitedText:         // UT
+      {
+        return false;
+      }
+
+      /**
+       * Below are all the VR whose character repertoire is tagged as
+       * "not applicable"
+       **/
+      case ValueRepresentation_AttributeTag:          // AT (2 x uint16_t)
+      case ValueRepresentation_FloatingPointSingle:   // FL (float)
+      case ValueRepresentation_FloatingPointDouble:   // FD (double)
+      case ValueRepresentation_OtherByte:             // OB
+      case ValueRepresentation_OtherDouble:           // OD
+      case ValueRepresentation_OtherFloat:            // OF
+      case ValueRepresentation_OtherLong:             // OL
+      case ValueRepresentation_OtherWord:             // OW
+      case ValueRepresentation_SignedLong:            // SL (int32_t)
+      case ValueRepresentation_Sequence:              // SQ
+      case ValueRepresentation_SignedShort:           // SS (int16_t)
+      case ValueRepresentation_UnsignedLong:          // UL (uint32_t)
+      case ValueRepresentation_Unknown:               // UN
+      case ValueRepresentation_UnsignedShort:         // US (uint16_t)
+      {
+        return true;
+      }
+
+      case ValueRepresentation_NotSupported:
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }  
+
+
+  static boost::mutex  defaultEncodingMutex_;  // Should not be necessary
+  static Encoding      defaultEncoding_ = ORTHANC_DEFAULT_DICOM_ENCODING;
+  
+  Encoding GetDefaultDicomEncoding()
+  {
+    boost::mutex::scoped_lock lock(defaultEncodingMutex_);
+    return defaultEncoding_;
+  }
+
+  void SetDefaultDicomEncoding(Encoding encoding)
+  {
+    std::string name = EnumerationToString(encoding);
+    
+    {
+      boost::mutex::scoped_lock lock(defaultEncodingMutex_);
+      defaultEncoding_ = encoding;
+    }
+
+    LOG(INFO) << "Default encoding for DICOM was changed to: " << name;
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Enumerations.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,662 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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>
+
+
+#if defined(_MSC_VER)
+#  define ORTHANC_FORCE_INLINE __forceinline
+#elif defined(__GNUC__) || defined(__clang__) || defined(__EMSCRIPTEN__)
+#  define ORTHANC_FORCE_INLINE inline __attribute((always_inline))
+#else
+#  error Please support your compiler here
+#endif
+
+
+namespace Orthanc
+{
+  enum Endianness
+  {
+    Endianness_Unknown,
+    Endianness_Big,
+    Endianness_Little
+  };
+
+  // This enumeration is autogenerated by the script
+  // "Resources/GenerateErrorCodes.py"
+  enum ErrorCode
+  {
+    ErrorCode_InternalError = -1    /*!< Internal error */,
+    ErrorCode_Success = 0    /*!< Success */,
+    ErrorCode_Plugin = 1    /*!< Error encountered within the plugin engine */,
+    ErrorCode_NotImplemented = 2    /*!< Not implemented yet */,
+    ErrorCode_ParameterOutOfRange = 3    /*!< Parameter out of range */,
+    ErrorCode_NotEnoughMemory = 4    /*!< The server hosting Orthanc is running out of memory */,
+    ErrorCode_BadParameterType = 5    /*!< Bad type for a parameter */,
+    ErrorCode_BadSequenceOfCalls = 6    /*!< Bad sequence of calls */,
+    ErrorCode_InexistentItem = 7    /*!< Accessing an inexistent item */,
+    ErrorCode_BadRequest = 8    /*!< Bad request */,
+    ErrorCode_NetworkProtocol = 9    /*!< Error in the network protocol */,
+    ErrorCode_SystemCommand = 10    /*!< Error while calling a system command */,
+    ErrorCode_Database = 11    /*!< Error with the database engine */,
+    ErrorCode_UriSyntax = 12    /*!< Badly formatted URI */,
+    ErrorCode_InexistentFile = 13    /*!< Inexistent file */,
+    ErrorCode_CannotWriteFile = 14    /*!< Cannot write to file */,
+    ErrorCode_BadFileFormat = 15    /*!< Bad file format */,
+    ErrorCode_Timeout = 16    /*!< Timeout */,
+    ErrorCode_UnknownResource = 17    /*!< Unknown resource */,
+    ErrorCode_IncompatibleDatabaseVersion = 18    /*!< Incompatible version of the database */,
+    ErrorCode_FullStorage = 19    /*!< The file storage is full */,
+    ErrorCode_CorruptedFile = 20    /*!< Corrupted file (e.g. inconsistent MD5 hash) */,
+    ErrorCode_InexistentTag = 21    /*!< Inexistent tag */,
+    ErrorCode_ReadOnly = 22    /*!< Cannot modify a read-only data structure */,
+    ErrorCode_IncompatibleImageFormat = 23    /*!< Incompatible format of the images */,
+    ErrorCode_IncompatibleImageSize = 24    /*!< Incompatible size of the images */,
+    ErrorCode_SharedLibrary = 25    /*!< Error while using a shared library (plugin) */,
+    ErrorCode_UnknownPluginService = 26    /*!< Plugin invoking an unknown service */,
+    ErrorCode_UnknownDicomTag = 27    /*!< Unknown DICOM tag */,
+    ErrorCode_BadJson = 28    /*!< Cannot parse a JSON document */,
+    ErrorCode_Unauthorized = 29    /*!< Bad credentials were provided to an HTTP request */,
+    ErrorCode_BadFont = 30    /*!< Badly formatted font file */,
+    ErrorCode_DatabasePlugin = 31    /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */,
+    ErrorCode_StorageAreaPlugin = 32    /*!< Error in the plugin implementing a custom storage area */,
+    ErrorCode_EmptyRequest = 33    /*!< The request is empty */,
+    ErrorCode_NotAcceptable = 34    /*!< Cannot send a response which is acceptable according to the Accept HTTP header */,
+    ErrorCode_NullPointer = 35    /*!< Cannot handle a NULL pointer */,
+    ErrorCode_DatabaseUnavailable = 36    /*!< The database is currently not available (probably a transient situation) */,
+    ErrorCode_SQLiteNotOpened = 1000    /*!< SQLite: The database is not opened */,
+    ErrorCode_SQLiteAlreadyOpened = 1001    /*!< SQLite: Connection is already open */,
+    ErrorCode_SQLiteCannotOpen = 1002    /*!< SQLite: Unable to open the database */,
+    ErrorCode_SQLiteStatementAlreadyUsed = 1003    /*!< SQLite: This cached statement is already being referred to */,
+    ErrorCode_SQLiteExecute = 1004    /*!< SQLite: Cannot execute a command */,
+    ErrorCode_SQLiteRollbackWithoutTransaction = 1005    /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */,
+    ErrorCode_SQLiteCommitWithoutTransaction = 1006    /*!< SQLite: Committing a nonexistent transaction */,
+    ErrorCode_SQLiteRegisterFunction = 1007    /*!< SQLite: Unable to register a function */,
+    ErrorCode_SQLiteFlush = 1008    /*!< SQLite: Unable to flush the database */,
+    ErrorCode_SQLiteCannotRun = 1009    /*!< SQLite: Cannot run a cached statement */,
+    ErrorCode_SQLiteCannotStep = 1010    /*!< SQLite: Cannot step over a cached statement */,
+    ErrorCode_SQLiteBindOutOfRange = 1011    /*!< SQLite: Bing a value while out of range (serious error) */,
+    ErrorCode_SQLitePrepareStatement = 1012    /*!< SQLite: Cannot prepare a cached statement */,
+    ErrorCode_SQLiteTransactionAlreadyStarted = 1013    /*!< SQLite: Beginning the same transaction twice */,
+    ErrorCode_SQLiteTransactionCommit = 1014    /*!< SQLite: Failure when committing the transaction */,
+    ErrorCode_SQLiteTransactionBegin = 1015    /*!< SQLite: Cannot start a transaction */,
+    ErrorCode_DirectoryOverFile = 2000    /*!< The directory to be created is already occupied by a regular file */,
+    ErrorCode_FileStorageCannotWrite = 2001    /*!< Unable to create a subdirectory or a file in the file storage */,
+    ErrorCode_DirectoryExpected = 2002    /*!< The specified path does not point to a directory */,
+    ErrorCode_HttpPortInUse = 2003    /*!< The TCP port of the HTTP server is privileged or already in use */,
+    ErrorCode_DicomPortInUse = 2004    /*!< The TCP port of the DICOM server is privileged or already in use */,
+    ErrorCode_BadHttpStatusInRest = 2005    /*!< This HTTP status is not allowed in a REST API */,
+    ErrorCode_RegularFileExpected = 2006    /*!< The specified path does not point to a regular file */,
+    ErrorCode_PathToExecutable = 2007    /*!< Unable to get the path to the executable */,
+    ErrorCode_MakeDirectory = 2008    /*!< Cannot create a directory */,
+    ErrorCode_BadApplicationEntityTitle = 2009    /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */,
+    ErrorCode_NoCFindHandler = 2010    /*!< No request handler factory for DICOM C-FIND SCP */,
+    ErrorCode_NoCMoveHandler = 2011    /*!< No request handler factory for DICOM C-MOVE SCP */,
+    ErrorCode_NoCStoreHandler = 2012    /*!< No request handler factory for DICOM C-STORE SCP */,
+    ErrorCode_NoApplicationEntityFilter = 2013    /*!< No application entity filter */,
+    ErrorCode_NoSopClassOrInstance = 2014    /*!< DicomUserConnection: Unable to find the SOP class and instance */,
+    ErrorCode_NoPresentationContext = 2015    /*!< DicomUserConnection: No acceptable presentation context for modality */,
+    ErrorCode_DicomFindUnavailable = 2016    /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */,
+    ErrorCode_DicomMoveUnavailable = 2017    /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */,
+    ErrorCode_CannotStoreInstance = 2018    /*!< Cannot store an instance */,
+    ErrorCode_CreateDicomNotString = 2019    /*!< Only string values are supported when creating DICOM instances */,
+    ErrorCode_CreateDicomOverrideTag = 2020    /*!< Trying to override a value inherited from a parent module */,
+    ErrorCode_CreateDicomUseContent = 2021    /*!< Use \"Content\" to inject an image into a new DICOM instance */,
+    ErrorCode_CreateDicomNoPayload = 2022    /*!< No payload is present for one instance in the series */,
+    ErrorCode_CreateDicomUseDataUriScheme = 2023    /*!< The payload of the DICOM instance must be specified according to Data URI scheme */,
+    ErrorCode_CreateDicomBadParent = 2024    /*!< Trying to attach a new DICOM instance to an inexistent resource */,
+    ErrorCode_CreateDicomParentIsInstance = 2025    /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */,
+    ErrorCode_CreateDicomParentEncoding = 2026    /*!< Unable to get the encoding of the parent resource */,
+    ErrorCode_UnknownModality = 2027    /*!< Unknown modality */,
+    ErrorCode_BadJobOrdering = 2028    /*!< Bad ordering of filters in a job */,
+    ErrorCode_JsonToLuaTable = 2029    /*!< Cannot convert the given JSON object to a Lua table */,
+    ErrorCode_CannotCreateLua = 2030    /*!< Cannot create the Lua context */,
+    ErrorCode_CannotExecuteLua = 2031    /*!< Cannot execute a Lua command */,
+    ErrorCode_LuaAlreadyExecuted = 2032    /*!< Arguments cannot be pushed after the Lua function is executed */,
+    ErrorCode_LuaBadOutput = 2033    /*!< The Lua function does not give the expected number of outputs */,
+    ErrorCode_NotLuaPredicate = 2034    /*!< The Lua function is not a predicate (only true/false outputs allowed) */,
+    ErrorCode_LuaReturnsNoString = 2035    /*!< The Lua function does not return a string */,
+    ErrorCode_StorageAreaAlreadyRegistered = 2036    /*!< Another plugin has already registered a custom storage area */,
+    ErrorCode_DatabaseBackendAlreadyRegistered = 2037    /*!< Another plugin has already registered a custom database back-end */,
+    ErrorCode_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
+    ErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
+    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_START_PLUGINS = 1000000
+  };
+
+  enum LogLevel
+  {
+    LogLevel_Error,
+    LogLevel_Warning,
+    LogLevel_Info,
+    LogLevel_Trace
+  };
+
+
+  /**
+   * {summary}{The memory layout of the pixels (resp. voxels) of a 2D (resp. 3D) image.}
+   **/
+  enum PixelFormat
+  {
+    /**
+     * {summary}{Color image in RGB24 format.}
+     * {description}{This format describes a color image. The pixels are stored in 3
+     * consecutive bytes. The memory layout is RGB.}
+     **/
+    PixelFormat_RGB24 = 1,
+
+    /**
+     * {summary}{Color image in RGBA32 format.}
+     * {description}{This format describes a color image. The pixels are stored in 4
+     * consecutive bytes. The memory layout is RGBA.}
+     **/
+    PixelFormat_RGBA32 = 2,
+
+    /**
+     * {summary}{Graylevel 8bpp image.}
+     * {description}{The image is graylevel. Each pixel is unsigned and stored in one byte.}
+     **/
+    PixelFormat_Grayscale8 = 3,
+      
+    /**
+     * {summary}{Graylevel, unsigned 16bpp image.}
+     * {description}{The image is graylevel. Each pixel is unsigned and stored in two bytes.}
+     **/
+    PixelFormat_Grayscale16 = 4,
+      
+    /**
+     * {summary}{Graylevel, signed 16bpp image.}
+     * {description}{The image is graylevel. Each pixel is signed and stored in two bytes.}
+     **/
+    PixelFormat_SignedGrayscale16 = 5,
+      
+    /**
+     * {summary}{Graylevel, floating-point image.}
+     * {description}{The image is graylevel. Each pixel is floating-point and stored in 4 bytes.}
+     **/
+    PixelFormat_Float32 = 6,
+
+    // This is the memory layout for Cairo (for internal use in Stone of Orthanc)
+    PixelFormat_BGRA32 = 7,
+
+    /**
+     * {summary}{Graylevel, unsigned 32bpp image.}
+     * {description}{The image is graylevel. Each pixel is unsigned and stored in 4 bytes.}
+     **/
+    PixelFormat_Grayscale32 = 8,
+    
+    /**
+     * {summary}{Color image in RGB48 format.}
+     * {description}{This format describes a color image. The pixels are stored in 6
+     * consecutive bytes. The memory layout is RGB.}
+     **/
+    PixelFormat_RGB48 = 9
+  };
+
+
+  /**
+   * {summary}{The extraction mode specifies the way the values of the pixels are scaled when downloading a 2D image.}
+   **/
+  enum ImageExtractionMode
+  {
+    /**
+     * {summary}{Rescaled to 8bpp.}
+     * {description}{The minimum value of the image is set to 0, and its maximum value is set to 255.}
+     **/
+    ImageExtractionMode_Preview = 1,
+
+    /**
+     * {summary}{Truncation to the [0, 255] range.}
+     **/
+    ImageExtractionMode_UInt8 = 2,
+
+    /**
+     * {summary}{Truncation to the [0, 65535] range.}
+     **/
+    ImageExtractionMode_UInt16 = 3,
+
+    /**
+     * {summary}{Truncation to the [-32768, 32767] range.}
+     **/
+    ImageExtractionMode_Int16 = 4
+  };
+
+
+  /**
+   * Most common, non-joke and non-experimental HTTP status codes
+   * http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
+   **/
+  enum HttpStatus
+  {
+    HttpStatus_None = -1,
+
+    // 1xx Informational
+    HttpStatus_100_Continue = 100,
+    HttpStatus_101_SwitchingProtocols = 101,
+    HttpStatus_102_Processing = 102,
+
+    // 2xx Success
+    HttpStatus_200_Ok = 200,
+    HttpStatus_201_Created = 201,
+    HttpStatus_202_Accepted = 202,
+    HttpStatus_203_NonAuthoritativeInformation = 203,
+    HttpStatus_204_NoContent = 204,
+    HttpStatus_205_ResetContent = 205,
+    HttpStatus_206_PartialContent = 206,
+    HttpStatus_207_MultiStatus = 207,
+    HttpStatus_208_AlreadyReported = 208,
+    HttpStatus_226_IMUsed = 226,
+
+    // 3xx Redirection
+    HttpStatus_300_MultipleChoices = 300,
+    HttpStatus_301_MovedPermanently = 301,
+    HttpStatus_302_Found = 302,
+    HttpStatus_303_SeeOther = 303,
+    HttpStatus_304_NotModified = 304,
+    HttpStatus_305_UseProxy = 305,
+    HttpStatus_307_TemporaryRedirect = 307,
+
+    // 4xx Client Error
+    HttpStatus_400_BadRequest = 400,
+    HttpStatus_401_Unauthorized = 401,
+    HttpStatus_402_PaymentRequired = 402,
+    HttpStatus_403_Forbidden = 403,
+    HttpStatus_404_NotFound = 404,
+    HttpStatus_405_MethodNotAllowed = 405,
+    HttpStatus_406_NotAcceptable = 406,
+    HttpStatus_407_ProxyAuthenticationRequired = 407,
+    HttpStatus_408_RequestTimeout = 408,
+    HttpStatus_409_Conflict = 409,
+    HttpStatus_410_Gone = 410,
+    HttpStatus_411_LengthRequired = 411,
+    HttpStatus_412_PreconditionFailed = 412,
+    HttpStatus_413_RequestEntityTooLarge = 413,
+    HttpStatus_414_RequestUriTooLong = 414,
+    HttpStatus_415_UnsupportedMediaType = 415,
+    HttpStatus_416_RequestedRangeNotSatisfiable = 416,
+    HttpStatus_417_ExpectationFailed = 417,
+    HttpStatus_422_UnprocessableEntity = 422,
+    HttpStatus_423_Locked = 423,
+    HttpStatus_424_FailedDependency = 424,
+    HttpStatus_426_UpgradeRequired = 426,
+
+    // 5xx Server Error
+    HttpStatus_500_InternalServerError = 500,
+    HttpStatus_501_NotImplemented = 501,
+    HttpStatus_502_BadGateway = 502,
+    HttpStatus_503_ServiceUnavailable = 503,
+    HttpStatus_504_GatewayTimeout = 504,
+    HttpStatus_505_HttpVersionNotSupported = 505,
+    HttpStatus_506_VariantAlsoNegotiates = 506,
+    HttpStatus_507_InsufficientStorage = 507,
+    HttpStatus_509_BandwidthLimitExceeded = 509,
+    HttpStatus_510_NotExtended = 510
+  };
+
+
+  enum HttpMethod
+  {
+    HttpMethod_Get = 0,
+    HttpMethod_Post = 1,
+    HttpMethod_Delete = 2,
+    HttpMethod_Put = 3
+  };
+
+
+  enum ImageFormat
+  {
+    ImageFormat_Png = 1
+  };
+
+
+  // https://en.wikipedia.org/wiki/HTTP_compression
+  enum HttpCompression
+  {
+    HttpCompression_None,
+    HttpCompression_Deflate,
+    HttpCompression_Gzip
+  };
+
+
+  // Specific Character Sets
+  // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2
+  enum Encoding
+  {
+    Encoding_Ascii,
+    Encoding_Utf8,
+    Encoding_Latin1,
+    Encoding_Latin2,
+    Encoding_Latin3,
+    Encoding_Latin4,
+    Encoding_Latin5,                        // Turkish
+    Encoding_Cyrillic,
+    Encoding_Windows1251,                   // Windows-1251 (commonly used for Cyrillic)
+    Encoding_Arabic,
+    Encoding_Greek,
+    Encoding_Hebrew,
+    Encoding_Thai,                          // TIS 620-2533
+    Encoding_Japanese,                      // JIS X 0201 (Shift JIS): Katakana
+    Encoding_Chinese                        // GB18030 - Chinese simplified
+    //Encoding_JapaneseKanji,               // Multibyte - JIS X 0208: Kanji
+    //Encoding_JapaneseSupplementaryKanji,  // Multibyte - JIS X 0212: Supplementary Kanji set
+    //Encoding_Korean,                      // Multibyte - KS X 1001: Hangul and Hanja
+  };
+
+
+  // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.7.6.3.1.2
+  enum PhotometricInterpretation
+  {
+    PhotometricInterpretation_ARGB,  // Retired
+    PhotometricInterpretation_CMYK,  // Retired
+    PhotometricInterpretation_HSV,   // Retired
+    PhotometricInterpretation_Monochrome1,
+    PhotometricInterpretation_Monochrome2,
+    PhotometricInterpretation_Palette,
+    PhotometricInterpretation_RGB,
+    PhotometricInterpretation_YBRFull,
+    PhotometricInterpretation_YBRFull422,
+    PhotometricInterpretation_YBRPartial420,
+    PhotometricInterpretation_YBRPartial422,
+    PhotometricInterpretation_YBR_ICT,
+    PhotometricInterpretation_YBR_RCT,
+    PhotometricInterpretation_Unknown
+  };
+
+  enum DicomModule
+  {
+    DicomModule_Patient,
+    DicomModule_Study,
+    DicomModule_Series,
+    DicomModule_Instance,
+    DicomModule_Image
+  };
+
+  enum RequestOrigin
+  {
+    RequestOrigin_Unknown,
+    RequestOrigin_DicomProtocol,
+    RequestOrigin_RestApi,
+    RequestOrigin_Plugins,
+    RequestOrigin_Lua
+  };
+
+  enum ServerBarrierEvent
+  {
+    ServerBarrierEvent_Stop,
+    ServerBarrierEvent_Reload  // SIGHUP signal: reload configuration file
+  };
+
+  enum FileMode
+  {
+    FileMode_ReadBinary,
+    FileMode_WriteBinary
+  };
+
+  /**
+   * The value representations Orthanc knows about. They correspond to
+   * the DICOM 2016b version of the standard.
+   * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html
+   **/
+  enum ValueRepresentation
+  {
+    ValueRepresentation_ApplicationEntity = 1,     // AE
+    ValueRepresentation_AgeString = 2,             // AS
+    ValueRepresentation_AttributeTag = 3,          // AT (2 x uint16_t)
+    ValueRepresentation_CodeString = 4,            // CS
+    ValueRepresentation_Date = 5,                  // DA
+    ValueRepresentation_DecimalString = 6,         // DS
+    ValueRepresentation_DateTime = 7,              // DT
+    ValueRepresentation_FloatingPointSingle = 8,   // FL (float)
+    ValueRepresentation_FloatingPointDouble = 9,   // FD (double)
+    ValueRepresentation_IntegerString = 10,        // IS
+    ValueRepresentation_LongString = 11,           // LO
+    ValueRepresentation_LongText = 12,             // LT
+    ValueRepresentation_OtherByte = 13,            // OB
+    ValueRepresentation_OtherDouble = 14,          // OD
+    ValueRepresentation_OtherFloat = 15,           // OF
+    ValueRepresentation_OtherLong = 16,            // OL
+    ValueRepresentation_OtherWord = 17,            // OW
+    ValueRepresentation_PersonName = 18,           // PN
+    ValueRepresentation_ShortString = 19,          // SH
+    ValueRepresentation_SignedLong = 20,           // SL (int32_t)
+    ValueRepresentation_Sequence = 21,             // SQ
+    ValueRepresentation_SignedShort = 22,          // SS (int16_t)
+    ValueRepresentation_ShortText = 23,            // ST
+    ValueRepresentation_Time = 24,                 // TM
+    ValueRepresentation_UnlimitedCharacters = 25,  // UC
+    ValueRepresentation_UniqueIdentifier = 26,     // UI (UID)
+    ValueRepresentation_UnsignedLong = 27,         // UL (uint32_t)
+    ValueRepresentation_Unknown = 28,              // UN
+    ValueRepresentation_UniversalResource = 29,    // UR (URI or URL)
+    ValueRepresentation_UnsignedShort = 30,        // US (uint16_t)
+    ValueRepresentation_UnlimitedText = 31,        // UT
+    ValueRepresentation_NotSupported               // Not supported by Orthanc, or tag not in dictionary
+  };
+
+  enum DicomReplaceMode
+  {
+    DicomReplaceMode_InsertIfAbsent,
+    DicomReplaceMode_ThrowIfAbsent,
+    DicomReplaceMode_IgnoreIfAbsent
+  };
+
+  enum DicomToJsonFormat
+  {
+    DicomToJsonFormat_Full,
+    DicomToJsonFormat_Short,
+    DicomToJsonFormat_Human
+  };
+
+  enum DicomToJsonFlags
+  {
+    DicomToJsonFlags_IncludeBinary         = (1 << 0),
+    DicomToJsonFlags_IncludePrivateTags    = (1 << 1),
+    DicomToJsonFlags_IncludeUnknownTags    = (1 << 2),
+    DicomToJsonFlags_IncludePixelData      = (1 << 3),
+    DicomToJsonFlags_ConvertBinaryToAscii  = (1 << 4),
+    DicomToJsonFlags_ConvertBinaryToNull   = (1 << 5),
+
+    // Some predefined combinations
+    DicomToJsonFlags_None     = 0,
+    DicomToJsonFlags_Default  = (DicomToJsonFlags_IncludeBinary |
+                                 DicomToJsonFlags_IncludePixelData | 
+                                 DicomToJsonFlags_IncludePrivateTags | 
+                                 DicomToJsonFlags_IncludeUnknownTags | 
+                                 DicomToJsonFlags_ConvertBinaryToNull)
+  };
+  
+  enum DicomFromJsonFlags
+  {
+    DicomFromJsonFlags_DecodeDataUriScheme = (1 << 0),
+    DicomFromJsonFlags_GenerateIdentifiers = (1 << 1)
+  };
+  
+  enum DicomVersion
+  {
+    DicomVersion_2008,
+    DicomVersion_2017c
+  };
+
+  enum ModalityManufacturer
+  {
+    ModalityManufacturer_Generic,
+    ModalityManufacturer_GenericNoWildcardInDates,
+    ModalityManufacturer_GenericNoUniversalWildcard,
+    ModalityManufacturer_StoreScp,
+    ModalityManufacturer_ClearCanvas,
+    ModalityManufacturer_Dcm4Chee,
+    ModalityManufacturer_Vitrea
+  };
+
+  enum DicomRequestType
+  {
+    DicomRequestType_Echo,
+    DicomRequestType_Find,
+    DicomRequestType_Get,
+    DicomRequestType_Move,
+    DicomRequestType_Store
+  };
+
+  enum TransferSyntax
+  {
+    TransferSyntax_Deflated,
+    TransferSyntax_Jpeg,
+    TransferSyntax_Jpeg2000,
+    TransferSyntax_JpegLossless,
+    TransferSyntax_Jpip,
+    TransferSyntax_Mpeg2,
+    TransferSyntax_Rle
+  };
+
+
+  /**
+   * WARNING: Do not change the explicit values in the enumerations
+   * below this point. This would result in incompatible databases
+   * between versions of Orthanc!
+   **/
+
+  enum CompressionType
+  {
+    /**
+     * Buffer/file that is stored as-is, in a raw fashion, without
+     * compression.
+     **/
+    CompressionType_None = 1,
+
+    /**
+     * Buffer that is compressed using the "deflate" algorithm (RFC
+     * 1951), wrapped inside the zlib data format (RFC 1950), prefixed
+     * with a "uint64_t" (8 bytes) that encodes the size of the
+     * uncompressed buffer. If the compressed buffer is empty, its
+     * represents an empty uncompressed buffer. This format is
+     * internal to Orthanc. If the 8 first bytes are skipped AND the
+     * buffer is non-empty, the buffer is compatible with the
+     * "deflate" HTTP compression.
+     **/
+    CompressionType_ZlibWithSize = 2
+  };
+
+  enum FileContentType
+  {
+    // If you add a value below, insert it in "PluginStorageArea" in
+    // the file "Plugins/Engine/OrthancPlugins.cpp"
+    FileContentType_Unknown = 0,
+    FileContentType_Dicom = 1,
+    FileContentType_DicomAsJson = 2,
+
+    // Make sure that the value "65535" can be stored into this enumeration
+    FileContentType_StartUser = 1024,
+    FileContentType_EndUser = 65535
+  };
+
+  enum ResourceType
+  {
+    ResourceType_Patient = 1,
+    ResourceType_Study = 2,
+    ResourceType_Series = 3,
+    ResourceType_Instance = 4
+  };
+
+
+  const char* EnumerationToString(ErrorCode code);
+
+  const char* EnumerationToString(HttpMethod method);
+
+  const char* EnumerationToString(HttpStatus status);
+
+  const char* EnumerationToString(ResourceType type);
+
+  const char* EnumerationToString(ImageFormat format);
+
+  const char* EnumerationToString(Encoding encoding);
+
+  const char* EnumerationToString(PhotometricInterpretation photometric);
+
+  const char* EnumerationToString(LogLevel level);
+
+  const char* EnumerationToString(RequestOrigin origin);
+
+  const char* EnumerationToString(PixelFormat format);
+
+  const char* EnumerationToString(ModalityManufacturer manufacturer);
+
+  const char* EnumerationToString(DicomRequestType type);
+
+  const char* EnumerationToString(TransferSyntax syntax);
+
+  const char* EnumerationToString(DicomVersion version);
+
+  Encoding StringToEncoding(const char* encoding);
+
+  ResourceType StringToResourceType(const char* type);
+
+  ImageFormat StringToImageFormat(const char* format);
+
+  LogLevel StringToLogLevel(const char* level);
+
+  ValueRepresentation StringToValueRepresentation(const std::string& vr,
+                                                  bool throwIfUnsupported);
+
+  PhotometricInterpretation StringToPhotometricInterpretation(const char* value);
+
+  ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer);
+
+  DicomVersion StringToDicomVersion(const std::string& version);
+  
+  unsigned int GetBytesPerPixel(PixelFormat format);
+
+  bool GetDicomEncoding(Encoding& encoding,
+                        const char* specificCharacterSet);
+
+  ResourceType GetChildResourceType(ResourceType type);
+
+  ResourceType GetParentResourceType(ResourceType type);
+
+  DicomModule GetModule(ResourceType type);
+
+  const char* GetDicomSpecificCharacterSet(Encoding encoding);
+
+  HttpStatus ConvertErrorCodeToHttpStatus(ErrorCode error);
+
+  bool IsUserContentType(FileContentType type);
+
+  bool IsBinaryValueRepresentation(ValueRepresentation vr);
+  
+  Encoding GetDefaultDicomEncoding();
+
+  void SetDefaultDicomEncoding(Encoding encoding);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/FileStorage/FileInfo.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,132 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 <stdint.h>
+#include "../Enumerations.h"
+
+namespace Orthanc
+{
+  struct FileInfo
+  {
+  private:
+    std::string uuid_;
+    FileContentType contentType_;
+
+    uint64_t uncompressedSize_;
+    std::string uncompressedMD5_;
+
+    CompressionType compressionType_;
+    uint64_t compressedSize_;
+    std::string compressedMD5_;
+
+  public:
+    FileInfo()
+    {
+    }
+
+    /**
+     * Constructor for an uncompressed attachment.
+     **/
+    FileInfo(const std::string& uuid,
+             FileContentType contentType,
+             uint64_t size,
+             const std::string& md5) :
+      uuid_(uuid),
+      contentType_(contentType),
+      uncompressedSize_(size),
+      uncompressedMD5_(md5),
+      compressionType_(CompressionType_None),
+      compressedSize_(size),
+      compressedMD5_(md5)
+    {
+    }
+
+    /**
+     * Constructor for a compressed attachment.
+     **/
+    FileInfo(const std::string& uuid,
+             FileContentType contentType,
+             uint64_t uncompressedSize,
+             const std::string& uncompressedMD5,
+             CompressionType compressionType,
+             uint64_t compressedSize,
+             const std::string& compressedMD5) :
+      uuid_(uuid),
+      contentType_(contentType),
+      uncompressedSize_(uncompressedSize),
+      uncompressedMD5_(uncompressedMD5),
+      compressionType_(compressionType),
+      compressedSize_(compressedSize),
+      compressedMD5_(compressedMD5)
+    {
+    }
+
+    const std::string& GetUuid() const
+    {
+      return uuid_;
+    }
+
+    FileContentType GetContentType() const
+    {
+      return contentType_;
+    }
+
+    uint64_t GetUncompressedSize() const
+    {
+      return uncompressedSize_;
+    }
+
+    CompressionType GetCompressionType() const
+    {
+      return compressionType_;
+    }
+
+    uint64_t GetCompressedSize() const
+    {
+      return compressedSize_;
+    }
+
+    const std::string& GetCompressedMD5() const
+    {
+      return compressedMD5_;
+    }
+
+    const std::string& GetUncompressedMD5() const
+    {
+      return uncompressedMD5_;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/FileStorage/FilesystemStorage.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,274 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "FilesystemStorage.h"
+
+// http://stackoverflow.com/questions/1576272/storing-large-number-of-files-in-file-system
+// http://stackoverflow.com/questions/446358/storing-a-large-number-of-images
+
+#include "../Logging.h"
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+#include "../SystemToolbox.h"
+
+#include <boost/filesystem/fstream.hpp>
+
+
+static std::string ToString(const boost::filesystem::path& p)
+{
+#if BOOST_HAS_FILESYSTEM_V3 == 1
+  return p.filename().string();
+#else
+  return p.filename();
+#endif
+}
+
+
+namespace Orthanc
+{
+  boost::filesystem::path FilesystemStorage::GetPath(const std::string& uuid) const
+  {
+    namespace fs = boost::filesystem;
+
+    if (!Toolbox::IsUuid(uuid))
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    fs::path path = root_;
+
+    path /= std::string(&uuid[0], &uuid[2]);
+    path /= std::string(&uuid[2], &uuid[4]);
+    path /= uuid;
+
+#if BOOST_HAS_FILESYSTEM_V3 == 1
+    path.make_preferred();
+#endif
+
+    return path;
+  }
+
+  FilesystemStorage::FilesystemStorage(std::string root)
+  {
+    //root_ = boost::filesystem::absolute(root).string();
+    root_ = root;
+
+    SystemToolbox::MakeDirectory(root);
+  }
+
+
+
+  static const char* GetDescriptionInternal(FileContentType content)
+  {
+    // This function is for logging only (internal use), a more
+    // fully-featured version is available in ServerEnumerations.cpp
+    switch (content)
+    {
+      case FileContentType_Unknown:
+        return "Unknown";
+
+      case FileContentType_Dicom:
+        return "DICOM";
+
+      case FileContentType_DicomAsJson:
+        return "JSON summary of DICOM";
+
+      default:
+        return "User-defined";
+    }
+  }
+
+
+  void FilesystemStorage::Create(const std::string& uuid,
+                                 const void* content, 
+                                 size_t size,
+                                 FileContentType type)
+  {
+    LOG(INFO) << "Creating attachment \"" << uuid << "\" of \"" << GetDescriptionInternal(type) 
+              << "\" type (size: " << (size / (1024 * 1024) + 1) << "MB)";
+
+    boost::filesystem::path path;
+    
+    path = GetPath(uuid);
+
+    if (boost::filesystem::exists(path))
+    {
+      // Extremely unlikely case: This Uuid has already been created
+      // in the past.
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    if (boost::filesystem::exists(path.parent_path()))
+    {
+      if (!boost::filesystem::is_directory(path.parent_path()))
+      {
+        throw OrthancException(ErrorCode_DirectoryOverFile);
+      }
+    }
+    else
+    {
+      if (!boost::filesystem::create_directories(path.parent_path()))
+      {
+        throw OrthancException(ErrorCode_FileStorageCannotWrite);
+      }
+    }
+
+    SystemToolbox::WriteFile(content, size, path.string());
+  }
+
+
+  void FilesystemStorage::Read(std::string& content,
+                               const std::string& uuid,
+                               FileContentType type)
+  {
+    LOG(INFO) << "Reading attachment \"" << uuid << "\" of \"" << GetDescriptionInternal(type) 
+              << "\" content type";
+
+    content.clear();
+    SystemToolbox::ReadFile(content, GetPath(uuid).string());
+  }
+
+
+  uintmax_t FilesystemStorage::GetSize(const std::string& uuid) const
+  {
+    boost::filesystem::path path = GetPath(uuid);
+    return boost::filesystem::file_size(path);
+  }
+
+
+
+  void FilesystemStorage::ListAllFiles(std::set<std::string>& result) const
+  {
+    namespace fs = boost::filesystem;
+
+    result.clear();
+
+    if (fs::exists(root_) && fs::is_directory(root_))
+    {
+      for (fs::recursive_directory_iterator current(root_), end; current != end ; ++current)
+      {
+        if (SystemToolbox::IsRegularFile(current->path().string()))
+        {
+          try
+          {
+            fs::path d = current->path();
+            std::string uuid = ToString(d);
+            if (Toolbox::IsUuid(uuid))
+            {
+              fs::path p0 = d.parent_path().parent_path().parent_path();
+              std::string p1 = ToString(d.parent_path().parent_path());
+              std::string p2 = ToString(d.parent_path());
+              if (p1.length() == 2 &&
+                  p2.length() == 2 &&
+                  p1 == uuid.substr(0, 2) &&
+                  p2 == uuid.substr(2, 2) &&
+                  p0 == root_)
+              {
+                result.insert(uuid);
+              }
+            }
+          }
+          catch (fs::filesystem_error)
+          {
+          }
+        }
+      }
+    }
+  }
+
+
+  void FilesystemStorage::Clear()
+  {
+    namespace fs = boost::filesystem;
+    typedef std::set<std::string> List;
+
+    List result;
+    ListAllFiles(result);
+
+    for (List::const_iterator it = result.begin(); it != result.end(); ++it)
+    {
+      Remove(*it, FileContentType_Unknown /*ignored in this class*/);
+    }
+  }
+
+
+  void FilesystemStorage::Remove(const std::string& uuid,
+                                 FileContentType type)
+  {
+    LOG(INFO) << "Deleting attachment \"" << uuid << "\" of type " << static_cast<int>(type);
+
+    namespace fs = boost::filesystem;
+
+    fs::path p = GetPath(uuid);
+
+    try
+    {
+      fs::remove(p);
+    }
+    catch (...)
+    {
+      // Ignore the error
+    }
+
+    // Remove the two parent directories, ignoring the error code if
+    // these directories are not empty
+
+    try
+    {
+#if BOOST_HAS_FILESYSTEM_V3 == 1
+      boost::system::error_code err;
+      fs::remove(p.parent_path(), err);
+      fs::remove(p.parent_path().parent_path(), err);
+#else
+      fs::remove(p.parent_path());
+      fs::remove(p.parent_path().parent_path());
+#endif
+    }
+    catch (...)
+    {
+      // Ignore the error
+    }
+  }
+
+
+  uintmax_t FilesystemStorage::GetCapacity() const
+  {
+    return boost::filesystem::space(root_).capacity;
+  }
+
+  uintmax_t FilesystemStorage::GetAvailableSpace() const
+  {
+    return boost::filesystem::space(root_).available;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/FileStorage/FilesystemStorage.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,88 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 !defined(ORTHANC_SANDBOXED)
+#  error The macro ORTHANC_SANDBOXED must be defined
+#endif
+
+#if ORTHANC_SANDBOXED == 1
+#  error The class FilesystemStorage cannot be used in sandboxed environments
+#endif
+
+#include "IStorageArea.h"
+
+#include <stdint.h>
+#include <boost/filesystem.hpp>
+#include <set>
+
+namespace Orthanc
+{
+  class FilesystemStorage : public IStorageArea
+  {
+    // TODO REMOVE THIS
+    friend class FilesystemHttpSender;
+    friend class FileStorageAccessor;
+
+  private:
+    boost::filesystem::path root_;
+
+    boost::filesystem::path GetPath(const std::string& uuid) const;
+
+  public:
+    explicit FilesystemStorage(std::string root);
+
+    virtual void Create(const std::string& uuid,
+                        const void* content, 
+                        size_t size,
+                        FileContentType type);
+
+    virtual void Read(std::string& content,
+                      const std::string& uuid,
+                      FileContentType type);
+
+    virtual void Remove(const std::string& uuid,
+                        FileContentType type);
+
+    void ListAllFiles(std::set<std::string>& result) const;
+
+    uintmax_t GetSize(const std::string& uuid) const;
+
+    void Clear();
+
+    uintmax_t GetCapacity() const;
+
+    uintmax_t GetAvailableSpace() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/FileStorage/IStorageArea.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,62 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "../Enumerations.h"
+
+#include <string>
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class IStorageArea : public boost::noncopyable
+  {
+  public:
+    virtual ~IStorageArea()
+    {
+    }
+
+    virtual void Create(const std::string& uuid,
+                        const void* content,
+                        size_t size,
+                        FileContentType type) = 0;
+
+    virtual void Read(std::string& content,
+                      const std::string& uuid,
+                      FileContentType type) = 0;
+
+    virtual void Remove(const std::string& uuid,
+                        FileContentType type) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/FileStorage/StorageAccessor.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,204 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "StorageAccessor.h"
+
+#include "../Compression/ZlibCompressor.h"
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+#include "../SystemToolbox.h"
+
+#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1
+#  include "../HttpServer/HttpStreamTranscoder.h"
+#endif
+
+namespace Orthanc
+{
+  FileInfo StorageAccessor::Write(const void* data,
+                                  size_t size,
+                                  FileContentType type,
+                                  CompressionType compression,
+                                  bool storeMd5)
+  {
+    std::string uuid = SystemToolbox::GenerateUuid();
+
+    std::string md5;
+
+    if (storeMd5)
+    {
+      Toolbox::ComputeMD5(md5, data, size);
+    }
+
+    switch (compression)
+    {
+      case CompressionType_None:
+      {
+        area_.Create(uuid, data, size, type);
+        return FileInfo(uuid, type, size, md5);
+      }
+
+      case CompressionType_ZlibWithSize:
+      {
+        ZlibCompressor zlib;
+
+        std::string compressed;
+        zlib.Compress(compressed, data, size);
+
+        std::string compressedMD5;
+      
+        if (storeMd5)
+        {
+          Toolbox::ComputeMD5(compressedMD5, compressed);
+        }
+
+        if (compressed.size() > 0)
+        {
+          area_.Create(uuid, &compressed[0], compressed.size(), type);
+        }
+        else
+        {
+          area_.Create(uuid, NULL, 0, type);
+        }
+
+        return FileInfo(uuid, type, size, md5,
+                        CompressionType_ZlibWithSize, compressed.size(), compressedMD5);
+      }
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void StorageAccessor::Read(std::string& content,
+                             const FileInfo& info)
+  {
+    switch (info.GetCompressionType())
+    {
+      case CompressionType_None:
+      {
+        area_.Read(content, info.GetUuid(), info.GetContentType());
+        break;
+      }
+
+      case CompressionType_ZlibWithSize:
+      {
+        ZlibCompressor zlib;
+
+        std::string compressed;
+        area_.Read(compressed, info.GetUuid(), info.GetContentType());
+        IBufferCompressor::Uncompress(content, zlib, compressed);
+        break;
+      }
+
+      default:
+      {
+        throw OrthancException(ErrorCode_NotImplemented);
+      }
+    }
+
+    // TODO Check the validity of the uncompressed MD5?
+  }
+
+
+  void StorageAccessor::Read(Json::Value& content,
+                             const FileInfo& info)
+  {
+    std::string s;
+    Read(s, info);
+
+    Json::Reader reader;
+    if (!reader.parse(s, content))
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+
+
+#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1
+  void StorageAccessor::SetupSender(BufferHttpSender& sender,
+                                    const FileInfo& info,
+                                    const std::string& mime)
+  {
+    area_.Read(sender.GetBuffer(), info.GetUuid(), info.GetContentType());
+    sender.SetContentType(mime);
+
+    const char* extension;
+    switch (info.GetContentType())
+    {
+      case FileContentType_Dicom:
+        extension = ".dcm";
+        break;
+
+      case FileContentType_DicomAsJson:
+        extension = ".json";
+        break;
+
+      default:
+        // Non-standard content type
+        extension = "";
+    }
+
+    sender.SetContentFilename(info.GetUuid() + std::string(extension));
+  }
+#endif
+
+
+#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1
+  void StorageAccessor::AnswerFile(HttpOutput& output,
+                                   const FileInfo& info,
+                                   const std::string& mime)
+  {
+    BufferHttpSender sender;
+    SetupSender(sender, info, mime);
+  
+    HttpStreamTranscoder transcoder(sender, info.GetCompressionType());
+    output.Answer(transcoder);
+  }
+#endif
+
+
+#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1
+  void StorageAccessor::AnswerFile(RestApiOutput& output,
+                                   const FileInfo& info,
+                                   const std::string& mime)
+  {
+    BufferHttpSender sender;
+    SetupSender(sender, info, mime);
+  
+    HttpStreamTranscoder transcoder(sender, info.GetCompressionType());
+    output.AnswerStream(transcoder);
+  }
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/FileStorage/StorageAccessor.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,120 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 !defined(ORTHANC_SANDBOXED)
+#  error The macro ORTHANC_SANDBOXED must be defined
+#endif
+
+#if ORTHANC_SANDBOXED == 1
+#  error The class StorageAccessor cannot be used in sandboxed environments
+#endif
+
+#if !defined(ORTHANC_ENABLE_CIVETWEB)
+#  error Macro ORTHANC_ENABLE_CIVETWEB must be defined to use this file
+#endif
+
+#if !defined(ORTHANC_ENABLE_MONGOOSE)
+#  error Macro ORTHANC_ENABLE_MONGOOSE must be defined to use this file
+#endif
+
+#include "IStorageArea.h"
+#include "FileInfo.h"
+
+#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1
+#  include "../HttpServer/BufferHttpSender.h"
+#  include "../RestApi/RestApiOutput.h"
+#endif
+
+#include <vector>
+#include <string>
+#include <boost/noncopyable.hpp>
+#include <stdint.h>
+#include <json/value.h>
+
+namespace Orthanc
+{
+  class StorageAccessor : boost::noncopyable
+  {
+  private:
+    IStorageArea&  area_;
+
+#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1
+    void SetupSender(BufferHttpSender& sender,
+                     const FileInfo& info,
+                     const std::string& mime);
+#endif
+
+  public:
+    StorageAccessor(IStorageArea& area) : area_(area)
+    {
+    }
+
+    FileInfo Write(const void* data,
+                   size_t size,
+                   FileContentType type,
+                   CompressionType compression,
+                   bool storeMd5);
+
+    FileInfo Write(const std::string& data, 
+                   FileContentType type,
+                   CompressionType compression,
+                   bool storeMd5)
+    {
+      return Write((data.size() == 0 ? NULL : data.c_str()),
+                   data.size(), type, compression, storeMd5);
+    }
+
+    void Read(std::string& content,
+              const FileInfo& info);
+
+    void Read(Json::Value& content,
+              const FileInfo& info);
+
+    void Remove(const FileInfo& info)
+    {
+      area_.Remove(info.GetUuid(), info.GetContentType());
+    }
+
+#if ORTHANC_ENABLE_CIVETWEB == 1 || ORTHANC_ENABLE_MONGOOSE == 1
+    void AnswerFile(HttpOutput& output,
+                    const FileInfo& info,
+                    const std::string& mime);
+
+    void AnswerFile(RestApiOutput& output,
+                    const FileInfo& info,
+                    const std::string& mime);
+#endif
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/HttpClient.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,845 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "HttpClient.h"
+
+#include "Toolbox.h"
+#include "OrthancException.h"
+#include "Logging.h"
+#include "ChunkedBuffer.h"
+#include "SystemToolbox.h"
+
+#include <string.h>
+#include <curl/curl.h>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/thread/mutex.hpp>
+
+
+#if ORTHANC_ENABLE_SSL == 1
+// For OpenSSL initialization and finalization
+#  include <openssl/conf.h>
+#  include <openssl/engine.h>
+#  include <openssl/err.h>
+#  include <openssl/evp.h>
+#  include <openssl/ssl.h>
+#endif
+
+
+#if ORTHANC_ENABLE_PKCS11 == 1
+#  include "Pkcs11.h"
+#endif
+
+
+extern "C"
+{
+  static CURLcode GetHttpStatus(CURLcode code, CURL* curl, long* status)
+  {
+    if (code == CURLE_OK)
+    {
+      code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, status);
+      return code;
+    }
+    else
+    {
+      *status = 0;
+      return code;
+    }
+  }
+
+  // This is a dummy wrapper function to suppress any OpenSSL-related
+  // problem in valgrind. Inlining is prevented.
+#if defined(__GNUC__) || defined(__clang__)
+    __attribute__((noinline)) 
+#endif
+    static CURLcode OrthancHttpClientPerformSSL(CURL* curl, long* status)
+  {
+#if ORTHANC_ENABLE_SSL == 1
+    return GetHttpStatus(curl_easy_perform(curl), curl, status);
+#else
+    LOG(ERROR) << "Orthanc was compiled without SSL support, cannot make HTTPS request";
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+#endif
+  }
+}
+
+
+
+namespace Orthanc
+{
+  class HttpClient::GlobalParameters
+  {
+  private:
+    boost::mutex    mutex_;
+    bool            httpsVerifyPeers_;
+    std::string     httpsCACertificates_;
+    std::string     proxy_;
+    long            timeout_;
+
+    GlobalParameters() : 
+      httpsVerifyPeers_(true),
+      timeout_(0)
+    {
+    }
+
+  public:
+    // Singleton pattern
+    static GlobalParameters& GetInstance()
+    {
+      static GlobalParameters parameters;
+      return parameters;
+    }
+
+    void ConfigureSsl(bool httpsVerifyPeers,
+                      const std::string& httpsCACertificates)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      httpsVerifyPeers_ = httpsVerifyPeers;
+      httpsCACertificates_ = httpsCACertificates;
+    }
+
+    void GetSslConfiguration(bool& httpsVerifyPeers,
+                             std::string& httpsCACertificates)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      httpsVerifyPeers = httpsVerifyPeers_;
+      httpsCACertificates = httpsCACertificates_;
+    }
+
+    void SetDefaultProxy(const std::string& proxy)
+    {
+      LOG(INFO) << "Setting the default proxy for HTTP client connections: " << proxy;
+
+      {
+        boost::mutex::scoped_lock lock(mutex_);
+        proxy_ = proxy;
+      }
+    }
+
+    void GetDefaultProxy(std::string& target)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      target = proxy_;
+    }
+
+    void SetDefaultTimeout(long seconds)
+    {
+      LOG(INFO) << "Setting the default timeout for HTTP client connections: " << seconds << " seconds";
+
+      {
+        boost::mutex::scoped_lock lock(mutex_);
+        timeout_ = seconds;
+      }
+    }
+
+    long GetDefaultTimeout()
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      return timeout_;
+    }
+
+#if ORTHANC_ENABLE_PKCS11 == 1
+    bool IsPkcs11Initialized()
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      return Pkcs11::IsInitialized();
+    }
+
+    void InitializePkcs11(const std::string& module,
+                          const std::string& pin,
+                          bool verbose)
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+      Pkcs11::Initialize(module, pin, verbose);
+    }
+#endif
+  };
+
+
+  struct HttpClient::PImpl
+  {
+    CURL* curl_;
+    struct curl_slist *defaultPostHeaders_;
+    struct curl_slist *userHeaders_;
+  };
+
+
+  static void ThrowException(HttpStatus status)
+  {
+    switch (status)
+    {
+      case HttpStatus_400_BadRequest:
+        throw OrthancException(ErrorCode_BadRequest);
+
+      case HttpStatus_401_Unauthorized:
+      case HttpStatus_403_Forbidden:
+        throw OrthancException(ErrorCode_Unauthorized);
+
+      case HttpStatus_404_NotFound:
+        throw OrthancException(ErrorCode_UnknownResource);
+
+      default:
+        throw OrthancException(ErrorCode_NetworkProtocol);
+    }
+  }
+
+
+  static CURLcode CheckCode(CURLcode code)
+  {
+    if (code == CURLE_NOT_BUILT_IN)
+    {
+      LOG(ERROR) << "Your libcurl does not contain a required feature, "
+                 << "please recompile Orthanc with -DUSE_SYSTEM_CURL=OFF";
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    if (code != CURLE_OK)
+    {
+      LOG(ERROR) << "libCURL error: " + std::string(curl_easy_strerror(code));
+      throw OrthancException(ErrorCode_NetworkProtocol);
+    }
+
+    return code;
+  }
+
+
+  static size_t CurlBodyCallback(void *buffer, size_t size, size_t nmemb, void *payload)
+  {
+    ChunkedBuffer& target = *(static_cast<ChunkedBuffer*>(payload));
+
+    size_t length = size * nmemb;
+    if (length == 0)
+    {
+      return 0;
+    }
+    else
+    {
+      target.AddChunk(buffer, length);
+      return length;
+    }
+  }
+
+
+  struct CurlHeaderParameters
+  {
+    bool lowerCase_;
+    HttpClient::HttpHeaders* headers_;
+  };
+
+
+  static size_t CurlHeaderCallback(void *buffer, size_t size, size_t nmemb, void *payload)
+  {
+    CurlHeaderParameters& parameters = *(static_cast<CurlHeaderParameters*>(payload));
+    assert(parameters.headers_ != NULL);
+
+    size_t length = size * nmemb;
+    if (length == 0)
+    {
+      return 0;
+    }
+    else
+    {
+      std::string s(reinterpret_cast<const char*>(buffer), length);
+      std::size_t colon = s.find(':');
+      std::size_t eol = s.find("\r\n");
+      if (colon != std::string::npos &&
+          eol != std::string::npos)
+      {
+        std::string tmp(s.substr(0, colon));
+
+        if (parameters.lowerCase_)
+        {
+          Toolbox::ToLowerCase(tmp);
+        }
+
+        std::string key = Toolbox::StripSpaces(tmp);
+
+        if (!key.empty())
+        {
+          std::string value = Toolbox::StripSpaces(s.substr(colon + 1, eol));
+          (*parameters.headers_) [key] = value;
+        }
+      }
+
+      return length;
+    }
+  }
+
+
+  void HttpClient::Setup()
+  {
+    pimpl_->userHeaders_ = NULL;
+    pimpl_->defaultPostHeaders_ = NULL;
+    if ((pimpl_->defaultPostHeaders_ = curl_slist_append(pimpl_->defaultPostHeaders_, "Expect:")) == NULL)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+
+    pimpl_->curl_ = curl_easy_init();
+    if (!pimpl_->curl_)
+    {
+      curl_slist_free_all(pimpl_->defaultPostHeaders_);
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEFUNCTION, &CurlBodyCallback));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADER, 0));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1));
+
+    // This fixes the "longjmp causes uninitialized stack frame" crash
+    // that happens on modern Linux versions.
+    // http://stackoverflow.com/questions/9191668/error-longjmp-causes-uninitialized-stack-frame
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOSIGNAL, 1));
+
+    url_ = "";
+    method_ = HttpMethod_Get;
+    lastStatus_ = HttpStatus_200_Ok;
+    SetVerbose(false);
+    timeout_ = GlobalParameters::GetInstance().GetDefaultTimeout();
+    GlobalParameters::GetInstance().GetDefaultProxy(proxy_);
+    GlobalParameters::GetInstance().GetSslConfiguration(verifyPeers_, caCertificates_);    
+  }
+
+
+  HttpClient::HttpClient() : 
+    pimpl_(new PImpl), 
+    verifyPeers_(true),
+    pkcs11Enabled_(false),
+    headersToLowerCase_(true),
+    redirectionFollowed_(true)
+  {
+    Setup();
+  }
+
+
+  HttpClient::HttpClient(const WebServiceParameters& service,
+                         const std::string& uri) : 
+    pimpl_(new PImpl), 
+    verifyPeers_(true),
+    headersToLowerCase_(true),
+    redirectionFollowed_(true)
+  {
+    Setup();
+
+    if (service.GetUsername().size() != 0 && 
+        service.GetPassword().size() != 0)
+    {
+      SetCredentials(service.GetUsername().c_str(), 
+                     service.GetPassword().c_str());
+    }
+
+    if (!service.GetCertificateFile().empty())
+    {
+      SetClientCertificate(service.GetCertificateFile(),
+                           service.GetCertificateKeyFile(),
+                           service.GetCertificateKeyPassword());
+    }
+
+    SetPkcs11Enabled(service.IsPkcs11Enabled());
+
+    SetUrl(service.GetUrl() + uri);
+  }
+
+
+  HttpClient::~HttpClient()
+  {
+    curl_easy_cleanup(pimpl_->curl_);
+    curl_slist_free_all(pimpl_->defaultPostHeaders_);
+    ClearHeaders();
+  }
+
+
+  void HttpClient::SetVerbose(bool isVerbose)
+  {
+    isVerbose_ = isVerbose;
+
+    if (isVerbose_)
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 1));
+    }
+    else
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_VERBOSE, 0));
+    }
+  }
+
+
+  void HttpClient::AddHeader(const std::string& key,
+                             const std::string& value)
+  {
+    if (key.empty())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    std::string s = key + ": " + value;
+
+    if ((pimpl_->userHeaders_ = curl_slist_append(pimpl_->userHeaders_, s.c_str())) == NULL)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+  }
+
+
+  void HttpClient::ClearHeaders()
+  {
+    if (pimpl_->userHeaders_ != NULL)
+    {
+      curl_slist_free_all(pimpl_->userHeaders_);
+      pimpl_->userHeaders_ = NULL;
+    }
+  }
+
+
+  bool HttpClient::ApplyInternal(std::string& answerBody,
+                                 HttpHeaders* answerHeaders)
+  {
+    answerBody.clear();
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_URL, url_.c_str()));
+
+    CurlHeaderParameters headerParameters;
+
+    if (answerHeaders == NULL)
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERFUNCTION, NULL));
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERDATA, NULL));
+    }
+    else
+    {
+      headerParameters.lowerCase_ = headersToLowerCase_;
+      headerParameters.headers_ = answerHeaders;
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERFUNCTION, &CurlHeaderCallback));
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERDATA, &headerParameters));
+    }
+
+#if ORTHANC_ENABLE_SSL == 1
+    // Setup HTTPS-related options
+
+    if (verifyPeers_)
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CAINFO, caCertificates_.c_str()));
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYHOST, 2));  // libcurl default is strict verifyhost
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 1)); 
+    }
+    else
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYHOST, 0)); 
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSL_VERIFYPEER, 0)); 
+    }
+#endif
+
+    // Setup the HTTPS client certificate
+    if (!clientCertificateFile_.empty() &&
+        pkcs11Enabled_)
+    {
+      LOG(ERROR) << "Cannot enable both client certificates and PKCS#11 authentication";
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    if (pkcs11Enabled_)
+    {
+#if ORTHANC_ENABLE_PKCS11 == 1
+      if (GlobalParameters::GetInstance().IsPkcs11Initialized())
+      {
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLENGINE, Pkcs11::GetEngineIdentifier()));
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEYTYPE, "ENG"));
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERTTYPE, "ENG"));
+      }
+      else
+      {
+        LOG(ERROR) << "Cannot use PKCS#11 for a HTTPS request, because it has not been initialized";
+        throw OrthancException(ErrorCode_BadSequenceOfCalls);
+      }
+#else
+      LOG(ERROR) << "This version of Orthanc is compiled without support for PKCS#11";
+      throw OrthancException(ErrorCode_InternalError);
+#endif
+    }
+    else if (!clientCertificateFile_.empty())
+    {
+#if ORTHANC_ENABLE_SSL == 1
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERTTYPE, "PEM"));
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERT, clientCertificateFile_.c_str()));
+
+      if (!clientCertificateKeyPassword_.empty())
+      {
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_KEYPASSWD, clientCertificateKeyPassword_.c_str()));
+      }
+
+      // NB: If no "clientKeyFile_" is provided, the key must be
+      // prepended to the certificate file
+      if (!clientCertificateKeyFile_.empty())
+      {
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEYTYPE, "PEM"));
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLKEY, clientCertificateKeyFile_.c_str()));
+      }
+#else
+      LOG(ERROR) << "This version of Orthanc is compiled without OpenSSL support, cannot use HTTPS client authentication";
+      throw OrthancException(ErrorCode_InternalError);
+#endif
+    }
+
+    // Reset the parameters from previous calls to Apply()
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->userHeaders_));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 0L));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 0L));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 0L));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, NULL));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, 0L));
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PROXY, NULL));
+
+    if (redirectionFollowed_)
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 1L));
+    }
+    else
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_FOLLOWLOCATION, 0L));
+    }
+
+    // Set timeouts
+    if (timeout_ <= 0)
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_TIMEOUT, 10));  /* default: 10 seconds */
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CONNECTTIMEOUT, 10));  /* default: 10 seconds */
+    }
+    else
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_TIMEOUT, timeout_));
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CONNECTTIMEOUT, timeout_));
+    }
+
+    if (credentials_.size() != 0)
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_USERPWD, credentials_.c_str()));
+    }
+
+    if (proxy_.size() != 0)
+    {
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PROXY, proxy_.c_str()));
+    }
+
+    switch (method_)
+    {
+    case HttpMethod_Get:
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPGET, 1L));
+      break;
+
+    case HttpMethod_Post:
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POST, 1L));
+
+      if (pimpl_->userHeaders_ == NULL)
+      {
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->defaultPostHeaders_));
+      }
+
+      break;
+
+    case HttpMethod_Delete:
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_NOBODY, 1L));
+      CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "DELETE"));
+      break;
+
+    case HttpMethod_Put:
+      // http://stackoverflow.com/a/7570281/881731: Don't use
+      // CURLOPT_PUT if there is a body
+
+      // CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_PUT, 1L));
+
+      curl_easy_setopt(pimpl_->curl_, CURLOPT_CUSTOMREQUEST, "PUT"); /* !!! */
+
+      if (pimpl_->userHeaders_ == NULL)
+      {
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HTTPHEADER, pimpl_->defaultPostHeaders_));
+      }
+
+      break;
+
+    default:
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+
+    if (method_ == HttpMethod_Post ||
+        method_ == HttpMethod_Put)
+    {
+      if (body_.size() > 0)
+      {
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, body_.c_str()));
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, body_.size()));
+      }
+      else
+      {
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDS, NULL));
+        CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_POSTFIELDSIZE, 0));
+      }
+    }
+
+
+    // Do the actual request
+    CURLcode code;
+    long status = 0;
+
+    ChunkedBuffer buffer;
+    CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_WRITEDATA, &buffer));
+
+    if (boost::starts_with(url_, "https://"))
+    {
+      code = OrthancHttpClientPerformSSL(pimpl_->curl_, &status);
+    }
+    else
+    {
+      code = GetHttpStatus(curl_easy_perform(pimpl_->curl_), pimpl_->curl_, &status);
+    }
+
+    CheckCode(code);
+
+    if (status == 0)
+    {
+      // This corresponds to a call to an inexistent host
+      lastStatus_ = HttpStatus_500_InternalServerError;
+    }
+    else
+    {
+      lastStatus_ = static_cast<HttpStatus>(status);
+    }
+
+    bool success = (status >= 200 && status < 300);
+
+    if (success)
+    {
+      buffer.Flatten(answerBody);
+    }
+    else
+    {
+      answerBody.clear();
+      LOG(INFO) << "Error in HTTP request, received HTTP status " << status 
+                << " (" << EnumerationToString(lastStatus_) << ")";
+    }
+
+    return success;
+  }
+
+
+  bool HttpClient::ApplyInternal(Json::Value& answerBody,
+                                 HttpClient::HttpHeaders* answerHeaders)
+  {
+    std::string s;
+    if (ApplyInternal(s, answerHeaders))
+    {
+      Json::Reader reader;
+      return reader.parse(s, answerBody);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  void HttpClient::SetCredentials(const char* username,
+                                  const char* password)
+  {
+    credentials_ = std::string(username) + ":" + std::string(password);
+  }
+
+
+  void HttpClient::ConfigureSsl(bool httpsVerifyPeers,
+                                const std::string& httpsVerifyCertificates)
+  {
+#if ORTHANC_ENABLE_SSL == 1
+    if (httpsVerifyPeers)
+    {
+      if (httpsVerifyCertificates.empty())
+      {
+        LOG(WARNING) << "No certificates are provided to validate peers, "
+                     << "set \"HttpsCACertificates\" if you need to do HTTPS requests";
+      }
+      else
+      {
+        LOG(WARNING) << "HTTPS will use the CA certificates from this file: " << httpsVerifyCertificates;
+      }
+    }
+    else
+    {
+      LOG(WARNING) << "The verification of the peers in HTTPS requests is disabled";
+    }
+#endif
+
+    GlobalParameters::GetInstance().ConfigureSsl(httpsVerifyPeers, httpsVerifyCertificates);
+  }
+
+  
+  void HttpClient::GlobalInitialize()
+  {
+#if ORTHANC_ENABLE_SSL == 1
+    CheckCode(curl_global_init(CURL_GLOBAL_ALL));
+#else
+    CheckCode(curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_SSL));
+#endif
+  }
+
+
+  void HttpClient::GlobalFinalize()
+  {
+    curl_global_cleanup();
+
+#if ORTHANC_ENABLE_PKCS11 == 1
+    Pkcs11::Finalize();
+#endif
+  }
+  
+
+  void HttpClient::SetDefaultProxy(const std::string& proxy)
+  {
+    GlobalParameters::GetInstance().SetDefaultProxy(proxy);
+  }
+
+
+  void HttpClient::SetDefaultTimeout(long timeout)
+  {
+    GlobalParameters::GetInstance().SetDefaultTimeout(timeout);
+  }
+
+
+  void HttpClient::ApplyAndThrowException(std::string& answerBody)
+  {
+    if (!Apply(answerBody))
+    {
+      ThrowException(GetLastStatus());
+    }
+  }
+
+  
+  void HttpClient::ApplyAndThrowException(Json::Value& answerBody)
+  {
+    if (!Apply(answerBody))
+    {
+      ThrowException(GetLastStatus());
+    }
+  }
+
+
+  void HttpClient::ApplyAndThrowException(std::string& answerBody,
+                                          HttpHeaders& answerHeaders)
+  {
+    if (!Apply(answerBody, answerHeaders))
+    {
+      ThrowException(GetLastStatus());
+    }
+  }
+  
+
+  void HttpClient::ApplyAndThrowException(Json::Value& answerBody,
+                                          HttpHeaders& answerHeaders)
+  {
+    if (!Apply(answerBody, answerHeaders))
+    {
+      ThrowException(GetLastStatus());
+    }
+  }
+
+
+  void HttpClient::SetClientCertificate(const std::string& certificateFile,
+                                        const std::string& certificateKeyFile,
+                                        const std::string& certificateKeyPassword)
+  {
+    if (certificateFile.empty())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    if (!SystemToolbox::IsRegularFile(certificateFile))
+    {
+      LOG(ERROR) << "Cannot open certificate file: " << certificateFile;
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+
+    if (!certificateKeyFile.empty() && 
+        !SystemToolbox::IsRegularFile(certificateKeyFile))
+    {
+      LOG(ERROR) << "Cannot open key file: " << certificateKeyFile;
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+
+    clientCertificateFile_ = certificateFile;
+    clientCertificateKeyFile_ = certificateKeyFile;
+    clientCertificateKeyPassword_ = certificateKeyPassword;
+  }
+
+
+  void HttpClient::InitializePkcs11(const std::string& module,
+                                    const std::string& pin,
+                                    bool verbose)
+  {
+#if ORTHANC_ENABLE_PKCS11 == 1
+    LOG(INFO) << "Initializing PKCS#11 using " << module 
+              << (pin.empty() ? " (no PIN provided)" : " (PIN is provided)");
+    GlobalParameters::GetInstance().InitializePkcs11(module, pin, verbose);    
+#else
+    LOG(ERROR) << "This version of Orthanc is compiled without support for PKCS#11";
+    throw OrthancException(ErrorCode_InternalError);
+#endif
+  }
+
+
+  void HttpClient::InitializeOpenSsl()
+  {
+#if ORTHANC_ENABLE_SSL == 1
+    // https://wiki.openssl.org/index.php/Library_Initialization
+    SSL_library_init();
+    SSL_load_error_strings();
+    OpenSSL_add_all_algorithms();
+    ERR_load_crypto_strings();
+#endif
+  }
+
+
+  void HttpClient::FinalizeOpenSsl()
+  {
+#if ORTHANC_ENABLE_SSL == 1
+    // Finalize OpenSSL
+    // https://wiki.openssl.org/index.php/Library_Initialization#Cleanup
+#ifdef FIPS_mode_set
+    FIPS_mode_set(0);
+#endif
+    ENGINE_cleanup();
+    CONF_modules_unload(1);
+    EVP_cleanup();
+    CRYPTO_cleanup_all_ex_data();
+    ERR_remove_state(0);
+    ERR_free_strings();
+#endif
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/HttpClient.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,296 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "Enumerations.h"
+#include "WebServiceParameters.h"
+
+#include <string>
+#include <boost/shared_ptr.hpp>
+#include <json/json.h>
+
+#if !defined(ORTHANC_ENABLE_SSL)
+#  error The macro ORTHANC_ENABLE_SSL must be defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_PKCS11)
+#  error The macro ORTHANC_ENABLE_PKCS11 must be defined
+#endif
+
+
+namespace Orthanc
+{
+  class HttpClient
+  {
+  public:
+    typedef std::map<std::string, std::string>  HttpHeaders;
+
+  private:
+    class GlobalParameters;
+
+    struct PImpl;
+    boost::shared_ptr<PImpl> pimpl_;
+
+    std::string url_;
+    std::string credentials_;
+    HttpMethod method_;
+    HttpStatus lastStatus_;
+    std::string body_;  // This only makes sense for POST and PUT requests
+    bool isVerbose_;
+    long timeout_;
+    std::string proxy_;
+    bool verifyPeers_;
+    std::string caCertificates_;
+    std::string clientCertificateFile_;
+    std::string clientCertificateKeyFile_;
+    std::string clientCertificateKeyPassword_;
+    bool pkcs11Enabled_;
+    bool headersToLowerCase_;
+    bool redirectionFollowed_;
+
+    void Setup();
+
+    void operator= (const HttpClient&);  // Assignment forbidden
+    HttpClient(const HttpClient& base);  // Copy forbidden
+
+    bool ApplyInternal(std::string& answerBody,
+                       HttpHeaders* answerHeaders);
+
+    bool ApplyInternal(Json::Value& answerBody,
+                       HttpHeaders* answerHeaders);
+
+  public:
+    HttpClient();
+
+    HttpClient(const WebServiceParameters& service,
+               const std::string& uri);
+
+    ~HttpClient();
+
+    void SetUrl(const char* url)
+    {
+      url_ = std::string(url);
+    }
+
+    void SetUrl(const std::string& url)
+    {
+      url_ = url;
+    }
+
+    const std::string& GetUrl() const
+    {
+      return url_;
+    }
+
+    void SetMethod(HttpMethod method)
+    {
+      method_ = method;
+    }
+
+    HttpMethod GetMethod() const
+    {
+      return method_;
+    }
+
+    void SetTimeout(long seconds)
+    {
+      timeout_ = seconds;
+    }
+
+    long GetTimeout() const
+    {
+      return timeout_;
+    }
+
+    void SetBody(const std::string& data)
+    {
+      body_ = data;
+    }
+
+    std::string& GetBody()
+    {
+      return body_;
+    }
+
+    const std::string& GetBody() const
+    {
+      return body_;
+    }
+
+    void SetVerbose(bool isVerbose);
+
+    bool IsVerbose() const
+    {
+      return isVerbose_;
+    }
+
+    void AddHeader(const std::string& key,
+                   const std::string& value);
+
+    void ClearHeaders();
+
+    bool Apply(std::string& answerBody)
+    {
+      return ApplyInternal(answerBody, NULL);
+    }
+
+    bool Apply(Json::Value& answerBody)
+    {
+      return ApplyInternal(answerBody, NULL);
+    }
+
+    bool Apply(std::string& answerBody,
+               HttpHeaders& answerHeaders)
+    {
+      return ApplyInternal(answerBody, &answerHeaders);
+    }
+
+    bool Apply(Json::Value& answerBody,
+               HttpHeaders& answerHeaders)
+    {
+      return ApplyInternal(answerBody, &answerHeaders);
+    }
+
+    HttpStatus GetLastStatus() const
+    {
+      return lastStatus_;
+    }
+
+    void SetCredentials(const char* username,
+                        const char* password);
+
+    void SetProxy(const std::string& proxy)
+    {
+      proxy_ = proxy;
+    }
+
+    void SetHttpsVerifyPeers(bool verify)
+    {
+      verifyPeers_ = verify;
+    }
+
+    bool IsHttpsVerifyPeers() const
+    {
+      return verifyPeers_;
+    }
+
+    void SetHttpsCACertificates(const std::string& certificates)
+    {
+      caCertificates_ = certificates;
+    }
+
+    const std::string& GetHttpsCACertificates() const
+    {
+      return caCertificates_;
+    }
+
+    void SetClientCertificate(const std::string& certificateFile,
+                              const std::string& certificateKeyFile,
+                              const std::string& certificateKeyPassword);
+
+    void SetPkcs11Enabled(bool enabled)
+    {
+      pkcs11Enabled_ = enabled;
+    }
+
+    bool IsPkcs11Enabled() const
+    {
+      return pkcs11Enabled_;
+    }
+
+    const std::string& GetClientCertificateFile() const
+    {
+      return clientCertificateFile_;
+    }
+
+    const std::string& GetClientCertificateKeyFile() const
+    {
+      return clientCertificateKeyFile_;
+    }
+
+    const std::string& GetClientCertificateKeyPassword() const
+    {
+      return clientCertificateKeyPassword_;
+    }
+
+    void SetConvertHeadersToLowerCase(bool lowerCase)
+    {
+      headersToLowerCase_ = lowerCase;
+    }
+
+    bool IsConvertHeadersToLowerCase() const
+    {
+      return headersToLowerCase_;
+    }
+
+    void SetRedirectionFollowed(bool follow)
+    {
+      redirectionFollowed_ = follow;
+    }
+
+    bool IsRedirectionFollowed() const
+    {
+      return redirectionFollowed_;
+    }
+
+    static void GlobalInitialize();
+  
+    static void GlobalFinalize();
+
+    static void InitializeOpenSsl();
+
+    static void FinalizeOpenSsl();
+
+    static void InitializePkcs11(const std::string& module,
+                                 const std::string& pin,
+                                 bool verbose);
+
+    static void ConfigureSsl(bool httpsVerifyPeers,
+                             const std::string& httpsCACertificates);
+
+    static void SetDefaultProxy(const std::string& proxy);
+
+    static void SetDefaultTimeout(long timeout);
+
+    void ApplyAndThrowException(std::string& answerBody);
+
+    void ApplyAndThrowException(Json::Value& answerBody);
+
+    void ApplyAndThrowException(std::string& answerBody,
+                                HttpHeaders& answerHeaders);
+
+    void ApplyAndThrowException(Json::Value& answerBody,
+                                HttpHeaders& answerHeaders);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/ICommand.h	Fri Mar 23 11:04:03 2018 +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-2018 Osimis S.A., 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 "IDynamicObject.h"
+
+namespace Orthanc
+{
+  /**
+   * This class is the base class for the "Command" design pattern.
+   * http://en.wikipedia.org/wiki/Command_pattern
+   **/
+  class ICommand : public IDynamicObject
+  {
+  public:
+    virtual bool Execute() = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/IDynamicObject.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,53 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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
+{
+  /**
+   * This class should be the ancestor to any class whose type is
+   * determined at the runtime, and that can be dynamically allocated.
+   * Being a child of IDynamicObject only implies the existence of a
+   * virtual destructor.
+   **/
+  class IDynamicObject : public boost::noncopyable
+  {
+  public:
+    virtual ~IDynamicObject()
+    {
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/Font.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,318 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "Font.h"
+
+#if !defined(ORTHANC_ENABLE_LOCALE)
+#  error ORTHANC_ENABLE_LOCALE must be defined to use this file
+#endif
+
+#if ORTHANC_SANDBOXED == 0
+#  include "../SystemToolbox.h"
+#endif
+
+#include "../Toolbox.h"
+#include "../OrthancException.h"
+
+#include <stdio.h>
+#include <memory>
+#include <boost/lexical_cast.hpp>
+
+namespace Orthanc
+{
+  Font::~Font()
+  {
+    for (Characters::iterator it = characters_.begin();
+         it != characters_.end(); ++it)
+    {
+      delete it->second;
+    }
+  }
+
+
+  void Font::LoadFromMemory(const std::string& font)
+  {
+    Json::Value v;
+    Json::Reader reader;
+    if (!reader.parse(font, v) ||
+        v.type() != Json::objectValue ||
+        !v.isMember("Name") ||
+        !v.isMember("Size") ||
+        !v.isMember("Characters") ||
+        v["Name"].type() != Json::stringValue ||
+        v["Size"].type() != Json::intValue ||
+        v["Characters"].type() != Json::objectValue)
+    {
+      throw OrthancException(ErrorCode_BadFont);
+    }
+
+    name_ = v["Name"].asString();
+    size_ = v["Size"].asUInt();
+    maxHeight_ = 0;
+
+    Json::Value::Members characters = v["Characters"].getMemberNames();
+
+    for (size_t i = 0; i < characters.size(); i++)
+    {
+      const Json::Value& info = v["Characters"][characters[i]];
+      if (info.type() != Json::objectValue ||
+          !info.isMember("Advance") ||
+          !info.isMember("Bitmap") ||
+          !info.isMember("Height") ||
+          !info.isMember("Top") ||
+          !info.isMember("Width") ||
+          info["Advance"].type() != Json::intValue ||
+          info["Bitmap"].type() != Json::arrayValue ||
+          info["Height"].type() != Json::intValue ||
+          info["Top"].type() != Json::intValue ||
+          info["Width"].type() != Json::intValue)
+      {
+        throw OrthancException(ErrorCode_BadFont);
+      }
+
+      std::auto_ptr<Character> c(new Character);
+      
+      c->advance_ = info["Advance"].asUInt();
+      c->height_ = info["Height"].asUInt();
+      c->top_ = info["Top"].asUInt();
+      c->width_ = info["Width"].asUInt();
+      c->bitmap_.resize(info["Bitmap"].size());
+
+      if (c->height_ > maxHeight_)
+      {
+        maxHeight_ = c->height_;
+      }
+      
+      for (Json::Value::ArrayIndex j = 0; j < info["Bitmap"].size(); j++)
+      {
+        if (info["Bitmap"][j].type() != Json::intValue)
+        {
+          throw OrthancException(ErrorCode_BadFont);
+        }
+
+        int value = info["Bitmap"][j].asInt();
+        if (value < 0 || value > 255)
+        {
+          throw OrthancException(ErrorCode_BadFont);
+        }
+
+        c->bitmap_[j] = static_cast<uint8_t>(value);
+      }
+
+      int index = boost::lexical_cast<int>(characters[i]);
+      if (index < 0 || index > 255)
+      {
+        throw OrthancException(ErrorCode_BadFont);
+      }
+
+      characters_[static_cast<char>(index)] = c.release();
+    }
+  }
+
+
+#if ORTHANC_SANDBOXED == 0
+  void Font::LoadFromFile(const std::string& path)
+  {
+    std::string font;
+    SystemToolbox::ReadFile(font, path);
+    LoadFromMemory(font);
+  }
+#endif
+
+
+  static unsigned int MyMin(unsigned int a, 
+                            unsigned int b)
+  {
+    return a < b ? a : b;
+  }
+
+
+  void Font::DrawCharacter(ImageAccessor& target,
+                           const Character& character,
+                           int x,
+                           int y,
+                           const uint8_t color[4]) const
+  {
+    // Compute the bounds of the character
+    if (x >= static_cast<int>(target.GetWidth()) ||
+        y >= static_cast<int>(target.GetHeight()))
+    {
+      // The character is out of the image
+      return;
+    }
+
+    unsigned int left = x < 0 ? -x : 0;
+    unsigned int top = y < 0 ? -y : 0;
+    unsigned int width = MyMin(character.width_, target.GetWidth() - x);
+    unsigned int height = MyMin(character.height_, target.GetHeight() - y);
+
+    unsigned int bpp = target.GetBytesPerPixel();
+
+    // Blit the font bitmap OVER the target image
+    // https://en.wikipedia.org/wiki/Alpha_compositing
+
+    for (unsigned int cy = top; cy < height; cy++)
+    {
+      uint8_t* p = reinterpret_cast<uint8_t*>(target.GetRow(y + cy)) + (x + left) * bpp;
+      unsigned int pos = cy * character.width_ + left;
+
+      switch (target.GetFormat())
+      {
+        case PixelFormat_Grayscale8:
+        {
+          assert(bpp == 1);
+          for (unsigned int cx = left; cx < width; cx++, pos++, p++)
+          {
+            uint16_t alpha = character.bitmap_[pos];
+            uint16_t value = alpha * static_cast<uint16_t>(color[0]) + (255 - alpha) * static_cast<uint16_t>(*p);
+            *p = static_cast<uint8_t>(value >> 8);
+          }
+
+          break;
+        }
+
+        case PixelFormat_RGB24:
+        {
+          assert(bpp == 3);
+          for (unsigned int cx = left; cx < width; cx++, pos++, p += 3)
+          {
+            uint16_t alpha = character.bitmap_[pos];
+            for (uint8_t i = 0; i < 3; i++)
+            {
+              uint16_t value = alpha * static_cast<uint16_t>(color[i]) + (255 - alpha) * static_cast<uint16_t>(p[i]);
+              p[i] = static_cast<uint8_t>(value >> 8);
+            }
+          }
+
+          break;
+        }
+
+        case PixelFormat_RGBA32:
+        {
+          assert(bpp == 4);
+
+          for (unsigned int cx = left; cx < width; cx++, pos++, p += 4)
+          {
+            float alpha = static_cast<float>(character.bitmap_[pos]) / 255.0f;
+            float beta = (1.0f - alpha) * static_cast<float>(p[3]) / 255.0f;
+            float denom = 1.0f / (alpha + beta);
+
+            for (uint8_t i = 0; i < 3; i++)
+            {
+              p[i] = static_cast<uint8_t>((alpha * static_cast<float>(color[i]) +
+                                           beta * static_cast<float>(p[i])) * denom);
+            }
+
+            p[3] = static_cast<uint8_t>(255.0f * (alpha + beta));
+          }
+
+          break;
+        }
+
+        default:
+          throw OrthancException(ErrorCode_NotImplemented);
+      }
+    }
+
+  }
+
+
+  void Font::DrawInternal(ImageAccessor& target,
+                          const std::string& utf8,
+                          int x,
+                          int y,
+                          const uint8_t color[4]) const
+  {
+    if (target.GetFormat() != PixelFormat_Grayscale8 &&
+        target.GetFormat() != PixelFormat_RGB24 &&
+        target.GetFormat() != PixelFormat_RGBA32)
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    int a = x;
+
+#if ORTHANC_ENABLE_LOCALE == 1
+    std::string s = Toolbox::ConvertFromUtf8(utf8, Encoding_Latin1);
+#else
+    // If the locale support is disabled, simply drop non-ASCII
+    // characters from the source UTF-8 string
+    std::string s = Toolbox::ConvertToAscii(utf8);
+#endif
+
+    for (size_t i = 0; i < s.size(); i++)
+    {
+      if (s[i] == '\n')
+      {
+        // Go to the next line
+        a = x;
+        y += maxHeight_ + 1;
+      }
+      else
+      {
+        Characters::const_iterator c = characters_.find(s[i]);
+        if (c != characters_.end())
+        {
+          DrawCharacter(target, *c->second, a, y + static_cast<int>(c->second->top_), color);
+          a += c->second->advance_;
+        }
+      }
+    }
+  }
+
+
+  void Font::Draw(ImageAccessor& target,
+                  const std::string& utf8,
+                  int x,
+                  int y,
+                  uint8_t grayscale) const
+  {
+    uint8_t color[4] = { grayscale, grayscale, grayscale, 255 };
+    DrawInternal(target, utf8, x, y, color);
+  }
+
+
+  void Font::Draw(ImageAccessor& target,
+                  const std::string& utf8,
+                  int x,
+                  int y,
+                  uint8_t r,
+                  uint8_t g,
+                  uint8_t b) const
+  {
+    uint8_t color[4] = { r, g, b, 255 };
+    DrawInternal(target, utf8, x, y, color);
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/Font.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,115 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "ImageAccessor.h"
+
+#include <stdint.h>
+#include <vector>
+#include <map>
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class Font : public boost::noncopyable
+  {
+  private:
+    struct Character
+    {
+      unsigned int  width_;
+      unsigned int  height_;
+      unsigned int  top_;
+      unsigned int  advance_;
+      std::vector<uint8_t>  bitmap_;
+    };
+
+    typedef std::map<char, Character*>  Characters;
+
+    std::string   name_;
+    unsigned int  size_;
+    Characters    characters_;
+    unsigned int  maxHeight_;
+
+    void DrawCharacter(ImageAccessor& target,
+                       const Character& character,
+                       int x,
+                       int y,
+                       const uint8_t color[4]) const;
+
+    void DrawInternal(ImageAccessor& target,
+                      const std::string& utf8,
+                      int x,
+                      int y,
+                      const uint8_t color[4]) const;
+
+  public:
+    Font() : 
+      size_(0), 
+      maxHeight_(0)
+    {
+    }
+
+    ~Font();
+
+    void LoadFromMemory(const std::string& font);
+
+#if ORTHANC_SANDBOXED == 0
+    void LoadFromFile(const std::string& path);
+#endif
+
+    const std::string& GetName() const
+    {
+      return name_;
+    }
+
+    unsigned int GetSize() const
+    {
+      return size_;
+    }
+
+    void Draw(ImageAccessor& target,
+              const std::string& utf8,
+              int x,
+              int y,
+              uint8_t grayscale) const;
+
+    void Draw(ImageAccessor& target,
+              const std::string& utf8,
+              int x,
+              int y,
+              uint8_t r,
+              uint8_t g,
+              uint8_t b) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/FontRegistry.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,91 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "FontRegistry.h"
+
+#include "../OrthancException.h"
+
+#include <memory>
+
+namespace Orthanc
+{
+  FontRegistry::~FontRegistry()
+  {
+    for (Fonts::iterator it = fonts_.begin(); it != fonts_.end(); ++it)
+    {
+      delete *it;
+    }
+  }
+
+
+  void FontRegistry::AddFromMemory(const std::string& font)
+  {
+    std::auto_ptr<Font> f(new Font);
+    f->LoadFromMemory(font);
+    fonts_.push_back(f.release());
+  }
+
+
+#if ORTHANC_SANDBOXED == 0
+  void FontRegistry::AddFromFile(const std::string& path)
+  {
+    std::auto_ptr<Font> f(new Font);
+    f->LoadFromFile(path);
+    fonts_.push_back(f.release());
+  }
+#endif
+
+
+#if ORTHANC_HAS_EMBEDDED_RESOURCES == 1
+  void FontRegistry::AddFromResource(EmbeddedResources::FileResourceId resource)
+  {
+    std::string content;
+    EmbeddedResources::GetFileResource(content, resource);
+    AddFromMemory(content);
+  }
+#endif
+
+
+  const Font& FontRegistry::GetFont(size_t i) const
+  {
+    if (i >= fonts_.size())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return *fonts_[i];
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/FontRegistry.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,75 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "Font.h"
+
+#if !defined(ORTHANC_HAS_EMBEDDED_RESOURCES)
+#  error Macro ORTHANC_HAS_EMBEDDED_RESOURCES must be defined
+#endif
+
+#if ORTHANC_HAS_EMBEDDED_RESOURCES == 1
+#  include <EmbeddedResources.h>   // Autogenerated file
+#endif
+
+namespace Orthanc
+{
+  class FontRegistry : public boost::noncopyable
+  {
+  private:
+    typedef std::vector<Font*>  Fonts;
+
+    Fonts  fonts_;
+
+  public:
+    ~FontRegistry();
+
+    void AddFromMemory(const std::string& font);
+
+#if ORTHANC_SANDBOXED == 0
+    void AddFromFile(const std::string& path);
+#endif
+
+#if ORTHANC_HAS_EMBEDDED_RESOURCES == 1
+    void AddFromResource(EmbeddedResources::FileResourceId resource);
+#endif
+
+    size_t GetSize() const
+    {
+      return fonts_.size();
+    }
+
+    const Font& GetFont(size_t i) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/IImageWriter.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,55 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "IImageWriter.h"
+
+#if ORTHANC_SANDBOXED == 0
+#  include "../SystemToolbox.h"
+#endif
+
+namespace Orthanc
+{
+#if ORTHANC_SANDBOXED == 0
+  void IImageWriter::WriteToFileInternal(const std::string& path,
+                                         unsigned int width,
+                                         unsigned int height,
+                                         unsigned int pitch,
+                                         PixelFormat format,
+                                         const void* buffer)
+  {
+    std::string compressed;
+    WriteToMemoryInternal(compressed, width, height, pitch, format, buffer);
+    SystemToolbox::WriteFile(compressed, path);
+  }
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/IImageWriter.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,86 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "ImageAccessor.h"
+
+#include <boost/noncopyable.hpp>
+
+#if !defined(ORTHANC_SANDBOXED)
+#  error The macro ORTHANC_SANDBOXED must be defined
+#endif
+
+namespace Orthanc
+{
+  class IImageWriter : public boost::noncopyable
+  {
+  protected:
+    virtual void WriteToMemoryInternal(std::string& compressed,
+                                       unsigned int width,
+                                       unsigned int height,
+                                       unsigned int pitch,
+                                       PixelFormat format,
+                                       const void* buffer) = 0;
+
+#if ORTHANC_SANDBOXED == 0
+    virtual void WriteToFileInternal(const std::string& path,
+                                     unsigned int width,
+                                     unsigned int height,
+                                     unsigned int pitch,
+                                     PixelFormat format,
+                                     const void* buffer);
+#endif
+
+  public:
+    virtual ~IImageWriter()
+    {
+    }
+
+    virtual void WriteToMemory(std::string& compressed,
+                               const ImageAccessor& accessor)
+    {
+      WriteToMemoryInternal(compressed, accessor.GetWidth(), accessor.GetHeight(),
+                            accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer());
+    }
+
+#if ORTHANC_SANDBOXED == 0
+    virtual void WriteToFile(const std::string& path,
+                             const ImageAccessor& accessor)
+    {
+      WriteToFileInternal(path, accessor.GetWidth(), accessor.GetHeight(),
+                          accessor.GetPitch(), accessor.GetFormat(), accessor.GetConstBuffer());
+    }
+#endif
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/Image.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,59 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "Image.h"
+
+#include "ImageProcessing.h"
+
+#include <memory>
+
+namespace Orthanc
+{
+  Image::Image(PixelFormat format,
+               unsigned int width,
+               unsigned int height,
+               bool forceMinimalPitch) :
+    image_(format, width, height, forceMinimalPitch)
+  {
+    ImageAccessor accessor = image_.GetAccessor();
+    AssignWritable(format, width, height, accessor.GetPitch(), accessor.GetBuffer());
+  }
+
+
+  Image* Image::Clone(const ImageAccessor& source)
+  {
+    std::auto_ptr<Image> target(new Image(source.GetFormat(), source.GetWidth(), source.GetHeight(), false));
+    ImageProcessing::Copy(*target, source);
+    return target.release();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/Image.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,54 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "ImageAccessor.h"
+#include "ImageBuffer.h"
+
+namespace Orthanc
+{
+  class Image : public ImageAccessor
+  {
+  private:
+    ImageBuffer  image_;
+
+  public:
+    Image(PixelFormat format,
+          unsigned int width,
+          unsigned int height,
+          bool forceMinimalPitch);
+
+    static Image* Clone(const ImageAccessor& source);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/ImageAccessor.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,301 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "ImageAccessor.h"
+
+#include "../Logging.h"
+#include "../OrthancException.h"
+#include "../ChunkedBuffer.h"
+
+#include <stdint.h>
+#include <cassert>
+#include <boost/lexical_cast.hpp>
+
+
+
+namespace Orthanc
+{
+  template <typename PixelType>
+  static void ToMatlabStringInternal(ChunkedBuffer& target,
+                                     const ImageAccessor& source)
+  {
+    target.AddChunk("double([ ");
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      const PixelType* p = reinterpret_cast<const PixelType*>(source.GetConstRow(y));
+
+      std::string s;
+      if (y > 0)
+      {
+        s = "; ";
+      }
+
+      s.reserve(source.GetWidth() * 8);
+
+      for (unsigned int x = 0; x < source.GetWidth(); x++, p++)
+      {
+        s += boost::lexical_cast<std::string>(static_cast<double>(*p)) + " ";
+      }
+
+      target.AddChunk(s);
+    }
+
+    target.AddChunk("])");
+  }
+
+
+  static void RGB24ToMatlabString(ChunkedBuffer& target,
+                                  const ImageAccessor& source)
+  {
+    assert(source.GetFormat() == PixelFormat_RGB24);
+
+    target.AddChunk("double(permute(reshape([ ");
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
+      
+      std::string s;
+      s.reserve(source.GetWidth() * 3 * 8);
+      
+      for (unsigned int x = 0; x < 3 * source.GetWidth(); x++, p++)
+      {
+        s += boost::lexical_cast<std::string>(static_cast<int>(*p)) + " ";
+      }
+      
+      target.AddChunk(s);
+    }
+
+    target.AddChunk("], [ 3 " + boost::lexical_cast<std::string>(source.GetHeight()) +
+                    " " + boost::lexical_cast<std::string>(source.GetWidth()) + " ]), [ 3 2 1 ]))");
+  }
+
+
+  void* ImageAccessor::GetBuffer() const
+  {
+    if (readOnly_)
+    {
+#if ORTHANC_ENABLE_LOGGING == 1
+      LOG(ERROR) << "Trying to write on a read-only image";
+#endif
+
+      throw OrthancException(ErrorCode_ReadOnly);
+    }
+
+    return buffer_;
+  }
+
+
+  const void* ImageAccessor::GetConstRow(unsigned int y) const
+  {
+    if (buffer_ != NULL)
+    {
+      return buffer_ + y * pitch_;
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+
+
+  void* ImageAccessor::GetRow(unsigned int y) const
+  {
+    if (readOnly_)
+    {
+#if ORTHANC_ENABLE_LOGGING == 1
+      LOG(ERROR) << "Trying to write on a read-only image";
+#endif
+
+      throw OrthancException(ErrorCode_ReadOnly);
+    }
+
+    if (buffer_ != NULL)
+    {
+      return buffer_ + y * pitch_;
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+
+
+  void ImageAccessor::AssignEmpty(PixelFormat format)
+  {
+    readOnly_ = false;
+    format_ = format;
+    width_ = 0;
+    height_ = 0;
+    pitch_ = 0;
+    buffer_ = NULL;
+  }
+
+
+  void ImageAccessor::AssignReadOnly(PixelFormat format,
+                                     unsigned int width,
+                                     unsigned int height,
+                                     unsigned int pitch,
+                                     const void *buffer)
+  {
+    readOnly_ = true;
+    format_ = format;
+    width_ = width;
+    height_ = height;
+    pitch_ = pitch;
+    buffer_ = reinterpret_cast<uint8_t*>(const_cast<void*>(buffer));
+
+    if (GetBytesPerPixel() * width_ > pitch_)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  void ImageAccessor::AssignWritable(PixelFormat format,
+                                     unsigned int width,
+                                     unsigned int height,
+                                     unsigned int pitch,
+                                     void *buffer)
+  {
+    readOnly_ = false;
+    format_ = format;
+    width_ = width;
+    height_ = height;
+    pitch_ = pitch;
+    buffer_ = reinterpret_cast<uint8_t*>(buffer);
+
+    if (GetBytesPerPixel() * width_ > pitch_)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  void ImageAccessor::ToMatlabString(std::string& target) const
+  {
+    ChunkedBuffer buffer;
+
+    switch (GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+        ToMatlabStringInternal<uint8_t>(buffer, *this);
+        break;
+
+      case PixelFormat_Grayscale16:
+        ToMatlabStringInternal<uint16_t>(buffer, *this);
+        break;
+
+      case PixelFormat_Grayscale32:
+        ToMatlabStringInternal<uint32_t>(buffer, *this);
+        break;
+
+      case PixelFormat_SignedGrayscale16:
+        ToMatlabStringInternal<int16_t>(buffer, *this);
+        break;
+
+      case PixelFormat_Float32:
+        ToMatlabStringInternal<float>(buffer, *this);
+        break;
+
+      case PixelFormat_RGB24:
+        RGB24ToMatlabString(buffer, *this);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }   
+
+    buffer.Flatten(target);
+  }
+
+
+
+  ImageAccessor ImageAccessor::GetRegion(unsigned int x,
+                                         unsigned int y,
+                                         unsigned int width,
+                                         unsigned int height) const
+  {
+    if (x + width > width_ ||
+        y + height > height_)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    
+    ImageAccessor result;
+
+    if (width == 0 ||
+        height == 0)
+    {
+      result.AssignWritable(format_, 0, 0, 0, NULL);
+    }
+    else
+    {
+      uint8_t* p = (buffer_ + 
+                    y * pitch_ + 
+                    x * GetBytesPerPixel());
+
+      if (readOnly_)
+      {
+        result.AssignReadOnly(format_, width, height, pitch_, p);
+      }
+      else
+      {
+        result.AssignWritable(format_, width, height, pitch_, p);
+      }
+    }
+
+    return result;
+  }
+
+
+  void ImageAccessor::SetFormat(PixelFormat format)
+  {
+    if (readOnly_)
+    {
+#if ORTHANC_ENABLE_LOGGING == 1
+      LOG(ERROR) << "Trying to modify the format of a read-only image";
+#endif
+      throw OrthancException(ErrorCode_ReadOnly);
+    }
+
+    if (::Orthanc::GetBytesPerPixel(format) != ::Orthanc::GetBytesPerPixel(format_))
+    {
+      throw OrthancException(ErrorCode_IncompatibleImageFormat);
+    }
+
+    format_ = format;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/ImageAccessor.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,152 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "../Enumerations.h"
+
+#include <string>
+#include <stdint.h>
+
+namespace Orthanc
+{
+  class ImageAccessor
+  {
+  private:
+    template <Orthanc::PixelFormat Format>
+    friend struct ImageTraits;
+    
+    bool readOnly_;
+    PixelFormat format_;
+    unsigned int width_;
+    unsigned int height_;
+    unsigned int pitch_;
+    uint8_t *buffer_;
+
+    template <typename T>
+    const T& GetPixelUnchecked(unsigned int x,
+                               unsigned int y) const
+    {
+      const uint8_t* row = reinterpret_cast<const uint8_t*>(buffer_) + y * pitch_;
+      return reinterpret_cast<const T*>(row) [x];
+    }
+
+
+    template <typename T>
+    T& GetPixelUnchecked(unsigned int x,
+                         unsigned int y)
+    {
+      uint8_t* row = reinterpret_cast<uint8_t*>(buffer_) + y * pitch_;
+      return reinterpret_cast<T*>(row) [x];
+    }
+
+  public:
+    ImageAccessor()
+    {
+      AssignEmpty(PixelFormat_Grayscale8);
+    }
+
+    virtual ~ImageAccessor()
+    {
+    }
+
+    bool IsReadOnly() const
+    {
+      return readOnly_;
+    }
+
+    PixelFormat GetFormat() const
+    {
+      return format_;
+    }
+
+    unsigned int GetBytesPerPixel() const
+    {
+      return ::Orthanc::GetBytesPerPixel(format_);
+    }
+
+    unsigned int GetWidth() const
+    {
+      return width_;
+    }
+
+    unsigned int GetHeight() const
+    {
+      return height_;
+    }
+
+    unsigned int GetPitch() const
+    {
+      return pitch_;
+    }
+
+    unsigned int GetSize() const
+    {
+      return GetHeight() * GetPitch();
+    }
+
+    const void* GetConstBuffer() const
+    {
+      return buffer_;
+    }
+
+    void* GetBuffer() const;
+
+    const void* GetConstRow(unsigned int y) const;
+
+    void* GetRow(unsigned int y) const;
+
+    void AssignEmpty(PixelFormat format);
+
+    void AssignReadOnly(PixelFormat format,
+                        unsigned int width,
+                        unsigned int height,
+                        unsigned int pitch,
+                        const void *buffer);
+
+    void AssignWritable(PixelFormat format,
+                        unsigned int width,
+                        unsigned int height,
+                        unsigned int pitch,
+                        void *buffer);
+
+    void ToMatlabString(std::string& target) const; 
+
+    ImageAccessor GetRegion(unsigned int x,
+                            unsigned int y,
+                            unsigned int width,
+                            unsigned int height) const;
+
+    void SetFormat(PixelFormat format);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/ImageBuffer.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,185 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "ImageBuffer.h"
+
+#include "../OrthancException.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+namespace Orthanc
+{
+  void ImageBuffer::Allocate()
+  {
+    if (changed_)
+    {
+      Deallocate();
+
+      /*
+        if (forceMinimalPitch_)
+        {
+        TODO: Align pitch and memory buffer to optimal size for SIMD.
+        }
+      */
+
+      pitch_ = GetBytesPerPixel() * width_;
+      size_t size = pitch_ * height_;
+
+      if (size == 0)
+      {
+        buffer_ = NULL;
+      }
+      else
+      {
+        buffer_ = malloc(size);
+        if (buffer_ == NULL)
+        {
+          throw OrthancException(ErrorCode_NotEnoughMemory);
+        }
+      }
+
+      changed_ = false;
+    }
+  }
+
+
+  void ImageBuffer::Deallocate()
+  {
+    if (buffer_ != NULL)
+    {
+      free(buffer_);
+      buffer_ = NULL;
+      changed_ = true;
+    }
+  }
+
+
+  ImageBuffer::ImageBuffer(PixelFormat format,
+                           unsigned int width,
+                           unsigned int height,
+                           bool forceMinimalPitch) :
+    forceMinimalPitch_(forceMinimalPitch)
+  {
+    Initialize();
+    SetWidth(width);
+    SetHeight(height);
+    SetFormat(format);
+  }
+
+
+  void ImageBuffer::Initialize()
+  {
+    changed_ = false;
+    forceMinimalPitch_ = true;
+    format_ = PixelFormat_Grayscale8;
+    width_ = 0;
+    height_ = 0;
+    pitch_ = 0;
+    buffer_ = NULL;
+  }
+
+
+  void ImageBuffer::SetFormat(PixelFormat format)
+  {
+    if (format != format_)
+    {
+      changed_ = true;
+      format_ = format;
+    }
+  }
+
+
+  void ImageBuffer::SetWidth(unsigned int width)
+  {
+    if (width != width_)
+    {
+      changed_ = true;
+      width_ = width;     
+    }
+  }
+
+
+  void ImageBuffer::SetHeight(unsigned int height)
+  {
+    if (height != height_)
+    {
+      changed_ = true;
+      height_ = height;     
+    }
+  }
+
+
+  ImageAccessor ImageBuffer::GetAccessor()
+  {
+    Allocate();
+
+    ImageAccessor accessor;
+    accessor.AssignWritable(format_, width_, height_, pitch_, buffer_);
+    return accessor;
+  }
+
+
+  ImageAccessor ImageBuffer::GetConstAccessor()
+  {
+    Allocate();
+
+    ImageAccessor accessor;
+    accessor.AssignReadOnly(format_, width_, height_, pitch_, buffer_);
+    return accessor;
+  }
+
+
+  void ImageBuffer::AcquireOwnership(ImageBuffer& other)
+  {
+    // Remove the content of the current image
+    Deallocate();
+
+    // Force the allocation of the other image (if not already
+    // allocated)
+    other.Allocate();
+
+    // Transfer the content of the other image
+    changed_ = false;
+    forceMinimalPitch_ = other.forceMinimalPitch_;
+    format_ = other.format_;
+    width_ = other.width_;
+    height_ = other.height_;
+    pitch_ = other.pitch_;
+    buffer_ = other.buffer_;
+
+    // Force the reinitialization of the other image
+    other.Initialize();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/ImageBuffer.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,115 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "ImageAccessor.h"
+
+#include <vector>
+#include <stdint.h>
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class ImageBuffer : public boost::noncopyable
+  {
+  private:
+    bool changed_;
+
+    bool forceMinimalPitch_;  // Currently unused
+    PixelFormat format_;
+    unsigned int width_;
+    unsigned int height_;
+    unsigned int pitch_;
+    void *buffer_;
+
+    void Initialize();
+    
+    void Allocate();
+
+    void Deallocate();
+
+  public:
+    ImageBuffer(PixelFormat format,
+                unsigned int width,
+                unsigned int height,
+                bool forceMinimalPitch);
+
+    ImageBuffer()
+    {
+      Initialize();
+    }
+
+    ~ImageBuffer()
+    {
+      Deallocate();
+    }
+
+    PixelFormat GetFormat() const
+    {
+      return format_;
+    }
+
+    void SetFormat(PixelFormat format);
+
+    unsigned int GetWidth() const
+    {
+      return width_;
+    }
+
+    void SetWidth(unsigned int width);
+
+    unsigned int GetHeight() const
+    {
+      return height_;
+    }
+
+    void SetHeight(unsigned int height);
+
+    unsigned int GetBytesPerPixel() const
+    {
+      return ::Orthanc::GetBytesPerPixel(format_);
+    }
+
+    ImageAccessor GetAccessor();
+
+    ImageAccessor GetConstAccessor();
+
+    bool IsMinimalPitchForced() const
+    {
+      return forceMinimalPitch_;
+    }
+
+    void AcquireOwnership(ImageBuffer& other);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/ImageProcessing.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,1226 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "ImageProcessing.h"
+
+#include "PixelTraits.h"
+
+#include <boost/math/special_functions/round.hpp>
+
+#include <cassert>
+#include <string.h>
+#include <limits>
+#include <stdint.h>
+
+namespace Orthanc
+{
+  template <typename TargetType, typename SourceType>
+  static void ConvertInternal(ImageAccessor& target,
+                              const ImageAccessor& source)
+  {
+    const TargetType minValue = std::numeric_limits<TargetType>::min();
+    const TargetType maxValue = std::numeric_limits<TargetType>::max();
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      TargetType* t = reinterpret_cast<TargetType*>(target.GetRow(y));
+      const SourceType* s = reinterpret_cast<const SourceType*>(source.GetConstRow(y));
+
+      for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s++)
+      {
+        if (static_cast<int32_t>(*s) < static_cast<int32_t>(minValue))
+        {
+          *t = minValue;
+        }
+        else if (static_cast<int32_t>(*s) > static_cast<int32_t>(maxValue))
+        {
+          *t = maxValue;
+        }
+        else
+        {
+          *t = static_cast<TargetType>(*s);
+        }
+      }
+    }
+  }
+
+
+  template <typename SourceType>
+  static void ConvertGrayscaleToFloat(ImageAccessor& target,
+                                      const ImageAccessor& source)
+  {
+    assert(sizeof(float) == 4);
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      float* t = reinterpret_cast<float*>(target.GetRow(y));
+      const SourceType* s = reinterpret_cast<const SourceType*>(source.GetConstRow(y));
+
+      for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s++)
+      {
+        *t = static_cast<float>(*s);
+      }
+    }
+  }
+
+
+  template <typename TargetType>
+  static void ConvertColorToGrayscale(ImageAccessor& target,
+                                      const ImageAccessor& source)
+  {
+    assert(source.GetFormat() == PixelFormat_RGB24);
+
+    const TargetType minValue = std::numeric_limits<TargetType>::min();
+    const TargetType maxValue = std::numeric_limits<TargetType>::max();
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      TargetType* t = reinterpret_cast<TargetType*>(target.GetRow(y));
+      const uint8_t* s = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
+
+      for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s += 3)
+      {
+        // Y = 0.2126 R + 0.7152 G + 0.0722 B
+        int32_t v = (2126 * static_cast<int32_t>(s[0]) +
+                     7152 * static_cast<int32_t>(s[1]) +
+                     0722 * static_cast<int32_t>(s[2])) / 10000;
+        
+        if (static_cast<int32_t>(v) < static_cast<int32_t>(minValue))
+        {
+          *t = minValue;
+        }
+        else if (static_cast<int32_t>(v) > static_cast<int32_t>(maxValue))
+        {
+          *t = maxValue;
+        }
+        else
+        {
+          *t = static_cast<TargetType>(v);
+        }
+      }
+    }
+  }
+
+
+  template <typename PixelType>
+  static void SetInternal(ImageAccessor& image,
+                          int64_t constant)
+  {
+    for (unsigned int y = 0; y < image.GetHeight(); y++)
+    {
+      PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
+
+      for (unsigned int x = 0; x < image.GetWidth(); x++, p++)
+      {
+        *p = static_cast<PixelType>(constant);
+      }
+    }
+  }
+
+
+  template <typename PixelType>
+  static void GetMinMaxValueInternal(PixelType& minValue,
+                                     PixelType& maxValue,
+                                     const ImageAccessor& source)
+  {
+    // Deal with the special case of empty image
+    if (source.GetWidth() == 0 ||
+        source.GetHeight() == 0)
+    {
+      minValue = 0;
+      maxValue = 0;
+      return;
+    }
+
+    minValue = std::numeric_limits<PixelType>::max();
+    maxValue = std::numeric_limits<PixelType>::min();
+
+    const unsigned int width = source.GetWidth();
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      const PixelType* p = reinterpret_cast<const PixelType*>(source.GetConstRow(y));
+
+      for (unsigned int x = 0; x < width; x++, p++)
+      {
+        if (*p < minValue)
+        {
+          minValue = *p;
+        }
+
+        if (*p > maxValue)
+        {
+          maxValue = *p;
+        }
+      }
+    }
+  }
+
+
+
+  template <typename PixelType>
+  static void AddConstantInternal(ImageAccessor& image,
+                                  int64_t constant)
+  {
+    if (constant == 0)
+    {
+      return;
+    }
+
+    const int64_t minValue = std::numeric_limits<PixelType>::min();
+    const int64_t maxValue = std::numeric_limits<PixelType>::max();
+
+    for (unsigned int y = 0; y < image.GetHeight(); y++)
+    {
+      PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
+
+      for (unsigned int x = 0; x < image.GetWidth(); x++, p++)
+      {
+        int64_t v = static_cast<int64_t>(*p) + constant;
+
+        if (v > maxValue)
+        {
+          *p = std::numeric_limits<PixelType>::max();
+        }
+        else if (v < minValue)
+        {
+          *p = std::numeric_limits<PixelType>::min();
+        }
+        else
+        {
+          *p = static_cast<PixelType>(v);
+        }
+      }
+    }
+  }
+
+
+
+  template <typename PixelType,
+            bool UseRound>
+  static void MultiplyConstantInternal(ImageAccessor& image,
+                                       float factor)
+  {
+    if (std::abs(factor - 1.0f) <= std::numeric_limits<float>::epsilon())
+    {
+      return;
+    }
+
+    const int64_t minValue = std::numeric_limits<PixelType>::min();
+    const int64_t maxValue = std::numeric_limits<PixelType>::max();
+    const unsigned int width = image.GetWidth();
+
+    for (unsigned int y = 0; y < image.GetHeight(); y++)
+    {
+      PixelType* p = reinterpret_cast<PixelType*>(image.GetRow(y));
+
+      for (unsigned int x = 0; x < width; x++, p++)
+      {
+        int64_t v;
+        if (UseRound)
+        {
+          // The "round" operation is very costly
+          v = boost::math::llround(static_cast<float>(*p) * factor);
+        }
+        else
+        {
+          v = static_cast<int64_t>(static_cast<float>(*p) * factor);
+        }
+
+        if (v > maxValue)
+        {
+          *p = std::numeric_limits<PixelType>::max();
+        }
+        else if (v < minValue)
+        {
+          *p = std::numeric_limits<PixelType>::min();
+        }
+        else
+        {
+          *p = static_cast<PixelType>(v);
+        }
+      }
+    }
+  }
+
+
+  template <typename PixelType,
+            bool UseRound>
+  static void ShiftScaleInternal(ImageAccessor& image,
+                                 float offset,
+                                 float scaling)
+  {
+    const float minFloatValue = static_cast<float>(std::numeric_limits<PixelType>::min());
+    const float maxFloatValue = static_cast<float>(std::numeric_limits<PixelType>::max());
+    const PixelType minPixelValue = std::numeric_limits<PixelType>::min();
+    const PixelType maxPixelValue = std::numeric_limits<PixelType>::max();
+
+    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++)
+      {
+        float v = (static_cast<float>(*p) + offset) * scaling;
+
+        if (v > maxFloatValue)
+        {
+          *p = maxPixelValue;
+        }
+        else if (v < minFloatValue)
+        {
+          *p = minPixelValue;
+        }
+        else if (UseRound)
+        {
+          // The "round" operation is very costly
+          *p = static_cast<PixelType>(boost::math::iround(v));
+        }
+        else
+        {
+          *p = static_cast<PixelType>(v);
+        }
+      }
+    }
+  }
+
+
+  void ImageProcessing::Copy(ImageAccessor& target,
+                             const ImageAccessor& source)
+  {
+    if (target.GetWidth() != source.GetWidth() ||
+        target.GetHeight() != source.GetHeight())
+    {
+      throw OrthancException(ErrorCode_IncompatibleImageSize);
+    }
+
+    if (target.GetFormat() != source.GetFormat())
+    {
+      throw OrthancException(ErrorCode_IncompatibleImageFormat);
+    }
+
+    unsigned int lineSize = GetBytesPerPixel(source.GetFormat()) * source.GetWidth();
+
+    assert(source.GetPitch() >= lineSize && target.GetPitch() >= lineSize);
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      memcpy(target.GetRow(y), source.GetConstRow(y), lineSize);
+    }
+  }
+
+
+  void ImageProcessing::Convert(ImageAccessor& target,
+                                const ImageAccessor& source)
+  {
+    if (target.GetWidth() != source.GetWidth() ||
+        target.GetHeight() != source.GetHeight())
+    {
+      throw OrthancException(ErrorCode_IncompatibleImageSize);
+    }
+
+    if (source.GetFormat() == target.GetFormat())
+    {
+      Copy(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Grayscale16 &&
+        source.GetFormat() == PixelFormat_Grayscale8)
+    {
+      ConvertInternal<uint16_t, uint8_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_SignedGrayscale16 &&
+        source.GetFormat() == PixelFormat_Grayscale8)
+    {
+      ConvertInternal<int16_t, uint8_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Grayscale8 &&
+        source.GetFormat() == PixelFormat_Grayscale16)
+    {
+      ConvertInternal<uint8_t, uint16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_SignedGrayscale16 &&
+        source.GetFormat() == PixelFormat_Grayscale16)
+    {
+      ConvertInternal<int16_t, uint16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Grayscale8 &&
+        source.GetFormat() == PixelFormat_SignedGrayscale16)
+    {
+      ConvertInternal<uint8_t, int16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Grayscale16 &&
+        source.GetFormat() == PixelFormat_SignedGrayscale16)
+    {
+      ConvertInternal<uint16_t, int16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Grayscale8 &&
+        source.GetFormat() == PixelFormat_RGB24)
+    {
+      ConvertColorToGrayscale<uint8_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Grayscale16 &&
+        source.GetFormat() == PixelFormat_RGB24)
+    {
+      ConvertColorToGrayscale<uint16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_SignedGrayscale16 &&
+        source.GetFormat() == PixelFormat_RGB24)
+    {
+      ConvertColorToGrayscale<int16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Float32 &&
+        source.GetFormat() == PixelFormat_Grayscale8)
+    {
+      ConvertGrayscaleToFloat<uint8_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Float32 &&
+        source.GetFormat() == PixelFormat_Grayscale16)
+    {
+      ConvertGrayscaleToFloat<uint16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Float32 &&
+        source.GetFormat() == PixelFormat_Grayscale32)
+    {
+      ConvertGrayscaleToFloat<uint32_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Float32 &&
+        source.GetFormat() == PixelFormat_SignedGrayscale16)
+    {
+      ConvertGrayscaleToFloat<int16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Grayscale8 &&
+        source.GetFormat() == PixelFormat_RGBA32)
+    {
+      for (unsigned int y = 0; y < source.GetHeight(); 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 < source.GetWidth(); x++, q++)
+        {
+          *q = static_cast<uint8_t>((2126 * static_cast<uint32_t>(p[0]) +
+                                     7152 * static_cast<uint32_t>(p[1]) +
+                                     0722 * static_cast<uint32_t>(p[2])) / 10000);
+          p += 4;
+        }
+      }
+
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_RGB24 &&
+        source.GetFormat() == PixelFormat_RGBA32)
+    {
+      for (unsigned int y = 0; y < source.GetHeight(); 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 < source.GetWidth(); x++)
+        {
+          q[0] = p[0];
+          q[1] = p[1];
+          q[2] = p[2];
+          p += 4;
+          q += 3;
+        }
+      }
+
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_RGB24 &&
+        source.GetFormat() == PixelFormat_BGRA32)
+    {
+      for (unsigned int y = 0; y < source.GetHeight(); 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 < source.GetWidth(); x++)
+        {
+          q[0] = p[2];
+          q[1] = p[1];
+          q[2] = p[0];
+          p += 4;
+          q += 3;
+        }
+      }
+
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_RGBA32 &&
+        source.GetFormat() == PixelFormat_RGB24)
+    {
+      for (unsigned int y = 0; y < source.GetHeight(); 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 < source.GetWidth(); x++)
+        {
+          q[0] = p[0];
+          q[1] = p[1];
+          q[2] = p[2];
+          q[3] = 255;   // Set the alpha channel to full opacity
+          p += 3;
+          q += 4;
+        }
+      }
+
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_RGB24 &&
+        source.GetFormat() == PixelFormat_Grayscale8)
+    {
+      for (unsigned int y = 0; y < source.GetHeight(); 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 < source.GetWidth(); x++)
+        {
+          q[0] = *p;
+          q[1] = *p;
+          q[2] = *p;
+          p += 1;
+          q += 3;
+        }
+      }
+
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_RGBA32 &&
+        source.GetFormat() == PixelFormat_Grayscale8)
+    {
+      for (unsigned int y = 0; y < source.GetHeight(); 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 < source.GetWidth(); x++)
+        {
+          q[0] = *p;
+          q[1] = *p;
+          q[2] = *p;
+          q[3] = 255;
+          p += 1;
+          q += 4;
+        }
+      }
+
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_BGRA32 &&
+        source.GetFormat() == PixelFormat_Grayscale16)
+    {
+      for (unsigned int y = 0; y < source.GetHeight(); y++)
+      {
+        const uint16_t* p = reinterpret_cast<const uint16_t*>(source.GetConstRow(y));
+        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
+        for (unsigned int x = 0; x < source.GetWidth(); x++)
+        {
+          uint8_t value = (*p < 256 ? *p : 255);
+          q[0] = value;
+          q[1] = value;
+          q[2] = value;
+          q[3] = 255;
+          p += 1;
+          q += 4;
+        }
+      }
+
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_BGRA32 &&
+        source.GetFormat() == PixelFormat_SignedGrayscale16)
+    {
+      for (unsigned int y = 0; y < source.GetHeight(); y++)
+      {
+        const int16_t* p = reinterpret_cast<const int16_t*>(source.GetConstRow(y));
+        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
+        for (unsigned int x = 0; x < source.GetWidth(); x++)
+        {
+          uint8_t value;
+          if (*p < 0)
+          {
+            value = 0;
+          }
+          else if (*p > 255)
+          {
+            value = 255;
+          }
+          else
+          {
+            value = static_cast<uint8_t>(*p);
+          }
+
+          q[0] = value;
+          q[1] = value;
+          q[2] = value;
+          q[3] = 255;
+          p += 1;
+          q += 4;
+        }
+      }
+
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_BGRA32 &&
+        source.GetFormat() == PixelFormat_RGB24)
+    {
+      for (unsigned int y = 0; y < source.GetHeight(); 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 < source.GetWidth(); x++)
+        {
+          q[0] = p[2];
+          q[1] = p[1];
+          q[2] = p[0];
+          q[3] = 255;
+          p += 3;
+          q += 4;
+        }
+      }
+
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_RGB24 &&
+        source.GetFormat() == PixelFormat_RGB48)
+    {
+      for (unsigned int y = 0; y < source.GetHeight(); y++)
+      {
+        const uint16_t* p = reinterpret_cast<const uint16_t*>(source.GetConstRow(y));
+        uint8_t* q = reinterpret_cast<uint8_t*>(target.GetRow(y));
+        for (unsigned int x = 0; x < source.GetWidth(); x++)
+        {
+          q[0] = p[0] >> 8;
+          q[1] = p[1] >> 8;
+          q[2] = p[2] >> 8;
+          p += 3;
+          q += 3;
+        }
+      }
+
+      return;
+    }
+
+    throw OrthancException(ErrorCode_NotImplemented);
+  }
+
+
+
+  void ImageProcessing::Set(ImageAccessor& image,
+                            int64_t value)
+  {
+    switch (image.GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+        memset(image.GetBuffer(), static_cast<uint8_t>(value), image.GetPitch() * image.GetHeight());
+        return;
+
+      case PixelFormat_Grayscale16:
+        if (value == 0)
+        {
+          memset(image.GetBuffer(), 0, image.GetPitch() * image.GetHeight());
+        }
+        else
+        {
+          SetInternal<uint16_t>(image, value);
+        }
+        return;
+
+      case PixelFormat_Grayscale32:
+        if (value == 0)
+        {
+          memset(image.GetBuffer(), 0, image.GetPitch() * image.GetHeight());
+        }
+        else
+        {
+          SetInternal<uint32_t>(image, value);
+        }
+        return;
+
+      case PixelFormat_SignedGrayscale16:
+        if (value == 0)
+        {
+          memset(image.GetBuffer(), 0, image.GetPitch() * image.GetHeight());
+        }
+        else
+        {
+          SetInternal<int16_t>(image, value);
+        }
+        return;
+
+      case PixelFormat_Float32:
+        assert(sizeof(float) == 4);
+        SetInternal<float>(image, value);
+        return;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void ImageProcessing::Set(ImageAccessor& image,
+                            uint8_t red,
+                            uint8_t green,
+                            uint8_t blue,
+                            uint8_t alpha)
+  {
+    uint8_t p[4];
+    unsigned int size;
+
+    switch (image.GetFormat())
+    {
+      case PixelFormat_RGBA32:
+        p[0] = red;
+        p[1] = green;
+        p[2] = blue;
+        p[3] = alpha;
+        size = 4;
+        break;
+
+      case PixelFormat_BGRA32:
+        p[0] = blue;
+        p[1] = green;
+        p[2] = red;
+        p[3] = alpha;
+        size = 4;
+        break;
+
+      case PixelFormat_RGB24:
+        p[0] = red;
+        p[1] = green;
+        p[2] = blue;
+        size = 3;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }    
+
+    for (unsigned int y = 0; y < image.GetHeight(); y++)
+    {
+      uint8_t* q = reinterpret_cast<uint8_t*>(image.GetRow(y));
+
+      for (unsigned int x = 0; x < image.GetWidth(); x++)
+      {
+        for (unsigned int i = 0; i < size; i++)
+        {
+          q[i] = p[i];
+        }
+
+        q += size;
+      }
+    }
+  }
+
+
+  void ImageProcessing::ShiftRight(ImageAccessor& image,
+                                   unsigned int shift)
+  {
+    if (image.GetWidth() == 0 ||
+        image.GetHeight() == 0 ||
+        shift == 0)
+    {
+      // Nothing to do
+      return;
+    }
+
+    throw OrthancException(ErrorCode_NotImplemented);
+  }
+
+
+  void ImageProcessing::GetMinMaxIntegerValue(int64_t& minValue,
+                                              int64_t& maxValue,
+                                              const ImageAccessor& image)
+  {
+    switch (image.GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+      {
+        uint8_t a, b;
+        GetMinMaxValueInternal<uint8_t>(a, b, image);
+        minValue = a;
+        maxValue = b;
+        break;
+      }
+
+      case PixelFormat_Grayscale16:
+      {
+        uint16_t a, b;
+        GetMinMaxValueInternal<uint16_t>(a, b, image);
+        minValue = a;
+        maxValue = b;
+        break;
+      }
+
+      case PixelFormat_Grayscale32:
+      {
+        uint32_t a, b;
+        GetMinMaxValueInternal<uint32_t>(a, b, image);
+        minValue = a;
+        maxValue = b;
+        break;
+      }
+
+      case PixelFormat_SignedGrayscale16:
+      {
+        int16_t a, b;
+        GetMinMaxValueInternal<int16_t>(a, b, image);
+        minValue = a;
+        maxValue = b;
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void ImageProcessing::GetMinMaxFloatValue(float& minValue,
+                                            float& maxValue,
+                                            const ImageAccessor& image)
+  {
+    switch (image.GetFormat())
+    {
+      case PixelFormat_Float32:
+      {
+        assert(sizeof(float) == 32);
+        float a, b;
+        GetMinMaxValueInternal<float>(a, b, image);
+        minValue = a;
+        maxValue = b;
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+
+  void ImageProcessing::AddConstant(ImageAccessor& image,
+                                    int64_t value)
+  {
+    switch (image.GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+        AddConstantInternal<uint8_t>(image, value);
+        return;
+
+      case PixelFormat_Grayscale16:
+        AddConstantInternal<uint16_t>(image, value);
+        return;
+
+      case PixelFormat_SignedGrayscale16:
+        AddConstantInternal<int16_t>(image, value);
+        return;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void ImageProcessing::MultiplyConstant(ImageAccessor& image,
+                                         float factor,
+                                         bool useRound)
+  {
+    switch (image.GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+        if (useRound)
+        {
+          MultiplyConstantInternal<uint8_t, true>(image, factor);
+        }
+        else
+        {
+          MultiplyConstantInternal<uint8_t, false>(image, factor);
+        }
+        return;
+
+      case PixelFormat_Grayscale16:
+        if (useRound)
+        {
+          MultiplyConstantInternal<uint16_t, true>(image, factor);
+        }
+        else
+        {
+          MultiplyConstantInternal<uint16_t, false>(image, factor);
+        }
+        return;
+
+      case PixelFormat_SignedGrayscale16:
+        if (useRound)
+        {
+          MultiplyConstantInternal<int16_t, true>(image, factor);
+        }
+        else
+        {
+          MultiplyConstantInternal<int16_t, false>(image, factor);
+        }
+        return;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void ImageProcessing::ShiftScale(ImageAccessor& image,
+                                   float offset,
+                                   float scaling,
+                                   bool useRound)
+  {
+    switch (image.GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+        if (useRound)
+        {
+          ShiftScaleInternal<uint8_t, true>(image, offset, scaling);
+        }
+        else
+        {
+          ShiftScaleInternal<uint8_t, false>(image, offset, scaling);
+        }
+        return;
+
+      case PixelFormat_Grayscale16:
+        if (useRound)
+        {
+          ShiftScaleInternal<uint16_t, true>(image, offset, scaling);
+        }
+        else
+        {
+          ShiftScaleInternal<uint16_t, false>(image, offset, scaling);
+        }
+        return;
+
+      case PixelFormat_SignedGrayscale16:
+        if (useRound)
+        {
+          ShiftScaleInternal<int16_t, true>(image, offset, scaling);
+        }
+        else
+        {
+          ShiftScaleInternal<int16_t, false>(image, offset, scaling);
+        }
+        return;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void ImageProcessing::Invert(ImageAccessor& image)
+  {
+    switch (image.GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+      {
+        for (unsigned int y = 0; y < image.GetHeight(); y++)
+        {
+          uint8_t* p = reinterpret_cast<uint8_t*>(image.GetRow(y));
+
+          for (unsigned int x = 0; x < image.GetWidth(); x++, p++)
+          {
+            *p = 255 - (*p);
+          }
+        }
+        
+        return;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }   
+  }
+
+
+
+  namespace
+  {
+    template <Orthanc::PixelFormat Format>
+    class BresenhamPixelWriter
+    {
+    private:
+      typedef typename PixelTraits<Format>::PixelType  PixelType;
+    
+      Orthanc::ImageAccessor&  image_;
+      PixelType                value_;
+
+      void PlotLineLow(int x0,
+                       int y0,
+                       int x1,
+                       int y1)
+      {
+        int dx = x1 - x0;
+        int dy = y1 - y0;
+        int yi = 1;
+
+        if (dy < 0)
+        {
+          yi = -1;
+          dy = -dy;
+        }
+
+        int d = 2 * dy - dx;
+        int y = y0;
+
+        for (int x = x0; x <= x1; x++)
+        {
+          Write(x, y);
+          
+          if (d > 0)
+          {
+            y = y + yi;
+            d = d - 2 * dx;
+          }
+      
+          d = d + 2*dy;
+        }
+      }
+      
+      void PlotLineHigh(int x0,
+                        int y0,
+                        int x1,
+                        int y1)
+      {
+        int dx = x1 - x0;
+        int dy = y1 - y0;
+        int xi = 1;
+    
+        if (dx < 0)
+        {
+          xi = -1;
+          dx = -dx;
+        }
+    
+        int d = 2 * dx - dy;
+        int x = x0;
+
+        for (int y = y0; y <= y1; y++)
+        {
+          Write(x, y);
+          
+          if (d > 0)
+          {
+            x = x + xi;
+            d = d - 2 * dy;
+          }
+      
+          d = d + 2 * dx;
+        }
+      }
+
+    public:
+      BresenhamPixelWriter(Orthanc::ImageAccessor& image,
+                           int64_t value) :
+        image_(image),
+        value_(PixelTraits<Format>::IntegerToPixel(value))
+      {
+      }
+
+      BresenhamPixelWriter(Orthanc::ImageAccessor& image,
+                           const PixelType& value) :
+        image_(image),
+        value_(value)
+      {
+      }
+
+      void Write(int x,
+                 int y)
+      {
+        if (x >= 0 &&
+            y >= 0 &&
+            static_cast<unsigned int>(x) < image_.GetWidth() &&
+            static_cast<unsigned int>(y) < image_.GetHeight())
+        {
+          PixelType* p = reinterpret_cast<PixelType*>(image_.GetRow(y));
+          p[x] = value_;
+        }
+      }
+
+      void DrawSegment(int x0,
+                       int y0,
+                       int x1,
+                       int y1)
+      {
+        // This is an implementation of Bresenham's line algorithm
+        // https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm#All_cases
+    
+        if (abs(y1 - y0) < abs(x1 - x0))
+        {
+          if (x0 > x1)
+          {
+            PlotLineLow(x1, y1, x0, y0);
+          }
+          else
+          {
+            PlotLineLow(x0, y0, x1, y1);
+          }
+        }
+        else
+        {
+          if (y0 > y1)
+          {
+            PlotLineHigh(x1, y1, x0, y0);
+          }
+          else
+          {
+            PlotLineHigh(x0, y0, x1, y1);
+          }
+        }
+      }
+    };
+  }
+
+  
+  void ImageProcessing::DrawLineSegment(ImageAccessor& image,
+                                        int x0,
+                                        int y0,
+                                        int x1,
+                                        int y1,
+                                        int64_t value)
+  {
+    switch (image.GetFormat())
+    {       
+      case Orthanc::PixelFormat_Grayscale8:
+      {
+        BresenhamPixelWriter<Orthanc::PixelFormat_Grayscale8> writer(image, value);
+        writer.DrawSegment(x0, y0, x1, y1);
+        break;
+      }
+
+      case Orthanc::PixelFormat_Grayscale16:
+      {
+        BresenhamPixelWriter<Orthanc::PixelFormat_Grayscale16> writer(image, value);
+        writer.DrawSegment(x0, y0, x1, y1);
+        break;
+      }
+
+      case Orthanc::PixelFormat_SignedGrayscale16:
+      {
+        BresenhamPixelWriter<Orthanc::PixelFormat_SignedGrayscale16> writer(image, value);
+        writer.DrawSegment(x0, y0, x1, y1);
+        break;
+      }
+        
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+
+  
+  void ImageProcessing::DrawLineSegment(ImageAccessor& image,
+                                        int x0,
+                                        int y0,
+                                        int x1,
+                                        int y1,
+                                        uint8_t red,
+                                        uint8_t green,
+                                        uint8_t blue,
+                                        uint8_t alpha)
+  {
+    switch (image.GetFormat())
+    {
+      case Orthanc::PixelFormat_BGRA32:
+      {
+        PixelTraits<Orthanc::PixelFormat_BGRA32>::PixelType pixel;
+        pixel.red_ = red;
+        pixel.green_ = green;
+        pixel.blue_ = blue;
+        pixel.alpha_ = alpha;
+
+        BresenhamPixelWriter<Orthanc::PixelFormat_BGRA32> writer(image, pixel);
+        writer.DrawSegment(x0, y0, x1, y1);
+        break;
+      }
+        
+      case Orthanc::PixelFormat_RGB24:
+      {
+        PixelTraits<Orthanc::PixelFormat_RGB24>::PixelType pixel;
+        pixel.red_ = red;
+        pixel.green_ = green;
+        pixel.blue_ = blue;
+
+        BresenhamPixelWriter<Orthanc::PixelFormat_RGB24> writer(image, pixel);
+        writer.DrawSegment(x0, y0, x1, y1);
+        break;
+      }
+        
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/ImageProcessing.h	Fri Mar 23 11:04:03 2018 +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-2018 Osimis S.A., 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 "ImageAccessor.h"
+
+#include <stdint.h>
+
+namespace Orthanc
+{
+  namespace ImageProcessing
+  {
+    void Copy(ImageAccessor& target,
+              const ImageAccessor& source);
+
+    void Convert(ImageAccessor& target,
+                 const ImageAccessor& source);
+
+    void Set(ImageAccessor& image,
+             int64_t value);
+
+    void Set(ImageAccessor& image,
+             uint8_t red,
+             uint8_t green,
+             uint8_t blue,
+             uint8_t alpha);
+
+    void ShiftRight(ImageAccessor& target,
+                    unsigned int shift);
+
+    void GetMinMaxIntegerValue(int64_t& minValue,
+                               int64_t& maxValue,
+                               const ImageAccessor& image);
+
+    void GetMinMaxFloatValue(float& minValue,
+                             float& maxValue,
+                             const ImageAccessor& image);
+
+    void AddConstant(ImageAccessor& image,
+                     int64_t value);
+
+    // "useRound" is expensive
+    void MultiplyConstant(ImageAccessor& image,
+                          float factor,
+                          bool useRound);
+
+    // "useRound" is expensive
+    void ShiftScale(ImageAccessor& image,
+                    float offset,
+                    float scaling,
+                    bool useRound);
+
+    void Invert(ImageAccessor& image);
+
+    void DrawLineSegment(ImageAccessor& image,
+                         int x0,
+                         int y0,
+                         int x1,
+                         int y1,
+                         int64_t value);
+
+    void DrawLineSegment(ImageAccessor& image,
+                         int x0,
+                         int y0,
+                         int x1,
+                         int y1,
+                         uint8_t red,
+                         uint8_t green,
+                         uint8_t blue,
+                         uint8_t alpha);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/ImageTraits.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,89 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "ImageAccessor.h"
+#include "PixelTraits.h"
+
+#include <cassert>
+
+namespace Orthanc
+{
+  template <PixelFormat Format>
+  struct ImageTraits
+  {
+    typedef ::Orthanc::PixelTraits<Format>    PixelTraits;
+    typedef typename PixelTraits::PixelType   PixelType;
+
+    static PixelFormat GetPixelFormat()
+    {
+      return Format;
+    }
+
+    static void GetPixel(PixelType& target,
+                         const ImageAccessor& image,
+                         unsigned int x,
+                         unsigned int y)
+    {
+      assert(x < image.GetWidth() && y < image.GetHeight());
+      PixelTraits::Copy(target, image.GetPixelUnchecked<PixelType>(x, y));
+    }
+
+    static void SetPixel(ImageAccessor& image,
+                         const PixelType& value,
+                         unsigned int x,
+                         unsigned int y)
+    {
+      assert(x < image.GetWidth() && y < image.GetHeight());
+      PixelTraits::Copy(image.GetPixelUnchecked<PixelType>(x, y), value);
+    }
+
+    static float GetFloatPixel(const ImageAccessor& image,
+                               unsigned int x,
+                               unsigned int y)
+    {
+      assert(x < image.GetWidth() && y < image.GetHeight());
+      return PixelTraits::PixelToFloat(image.GetPixelUnchecked<PixelType>(x, y));
+    }
+
+    static void SetFloatPixel(ImageAccessor& image,
+                              float value,
+                              unsigned int x,
+                              unsigned int y)
+    {
+      assert(x < image.GetWidth() && y < image.GetHeight());
+      PixelTraits::FloatToPixel(image.GetPixelUnchecked<PixelType>(x, y), value);
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/JpegErrorManager.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,70 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "JpegErrorManager.h"
+
+namespace Orthanc
+{
+  namespace Internals
+  {
+    void JpegErrorManager::OutputMessage(j_common_ptr cinfo)
+    {
+      char message[JMSG_LENGTH_MAX];
+      (*cinfo->err->format_message) (cinfo, message);
+
+      JpegErrorManager* that = reinterpret_cast<JpegErrorManager*>(cinfo->err);
+      that->message = std::string(message);
+    }
+
+
+    void JpegErrorManager::ErrorExit(j_common_ptr cinfo)
+    {
+      (*cinfo->err->output_message) (cinfo);
+
+      JpegErrorManager* that = reinterpret_cast<JpegErrorManager*>(cinfo->err);
+      longjmp(that->setjmp_buffer, 1);
+    }
+      
+
+    JpegErrorManager::JpegErrorManager()
+    {
+      memset(&pub, 0, sizeof(struct jpeg_error_mgr));
+      memset(&setjmp_buffer, 0, sizeof(jmp_buf));
+
+      jpeg_std_error(&pub);
+      pub.error_exit = ErrorExit;
+      pub.output_message = OutputMessage;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/JpegErrorManager.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,83 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 !defined(ORTHANC_ENABLE_JPEG)
+#  error The macro ORTHANC_ENABLE_JPEG must be defined
+#endif
+
+#if ORTHANC_ENABLE_JPEG != 1
+#  error JPEG support must be enabled to include this file
+#endif
+
+#include <string.h>
+#include <stdio.h>
+#include <jpeglib.h>
+#include <setjmp.h>
+#include <string>
+
+namespace Orthanc
+{
+  namespace Internals
+  {
+    class JpegErrorManager 
+    {
+    private:
+      struct jpeg_error_mgr pub;  /* "public" fields */
+      jmp_buf setjmp_buffer;      /* for return to caller */
+      std::string message;
+
+      static void OutputMessage(j_common_ptr cinfo);
+
+      static void ErrorExit(j_common_ptr cinfo);
+
+    public:
+      JpegErrorManager();
+
+      struct jpeg_error_mgr* GetPublic()
+      {
+        return &pub;
+      }
+
+      jmp_buf& GetJumpBuffer()
+      {
+        return setjmp_buffer;
+      }
+
+      const std::string& GetMessage() const
+      {
+        return message;
+      }
+    };
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/JpegReader.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,192 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "JpegReader.h"
+
+#include "JpegErrorManager.h"
+#include "../OrthancException.h"
+#include "../Logging.h"
+
+#if ORTHANC_SANDBOXED == 0
+#  include "../SystemToolbox.h"
+#endif
+
+
+namespace Orthanc
+{
+  static void Uncompress(struct jpeg_decompress_struct& cinfo,
+                         std::string& content,
+                         ImageAccessor& accessor)
+  {
+    jpeg_read_header(&cinfo, TRUE);
+    jpeg_start_decompress(&cinfo);
+
+    PixelFormat format;
+    if (cinfo.output_components == 1 &&
+        cinfo.out_color_space == JCS_GRAYSCALE)
+    {
+      format = PixelFormat_Grayscale8;
+    }
+    else if (cinfo.output_components == 3 &&
+             cinfo.out_color_space == JCS_RGB)
+    {
+      format = PixelFormat_RGB24;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    unsigned int pitch = cinfo.output_width * cinfo.output_components;
+
+    /* Make a one-row-high sample array that will go away when done with image */
+    JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, pitch, 1);
+
+    try
+    {
+      content.resize(pitch * cinfo.output_height);
+    }
+    catch (...)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+
+    accessor.AssignWritable(format, cinfo.output_width, cinfo.output_height, pitch, 
+                            content.empty() ? NULL : &content[0]);
+
+    uint8_t* target = reinterpret_cast<uint8_t*>(&content[0]);
+    while (cinfo.output_scanline < cinfo.output_height) 
+    {
+      jpeg_read_scanlines(&cinfo, buffer, 1);
+      memcpy(target, buffer[0], pitch);
+      target += pitch;
+    }
+
+    // Everything went fine, "setjmp()" didn't get called
+
+    jpeg_finish_decompress(&cinfo);
+  }
+
+
+#if ORTHANC_SANDBOXED == 0
+  void JpegReader::ReadFromFile(const std::string& filename)
+  {
+    FILE* fp = SystemToolbox::OpenFile(filename, FileMode_ReadBinary);
+    if (!fp)
+    {
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+
+    struct jpeg_decompress_struct cinfo;
+    memset(&cinfo, 0, sizeof(struct jpeg_decompress_struct));
+
+    Internals::JpegErrorManager jerr;
+    cinfo.err = jerr.GetPublic();
+    
+    if (setjmp(jerr.GetJumpBuffer())) 
+    {
+      jpeg_destroy_decompress(&cinfo);
+      fclose(fp);
+      LOG(ERROR) << "Error during JPEG decoding: " << jerr.GetMessage();
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    // Below this line, we are under the scope of a "setjmp"
+
+    jpeg_create_decompress(&cinfo);
+    jpeg_stdio_src(&cinfo, fp);
+
+    try
+    {
+      Uncompress(cinfo, content_, *this);
+    }
+    catch (OrthancException&)
+    {
+      jpeg_destroy_decompress(&cinfo);
+      fclose(fp);
+      throw;
+    }
+
+    jpeg_destroy_decompress(&cinfo);
+    fclose(fp);
+  }
+#endif
+
+
+  void JpegReader::ReadFromMemory(const void* buffer,
+                                  size_t size)
+  {
+    struct jpeg_decompress_struct cinfo;
+    memset(&cinfo, 0, sizeof(struct jpeg_decompress_struct));
+
+    Internals::JpegErrorManager jerr;
+    cinfo.err = jerr.GetPublic();
+    
+    if (setjmp(jerr.GetJumpBuffer())) 
+    {
+      jpeg_destroy_decompress(&cinfo);
+      LOG(ERROR) << "Error during JPEG decoding: " << jerr.GetMessage();
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    // Below this line, we are under the scope of a "setjmp"
+    jpeg_create_decompress(&cinfo);
+    jpeg_mem_src(&cinfo, const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(buffer)), size);
+
+    try
+    {
+      Uncompress(cinfo, content_, *this);
+    }
+    catch (OrthancException&)
+    {
+      jpeg_destroy_decompress(&cinfo);
+      throw;
+    }
+
+    jpeg_destroy_decompress(&cinfo);
+  }
+
+
+  void JpegReader::ReadFromMemory(const std::string& buffer)
+  {
+    if (buffer.empty())
+    {
+      ReadFromMemory(NULL, 0);
+    }
+    else
+    {
+      ReadFromMemory(buffer.c_str(), buffer.size());
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/JpegReader.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,72 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 !defined(ORTHANC_SANDBOXED)
+#  error The macro ORTHANC_SANDBOXED must be defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_JPEG)
+#  error The macro ORTHANC_ENABLE_JPEG must be defined
+#endif
+
+#if ORTHANC_ENABLE_JPEG != 1
+#  error JPEG support must be enabled to include this file
+#endif
+
+#include "ImageAccessor.h"
+
+#include <string>
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class JpegReader : 
+    public ImageAccessor,
+    public boost::noncopyable
+  {
+  private:
+    std::string  content_;
+
+  public:
+#if ORTHANC_SANDBOXED == 0
+    void ReadFromFile(const std::string& filename);
+#endif
+
+    void ReadFromMemory(const void* buffer,
+                        size_t size);
+
+    void ReadFromMemory(const std::string& buffer);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/JpegWriter.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,211 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "JpegWriter.h"
+
+#include "../OrthancException.h"
+#include "../Logging.h"
+#include "JpegErrorManager.h"
+
+#if ORTHANC_SANDBOXED == 0
+#  include "../SystemToolbox.h"
+#endif
+
+#include <stdlib.h>
+#include <vector>
+
+namespace Orthanc
+{
+  static void GetLines(std::vector<uint8_t*>& lines,
+                       unsigned int height,
+                       unsigned int pitch,
+                       PixelFormat format,
+                       const void* buffer)
+  {
+    if (format != PixelFormat_Grayscale8 &&
+        format != PixelFormat_RGB24)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    lines.resize(height);
+
+    uint8_t* base = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(buffer));
+    for (unsigned int y = 0; y < height; y++)
+    {
+      lines[y] = base + static_cast<intptr_t>(y) * static_cast<intptr_t>(pitch);
+    }
+  }
+
+
+  static void Compress(struct jpeg_compress_struct& cinfo,
+                       std::vector<uint8_t*>& lines,
+                       unsigned int width,
+                       unsigned int height,
+                       PixelFormat format,
+                       uint8_t quality)
+  {
+    cinfo.image_width = width;
+    cinfo.image_height = height;
+
+    switch (format)
+    {
+      case PixelFormat_Grayscale8:
+        cinfo.input_components = 1;
+        cinfo.in_color_space = JCS_GRAYSCALE;
+        break;
+
+      case PixelFormat_RGB24:
+        cinfo.input_components = 3;
+        cinfo.in_color_space = JCS_RGB;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+
+    jpeg_set_defaults(&cinfo);
+    jpeg_set_quality(&cinfo, quality, TRUE);
+    jpeg_start_compress(&cinfo, TRUE);
+    jpeg_write_scanlines(&cinfo, &lines[0], height);
+    jpeg_finish_compress(&cinfo);
+    jpeg_destroy_compress(&cinfo);
+  }
+                       
+
+  void JpegWriter::SetQuality(uint8_t quality)
+  {
+    if (quality == 0 || quality > 100)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    quality_ = quality;
+  }
+
+
+#if ORTHANC_SANDBOXED == 0
+  void JpegWriter::WriteToFileInternal(const std::string& filename,
+                                       unsigned int width,
+                                       unsigned int height,
+                                       unsigned int pitch,
+                                       PixelFormat format,
+                                       const void* buffer)
+  {
+    FILE* fp = SystemToolbox::OpenFile(filename, FileMode_WriteBinary);
+    if (fp == NULL)
+    {
+      throw OrthancException(ErrorCode_CannotWriteFile);
+    }
+
+    std::vector<uint8_t*> lines;
+    GetLines(lines, height, pitch, format, buffer);
+
+    struct jpeg_compress_struct cinfo;
+    memset(&cinfo, 0, sizeof(struct jpeg_compress_struct));
+
+    Internals::JpegErrorManager jerr;
+    cinfo.err = jerr.GetPublic();
+
+    if (setjmp(jerr.GetJumpBuffer())) 
+    {
+      /* If we get here, the JPEG code has signaled an error.
+       * We need to clean up the JPEG object, close the input file, and return.
+       */
+      jpeg_destroy_compress(&cinfo);
+      fclose(fp);
+      LOG(ERROR) << "Error during JPEG encoding: " << jerr.GetMessage();
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    // Do not allocate data on the stack below this line!
+
+    jpeg_create_compress(&cinfo);
+    jpeg_stdio_dest(&cinfo, fp);
+    Compress(cinfo, lines, width, height, format, quality_);
+
+    // Everything went fine, "setjmp()" didn't get called
+
+    fclose(fp);
+  }
+#endif
+
+
+#if ORTHANC_SANDBOXED == 0
+  void JpegWriter::WriteToMemoryInternal(std::string& jpeg,
+                                         unsigned int width,
+                                         unsigned int height,
+                                         unsigned int pitch,
+                                         PixelFormat format,
+                                         const void* buffer)
+  {
+    std::vector<uint8_t*> lines;
+    GetLines(lines, height, pitch, format, buffer);
+
+    struct jpeg_compress_struct cinfo;
+    memset(&cinfo, 0, sizeof(struct jpeg_compress_struct));
+
+    Internals::JpegErrorManager jerr;
+
+    unsigned char* data = NULL;
+    unsigned long size;
+
+    if (setjmp(jerr.GetJumpBuffer())) 
+    {
+      jpeg_destroy_compress(&cinfo);
+
+      if (data != NULL)
+      {
+        free(data);
+      }
+
+      LOG(ERROR) << "Error during JPEG encoding: " << jerr.GetMessage();
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    // Do not allocate data on the stack below this line!
+
+    jpeg_create_compress(&cinfo);
+    cinfo.err = jerr.GetPublic();
+    jpeg_mem_dest(&cinfo, &data, &size);
+
+    Compress(cinfo, lines, width, height, format, quality_);
+
+    // Everything went fine, "setjmp()" didn't get called
+
+    jpeg.assign(reinterpret_cast<const char*>(data), size);
+    free(data);
+  }
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/JpegWriter.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,82 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 !defined(ORTHANC_ENABLE_JPEG)
+#  error The macro ORTHANC_ENABLE_JPEG must be defined
+#endif
+
+#if ORTHANC_ENABLE_JPEG != 1
+#  error JPEG support must be enabled to include this file
+#endif
+
+#include "IImageWriter.h"
+
+namespace Orthanc
+{
+  class JpegWriter : public IImageWriter
+  {
+  protected:
+    virtual void WriteToFileInternal(const std::string& filename,
+                                     unsigned int width,
+                                     unsigned int height,
+                                     unsigned int pitch,
+                                     PixelFormat format,
+                                     const void* buffer);
+
+#if ORTHANC_SANDBOXED == 0
+    virtual void WriteToMemoryInternal(std::string& jpeg,
+                                       unsigned int width,
+                                       unsigned int height,
+                                       unsigned int pitch,
+                                       PixelFormat format,
+                                       const void* buffer);
+#endif
+
+  private:
+    uint8_t  quality_;
+
+  public:
+    JpegWriter() : quality_(90)
+    {
+    }
+
+    void SetQuality(uint8_t quality);
+
+    uint8_t GetQuality() const
+    {
+      return quality_;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/PixelTraits.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,267 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "../Enumerations.h"
+#include "../OrthancException.h"
+
+#include <limits>
+
+namespace Orthanc
+{
+  template <PixelFormat format,
+            typename _PixelType>
+  struct IntegerPixelTraits
+  {
+    typedef _PixelType  PixelType;
+
+    ORTHANC_FORCE_INLINE
+    static PixelFormat GetPixelFormat()
+    {
+      return format;
+    }
+
+    ORTHANC_FORCE_INLINE
+    static PixelType IntegerToPixel(int64_t value)
+    {
+      if (value < static_cast<int64_t>(std::numeric_limits<PixelType>::min()) ||
+          value > static_cast<int64_t>(std::numeric_limits<PixelType>::max()))
+      {
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+      else
+      {
+        return static_cast<PixelType>(value);
+      }
+    }
+
+    ORTHANC_FORCE_INLINE
+    static void SetZero(PixelType& target)
+    {
+      target = 0;
+    }
+
+    ORTHANC_FORCE_INLINE
+    static void SetMinValue(PixelType& target)
+    {
+      target = std::numeric_limits<PixelType>::min();
+    }
+
+    ORTHANC_FORCE_INLINE
+    static void SetMaxValue(PixelType& target)
+    {
+      target = std::numeric_limits<PixelType>::max();
+    }
+
+    ORTHANC_FORCE_INLINE
+    static void Copy(PixelType& target,
+                     const PixelType& source)
+    {
+      target = source;
+    }
+
+    ORTHANC_FORCE_INLINE
+    static float PixelToFloat(const PixelType& source)
+    {
+      return static_cast<float>(source);
+    }
+
+    ORTHANC_FORCE_INLINE
+    static void FloatToPixel(PixelType& target,
+                             float value)
+    {
+      if (value < static_cast<float>(std::numeric_limits<PixelType>::min()))
+      {
+        target = std::numeric_limits<PixelType>::min();
+      }
+      else if (value > static_cast<float>(std::numeric_limits<PixelType>::max()))
+      {
+        target = std::numeric_limits<PixelType>::max();
+      }
+      else
+      {
+        target = static_cast<PixelType>(value);
+      }
+    }
+
+    ORTHANC_FORCE_INLINE
+    static bool IsEqual(const PixelType& a,
+                        const PixelType& b)
+    {
+      return a == b;
+    }
+  };
+
+
+  template <PixelFormat Format>
+  struct PixelTraits;
+
+
+  template <>
+  struct PixelTraits<PixelFormat_Grayscale8> :
+    public IntegerPixelTraits<PixelFormat_Grayscale8, uint8_t>
+  {
+  };
+
+  
+  template <>
+  struct PixelTraits<PixelFormat_Grayscale16> :
+    public IntegerPixelTraits<PixelFormat_Grayscale16, uint16_t>
+  {
+  };
+
+  
+  template <>
+  struct PixelTraits<PixelFormat_SignedGrayscale16> :
+    public IntegerPixelTraits<PixelFormat_SignedGrayscale16, int16_t>
+  {
+  };
+
+
+  template <>
+  struct PixelTraits<PixelFormat_RGB24>
+  {
+    struct PixelType
+    {
+      uint8_t  red_;
+      uint8_t  green_;
+      uint8_t  blue_;
+    };
+
+    ORTHANC_FORCE_INLINE
+    static PixelFormat GetPixelFormat()
+    {
+      return PixelFormat_RGB24;
+    }
+
+    ORTHANC_FORCE_INLINE
+    static void SetZero(PixelType& target)
+    {
+      target.red_ = 0;
+      target.green_ = 0;
+      target.blue_ = 0;
+    }
+
+    ORTHANC_FORCE_INLINE
+    static void Copy(PixelType& target,
+                     const PixelType& source)
+    {
+      target.red_ = source.red_;
+      target.green_ = source.green_;
+      target.blue_ = source.blue_;
+    }
+
+    ORTHANC_FORCE_INLINE
+    static bool IsEqual(const PixelType& a,
+                        const PixelType& b)
+    {
+      return (a.red_ == b.red_ &&
+              a.green_ == b.green_ &&
+              a.blue_ == b.blue_);
+    }
+
+    ORTHANC_FORCE_INLINE
+    static void FloatToPixel(PixelType& target,
+                             float value)
+    {
+      uint8_t v;
+      PixelTraits<PixelFormat_Grayscale8>::FloatToPixel(v, value);
+
+      target.red_ = v;
+      target.green_ = v;
+      target.blue_ = v;
+    }
+  };
+
+
+  template <>
+  struct PixelTraits<PixelFormat_BGRA32>
+  {
+    struct PixelType
+    {
+      uint8_t  blue_;
+      uint8_t  green_;
+      uint8_t  red_;
+      uint8_t  alpha_;
+    };
+
+    ORTHANC_FORCE_INLINE
+    static PixelFormat GetPixelFormat()
+    {
+      return PixelFormat_BGRA32;
+    }
+
+    ORTHANC_FORCE_INLINE
+    static void SetZero(PixelType& target)
+    {
+      target.blue_ = 0;
+      target.green_ = 0;
+      target.red_ = 0;
+      target.alpha_ = 0;
+    }
+
+    ORTHANC_FORCE_INLINE
+    static void Copy(PixelType& target,
+                     const PixelType& source)
+    {
+      target.blue_ = source.blue_;
+      target.green_ = source.green_;
+      target.red_ = source.red_;
+      target.alpha_ = source.alpha_;
+    }
+
+    ORTHANC_FORCE_INLINE
+    static bool IsEqual(const PixelType& a,
+                        const PixelType& b)
+    {
+      return (a.blue_ == b.blue_ &&
+              a.green_ == b.green_ &&
+              a.red_ == b.red_ &&
+              a.alpha_ == b.alpha_);
+    }
+
+    ORTHANC_FORCE_INLINE
+    static void FloatToPixel(PixelType& target,
+                             float value)
+    {
+      uint8_t v;
+      PixelTraits<PixelFormat_Grayscale8>::FloatToPixel(v, value);
+
+      target.blue_ = v;
+      target.green_ = v;
+      target.red_ = v;
+      target.alpha_ = 255;      
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/PngReader.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,325 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "PngReader.h"
+
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+
+#if ORTHANC_SANDBOXED == 0
+#  include "../SystemToolbox.h"
+#endif
+
+#include <png.h>
+#include <string.h>  // For memcpy()
+
+namespace Orthanc
+{
+#if ORTHANC_SANDBOXED == 0
+  namespace 
+  {
+    struct FileRabi
+    {
+      FILE* fp_;
+
+      FileRabi(const char* filename)
+      {
+        fp_ = SystemToolbox::OpenFile(filename, FileMode_ReadBinary);
+        if (!fp_)
+        {
+          throw OrthancException(ErrorCode_InexistentFile);
+        }
+      }
+
+      ~FileRabi()
+      {
+        if (fp_)
+        {
+          fclose(fp_);
+        }
+      }
+    };
+  }
+#endif
+
+
+  struct PngReader::PngRabi
+  {
+    png_structp png_;
+    png_infop info_;
+    png_infop endInfo_;
+
+    void Destruct()
+    {
+      if (png_)
+      {
+        png_destroy_read_struct(&png_, &info_, &endInfo_);
+
+        png_ = NULL;
+        info_ = NULL;
+        endInfo_ = NULL;
+      }
+    }
+
+    PngRabi()
+    {
+      png_ = NULL;
+      info_ = NULL;
+      endInfo_ = NULL;
+
+      png_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+      if (!png_)
+      {
+        throw OrthancException(ErrorCode_NotEnoughMemory);
+      }
+
+      info_ = png_create_info_struct(png_);
+      if (!info_)
+      {
+        png_destroy_read_struct(&png_, NULL, NULL);
+        throw OrthancException(ErrorCode_NotEnoughMemory);
+      }
+
+      endInfo_ = png_create_info_struct(png_);
+      if (!info_)
+      {
+        png_destroy_read_struct(&png_, &info_, NULL);
+        throw OrthancException(ErrorCode_NotEnoughMemory);
+      }
+    }
+
+    ~PngRabi()
+    {
+      Destruct();
+    }
+
+    static void MemoryCallback(png_structp png_ptr, 
+                               png_bytep data, 
+                               png_size_t size);
+  };
+
+
+  void PngReader::CheckHeader(const void* header)
+  {
+    int is_png = !png_sig_cmp((png_bytep) header, 0, 8);
+    if (!is_png)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+
+  PngReader::PngReader()
+  {
+  }
+
+  void PngReader::Read(PngRabi& rabi)
+  {
+    png_set_sig_bytes(rabi.png_, 8);
+
+    png_read_info(rabi.png_, rabi.info_);
+
+    png_uint_32 width, height;
+    int bit_depth, color_type, interlace_type;
+    int compression_type, filter_method;
+    // get size and bit-depth of the PNG-image
+    png_get_IHDR(rabi.png_, rabi.info_,
+                 &width, &height,
+                 &bit_depth, &color_type, &interlace_type,
+                 &compression_type, &filter_method);
+
+    PixelFormat format;
+    unsigned int pitch;
+
+    if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 8)
+    {
+      format = PixelFormat_Grayscale8;
+      pitch = width;
+    }
+    else if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth == 16)
+    {
+      format = PixelFormat_Grayscale16;
+      pitch = 2 * width;
+
+      if (Toolbox::DetectEndianness() == Endianness_Little)
+      {
+        png_set_swap(rabi.png_);
+      }
+    }
+    else if (color_type == PNG_COLOR_TYPE_RGB && bit_depth == 8)
+    {
+      format = PixelFormat_RGB24;
+      pitch = 3 * width;
+    }
+    else if (color_type == PNG_COLOR_TYPE_RGBA && bit_depth == 8)
+    {
+      format = PixelFormat_RGBA32;
+      pitch = 4 * width;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    data_.resize(height * pitch);
+
+    if (height == 0 || width == 0)
+    {
+      // Empty image, we are done
+      AssignEmpty(format);
+      return;
+    }
+    
+    png_read_update_info(rabi.png_, rabi.info_);
+
+    std::vector<png_bytep> rows(height);
+    for (size_t i = 0; i < height; i++)
+    {
+      rows[i] = &data_[0] + i * pitch;
+    }
+
+    png_read_image(rabi.png_, &rows[0]);
+
+    AssignWritable(format, width, height, pitch, &data_[0]);
+  }
+
+
+#if ORTHANC_SANDBOXED == 0
+  void PngReader::ReadFromFile(const std::string& filename)
+  {
+    FileRabi f(filename.c_str());
+
+    char header[8];
+    if (fread(header, 1, 8, f.fp_) != 8)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    CheckHeader(header);
+
+    PngRabi rabi;
+
+    if (setjmp(png_jmpbuf(rabi.png_)))
+    {
+      rabi.Destruct();
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    png_init_io(rabi.png_, f.fp_);
+
+    Read(rabi);
+  }
+#endif
+
+
+  namespace
+  {
+    struct MemoryBuffer
+    {
+      const uint8_t* buffer_;
+      size_t size_;
+      size_t pos_;
+      bool ok_;
+    };
+  }
+
+
+  void PngReader::PngRabi::MemoryCallback(png_structp png_ptr, 
+                                          png_bytep outBytes, 
+                                          png_size_t byteCountToRead)
+  {
+    MemoryBuffer* from = reinterpret_cast<MemoryBuffer*>(png_get_io_ptr(png_ptr));
+
+    if (!from->ok_)
+    {
+      return;
+    }
+
+    if (from->pos_ + byteCountToRead > from->size_)
+    {
+      from->ok_ = false;
+      return;
+    }
+
+    memcpy(outBytes, from->buffer_ + from->pos_, byteCountToRead);
+
+    from->pos_ += byteCountToRead;
+  }
+
+
+  void PngReader::ReadFromMemory(const void* buffer,
+                                 size_t size)
+  {
+    if (size < 8)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    CheckHeader(buffer);
+
+    PngRabi rabi;
+
+    if (setjmp(png_jmpbuf(rabi.png_)))
+    {
+      rabi.Destruct();
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    MemoryBuffer tmp;
+    tmp.buffer_ = reinterpret_cast<const uint8_t*>(buffer) + 8;  // We skip the header
+    tmp.size_ = size - 8;
+    tmp.pos_ = 0;
+    tmp.ok_ = true;
+
+    png_set_read_fn(rabi.png_, &tmp, PngRabi::MemoryCallback);
+
+    Read(rabi);
+
+    if (!tmp.ok_)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+
+  void PngReader::ReadFromMemory(const std::string& buffer)
+  {
+    if (buffer.size() != 0)
+    {
+      ReadFromMemory(&buffer[0], buffer.size());
+    }
+    else
+    {
+      ReadFromMemory(NULL, 0);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/PngReader.h	Fri Mar 23 11:04:03 2018 +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-2018 Osimis S.A., 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 !defined(ORTHANC_ENABLE_PNG)
+#  error The macro ORTHANC_ENABLE_PNG must be defined
+#endif
+
+#if ORTHANC_ENABLE_PNG != 1
+#  error PNG support must be enabled to include this file
+#endif
+
+#include "ImageAccessor.h"
+
+#include "../Enumerations.h"
+
+#include <vector>
+#include <stdint.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+#if !defined(ORTHANC_SANDBOXED)
+#  error The macro ORTHANC_SANDBOXED must be defined
+#endif
+
+namespace Orthanc
+{
+  class PngReader : 
+    public ImageAccessor, 
+    public boost::noncopyable
+  {
+  private:
+    struct PngRabi;
+
+    std::vector<uint8_t> data_;
+
+    void CheckHeader(const void* header);
+
+    void Read(PngRabi& rabi);
+
+  public:
+    PngReader();
+
+#if ORTHANC_SANDBOXED == 0
+    void ReadFromFile(const std::string& filename);
+#endif
+
+    void ReadFromMemory(const void* buffer,
+                        size_t size);
+
+    void ReadFromMemory(const std::string& buffer);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/PngWriter.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,276 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "PngWriter.h"
+
+#include <vector>
+#include <stdint.h>
+#include <png.h>
+#include "../OrthancException.h"
+#include "../ChunkedBuffer.h"
+#include "../Toolbox.h"
+
+#if ORTHANC_SANDBOXED == 0
+#  include "../SystemToolbox.h"
+#endif
+
+
+// http://www.libpng.org/pub/png/libpng-1.2.5-manual.html#section-4
+// http://zarb.org/~gc/html/libpng.html
+/*
+  void write_row_callback(png_ptr, png_uint_32 row, int pass)
+  {
+  }*/
+
+
+
+
+/*  bool isError_;
+
+// http://www.libpng.org/pub/png/book/chapter14.html#png.ch14.div.2
+
+static void ErrorHandler(png_structp png, png_const_charp message)
+{
+printf("** [%s]\n", message);
+
+PngWriter* that = (PngWriter*) png_get_error_ptr(png);
+that->isError_ = true;
+printf("** %d\n", (int)that);
+
+//((PngWriter*) payload)->isError_ = true;
+}
+
+static void WarningHandler(png_structp png, png_const_charp message)
+{
+  printf("++ %d\n", (int)message);
+}*/
+
+
+namespace Orthanc
+{
+  struct PngWriter::PImpl
+  {
+    png_structp png_;
+    png_infop info_;
+
+    // Filled by Prepare()
+    std::vector<uint8_t*> rows_;
+    int bitDepth_;
+    int colorType_;
+  };
+
+
+
+  PngWriter::PngWriter() : pimpl_(new PImpl)
+  {
+    pimpl_->png_ = NULL;
+    pimpl_->info_ = NULL;
+
+    pimpl_->png_ = png_create_write_struct
+      (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); //this, ErrorHandler, WarningHandler);
+    if (!pimpl_->png_)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+
+    pimpl_->info_ = png_create_info_struct(pimpl_->png_);
+    if (!pimpl_->info_)
+    {
+      png_destroy_write_struct(&pimpl_->png_, NULL);
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+  }
+
+  PngWriter::~PngWriter()
+  {
+    if (pimpl_->info_)
+    {
+      png_destroy_info_struct(pimpl_->png_, &pimpl_->info_);
+    }
+
+    if (pimpl_->png_)
+    {
+      png_destroy_write_struct(&pimpl_->png_, NULL);
+    }
+  }
+
+
+
+  void PngWriter::Prepare(unsigned int width,
+                          unsigned int height,
+                          unsigned int pitch,
+                          PixelFormat format,
+                          const void* buffer)
+  {
+    pimpl_->rows_.resize(height);
+    for (unsigned int y = 0; y < height; y++)
+    {
+      pimpl_->rows_[y] = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(buffer)) + y * pitch;
+    }
+
+    switch (format)
+    {
+    case PixelFormat_RGB24:
+      pimpl_->bitDepth_ = 8;
+      pimpl_->colorType_ = PNG_COLOR_TYPE_RGB;
+      break;
+
+    case PixelFormat_RGBA32:
+      pimpl_->bitDepth_ = 8;
+      pimpl_->colorType_ = PNG_COLOR_TYPE_RGBA;
+      break;
+
+    case PixelFormat_Grayscale8:
+      pimpl_->bitDepth_ = 8;
+      pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY;
+      break;
+
+    case PixelFormat_Grayscale16:
+    case PixelFormat_SignedGrayscale16:
+      pimpl_->bitDepth_ = 16;
+      pimpl_->colorType_ = PNG_COLOR_TYPE_GRAY;
+      break;
+
+    default:
+      throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void PngWriter::Compress(unsigned int width,
+                           unsigned int height,
+                           unsigned int pitch,
+                           PixelFormat format)
+  {
+    png_set_IHDR(pimpl_->png_, pimpl_->info_, width, height,
+                 pimpl_->bitDepth_, pimpl_->colorType_, PNG_INTERLACE_NONE,
+                 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
+
+    png_write_info(pimpl_->png_, pimpl_->info_);
+
+    if (height > 0)
+    {
+      switch (format)
+      {
+      case PixelFormat_Grayscale16:
+      case PixelFormat_SignedGrayscale16:
+      {
+        int transforms = 0;
+        if (Toolbox::DetectEndianness() == Endianness_Little)
+        {
+          transforms = PNG_TRANSFORM_SWAP_ENDIAN;
+        }
+
+        png_set_rows(pimpl_->png_, pimpl_->info_, &pimpl_->rows_[0]);
+        png_write_png(pimpl_->png_, pimpl_->info_, transforms, NULL);
+
+        break;
+      }
+
+      default:
+        png_write_image(pimpl_->png_, &pimpl_->rows_[0]);
+      }
+    }
+
+    png_write_end(pimpl_->png_, NULL);
+  }
+
+
+#if ORTHANC_SANDBOXED == 0
+  void PngWriter::WriteToFileInternal(const std::string& filename,
+                                      unsigned int width,
+                                      unsigned int height,
+                                      unsigned int pitch,
+                                      PixelFormat format,
+                                      const void* buffer)
+  {
+    Prepare(width, height, pitch, format, buffer);
+
+    FILE* fp = SystemToolbox::OpenFile(filename, FileMode_WriteBinary);
+    if (!fp)
+    {
+      throw OrthancException(ErrorCode_CannotWriteFile);
+    }    
+
+    png_init_io(pimpl_->png_, fp);
+
+    if (setjmp(png_jmpbuf(pimpl_->png_)))
+    {
+      // Error during writing PNG
+      throw OrthancException(ErrorCode_CannotWriteFile);      
+    }
+
+    Compress(width, height, pitch, format);
+
+    fclose(fp);
+  }
+#endif
+
+
+  static void MemoryCallback(png_structp png_ptr, 
+                             png_bytep data, 
+                             png_size_t size)
+  {
+    ChunkedBuffer* buffer = reinterpret_cast<ChunkedBuffer*>(png_get_io_ptr(png_ptr));
+    buffer->AddChunk(reinterpret_cast<const char*>(data), size);
+  }
+
+
+
+#if ORTHANC_SANDBOXED == 0
+  void PngWriter::WriteToMemoryInternal(std::string& png,
+                                        unsigned int width,
+                                        unsigned int height,
+                                        unsigned int pitch,
+                                        PixelFormat format,
+                                        const void* buffer)
+  {
+    ChunkedBuffer chunks;
+
+    Prepare(width, height, pitch, format, buffer);
+
+    if (setjmp(png_jmpbuf(pimpl_->png_)))
+    {
+      // Error during writing PNG
+      throw OrthancException(ErrorCode_InternalError);      
+    }
+
+    png_set_write_fn(pimpl_->png_, &chunks, MemoryCallback, NULL);
+
+    Compress(width, height, pitch, format);
+
+    chunks.Flatten(png);
+  }
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Images/PngWriter.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,89 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 !defined(ORTHANC_ENABLE_PNG)
+#  error The macro ORTHANC_ENABLE_PNG must be defined
+#endif
+
+#if ORTHANC_ENABLE_PNG != 1
+#  error PNG support must be enabled to include this file
+#endif
+
+#include "IImageWriter.h"
+
+#include <boost/shared_ptr.hpp>
+
+namespace Orthanc
+{
+  class PngWriter : public IImageWriter
+  {
+  protected:
+    virtual void WriteToFileInternal(const std::string& filename,
+                                     unsigned int width,
+                                     unsigned int height,
+                                     unsigned int pitch,
+                                     PixelFormat format,
+                                     const void* buffer);
+
+#if ORTHANC_SANDBOXED == 0
+    virtual void WriteToMemoryInternal(std::string& png,
+                                       unsigned int width,
+                                       unsigned int height,
+                                       unsigned int pitch,
+                                       PixelFormat format,
+                                       const void* buffer);
+#endif
+
+  private:
+    struct PImpl;
+    boost::shared_ptr<PImpl> pimpl_;
+
+    void Compress(unsigned int width,
+                  unsigned int height,
+                  unsigned int pitch,
+                  PixelFormat format);
+
+    void Prepare(unsigned int width,
+                 unsigned int height,
+                 unsigned int pitch,
+                 PixelFormat format,
+                 const void* buffer);
+
+  public:
+    PngWriter();
+
+    ~PngWriter();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Logging.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,603 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "Logging.h"
+
+#if ORTHANC_ENABLE_LOGGING != 1
+
+namespace Orthanc
+{
+  namespace Logging
+  {
+    void Initialize()
+    {
+    }
+
+    void Finalize()
+    {
+    }
+
+    void Reset()
+    {
+    }
+
+    void Flush()
+    {
+    }
+
+    void EnableInfoLevel(bool enabled)
+    {
+    }
+
+    void EnableTraceLevel(bool enabled)
+    {
+    }
+
+    void SetTargetFile(const std::string& path)
+    {
+    }
+
+    void SetTargetFolder(const std::string& path)
+    {
+    }
+  }
+}
+
+
+#elif ORTHANC_ENABLE_LOGGING_PLUGIN == 1
+
+/*********************************************************
+ * Logger compatible with the Orthanc plugin SDK
+ *********************************************************/
+
+#include <boost/lexical_cast.hpp>
+
+namespace Orthanc
+{
+  namespace Logging
+  {
+    static OrthancPluginContext* context_ = NULL;
+
+    void Initialize(OrthancPluginContext* context)
+    {
+      context_ = context;
+    }
+
+    InternalLogger::InternalLogger(Level level,
+                                   const char* file  /* ignored */,
+                                   int line  /* ignored */) :
+      level_(level)
+    {
+    }
+
+    InternalLogger::~InternalLogger()
+    {
+      if (context_ != NULL)
+      {
+        switch (level_)
+        {
+          case ERROR:
+            OrthancPluginLogError(context_, message_.c_str());
+            break;
+
+          case WARNING:
+            OrthancPluginLogWarning(context_, message_.c_str());
+            break;
+
+          case INFO:
+            OrthancPluginLogInfo(context_, message_.c_str());
+            break;
+
+          case TRACE:
+            // Not used by plugins
+            break;
+
+          default:
+          {
+            std::string s = ("Unknown log level (" + boost::lexical_cast<std::string>(level_) +
+                             ") for message: " + message_);
+            OrthancPluginLogError(context_, s.c_str());
+            break;
+          }
+        }
+      }
+    }
+  }
+}
+
+
+#elif ORTHANC_ENABLE_LOGGING_STDIO == 1
+
+/*********************************************************
+ * Logger compatible with <stdio.h>
+ *********************************************************/
+
+#include <stdio.h>
+#include <boost/lexical_cast.hpp>
+
+namespace Orthanc
+{
+  namespace Logging
+  {
+    static bool globalVerbose_ = false;
+    static bool globalTrace_ = false;
+    
+    InternalLogger::InternalLogger(Level level,
+                                   const char* file  /* ignored */,
+                                   int line  /* ignored */) :
+      level_(level)
+    {
+    }
+
+    InternalLogger::~InternalLogger()
+    {
+      switch (level_)
+      {
+        case ERROR:
+          fprintf(stderr, "E: %s\n", message_.c_str());
+          break;
+
+        case WARNING:
+          fprintf(stdout, "W: %s\n", message_.c_str());
+          break;
+
+        case INFO:
+          if (globalVerbose_)
+          {
+            fprintf(stdout, "I: %s\n", message_.c_str());
+          }
+          break;
+
+        case TRACE:
+          if (globalTrace_)
+          {
+            fprintf(stdout, "T: %s\n", message_.c_str());
+          }
+          break;
+
+        default:
+          fprintf(stderr, "Unknown log level (%d) for message: %s\n", level_, message_.c_str());
+      }
+    }
+
+    void EnableInfoLevel(bool enabled)
+    {
+      globalVerbose_ = enabled;
+    }
+
+    void EnableTraceLevel(bool enabled)
+    {
+      globalTrace_ = enabled;
+    }
+  }
+}
+
+
+#else  /* ORTHANC_ENABLE_LOGGING_PLUGIN == 0 && 
+          ORTHANC_ENABLE_LOGGING_STDIO == 0 && 
+          ORTHANC_ENABLE_LOGGING == 1 */
+
+/*********************************************************
+ * Internal logger of Orthanc, that mimics some
+ * behavior from Google Log.
+ *********************************************************/
+
+#include "OrthancException.h"
+#include "Enumerations.h"
+#include "Toolbox.h"
+
+#if ORTHANC_SANDBOXED == 1
+#  include <stdio.h>
+#else
+#  include "SystemToolbox.h"
+#endif
+
+#include <fstream>
+#include <boost/filesystem.hpp>
+#include <boost/thread.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+
+namespace
+{
+  struct LoggingContext
+  {
+    bool infoEnabled_;
+    bool traceEnabled_;
+    std::string  targetFile_;
+    std::string  targetFolder_;
+
+    std::ostream* error_;
+    std::ostream* warning_;
+    std::ostream* info_;
+
+    std::auto_ptr<std::ofstream> file_;
+
+    LoggingContext() : 
+      infoEnabled_(false),
+      traceEnabled_(false),
+      error_(&std::cerr),
+      warning_(&std::cerr),
+      info_(&std::cerr)
+    {
+    }
+  };
+}
+
+
+
+static std::auto_ptr<LoggingContext> loggingContext_;
+static boost::mutex  loggingMutex_;
+
+
+
+namespace Orthanc
+{
+  namespace Logging
+  {
+    static void GetLogPath(boost::filesystem::path& log,
+                           boost::filesystem::path& link,
+                           const std::string& suffix,
+                           const std::string& directory)
+    {
+      /**
+         From Google Log documentation:
+
+         Unless otherwise specified, logs will be written to the filename
+         "<program name>.<hostname>.<user name>.log<suffix>.",
+         followed by the date, time, and pid (you can't prevent the date,
+         time, and pid from being in the filename).
+
+         In this implementation : "hostname" and "username" are not used
+      **/
+
+      boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
+      boost::filesystem::path root(directory);
+      boost::filesystem::path exe(SystemToolbox::GetPathToExecutable());
+      
+      if (!boost::filesystem::exists(root) ||
+          !boost::filesystem::is_directory(root))
+      {
+        throw OrthancException(ErrorCode_CannotWriteFile);
+      }
+
+      char date[64];
+      sprintf(date, "%04d%02d%02d-%02d%02d%02d.%d",
+              static_cast<int>(now.date().year()),
+              now.date().month().as_number(),
+              now.date().day().as_number(),
+              static_cast<int>(now.time_of_day().hours()),
+              static_cast<int>(now.time_of_day().minutes()),
+              static_cast<int>(now.time_of_day().seconds()),
+              SystemToolbox::GetProcessId());
+
+      std::string programName = exe.filename().replace_extension("").string();
+
+      log = (root / (programName + ".log" + suffix + "." + std::string(date)));
+      link = (root / (programName + ".log" + suffix));
+    }
+
+
+    static void PrepareLogFolder(std::auto_ptr<std::ofstream>& file,
+                                 const std::string& suffix,
+                                 const std::string& directory)
+    {
+      boost::filesystem::path log, link;
+      GetLogPath(log, link, suffix, directory);
+
+#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)))
+      boost::filesystem::remove(link);
+      boost::filesystem::create_symlink(log.filename(), link);
+#endif
+
+      file.reset(new std::ofstream(log.string().c_str()));
+    }
+
+
+    void Initialize()
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+      loggingContext_.reset(new LoggingContext);
+    }
+
+    void Finalize()
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+      loggingContext_.reset(NULL);
+    }
+
+    void Reset()
+    {
+      // Recover the old logging context
+      std::auto_ptr<LoggingContext> old;
+
+      {
+        boost::mutex::scoped_lock lock(loggingMutex_);
+        if (loggingContext_.get() == NULL)
+        {
+          return;
+        }
+        else
+        {
+          old = loggingContext_;
+
+          // Create a new logging context, 
+          loggingContext_.reset(new LoggingContext);
+        }
+      }
+      
+      EnableInfoLevel(old->infoEnabled_);
+      EnableTraceLevel(old->traceEnabled_);
+
+      if (!old->targetFolder_.empty())
+      {
+        SetTargetFolder(old->targetFolder_);
+      }
+      else if (!old->targetFile_.empty())
+      {
+        SetTargetFile(old->targetFile_);
+      }
+    }
+
+    void EnableInfoLevel(bool enabled)
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+      assert(loggingContext_.get() != NULL);
+
+      loggingContext_->infoEnabled_ = enabled;
+      
+      if (!enabled)
+      {
+        // Also disable the "TRACE" level when info-level debugging is disabled
+        loggingContext_->traceEnabled_ = false;
+      }
+    }
+
+    void EnableTraceLevel(bool enabled)
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+      assert(loggingContext_.get() != NULL);
+
+      loggingContext_->traceEnabled_ = enabled;
+      
+      if (enabled)
+      {
+        // Also enable the "INFO" level when trace-level debugging is enabled
+        loggingContext_->infoEnabled_ = true;
+      }
+    }
+
+
+    static void CheckFile(std::auto_ptr<std::ofstream>& f)
+    {
+      if (loggingContext_->file_.get() == NULL ||
+          !loggingContext_->file_->is_open())
+      {
+        throw OrthancException(ErrorCode_CannotWriteFile);
+      }
+    }
+
+    void SetTargetFolder(const std::string& path)
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+      assert(loggingContext_.get() != NULL);
+
+      PrepareLogFolder(loggingContext_->file_, "" /* no suffix */, path);
+      CheckFile(loggingContext_->file_);
+
+      loggingContext_->targetFile_.clear();
+      loggingContext_->targetFolder_ = path;
+      loggingContext_->warning_ = loggingContext_->file_.get();
+      loggingContext_->error_ = loggingContext_->file_.get();
+      loggingContext_->info_ = loggingContext_->file_.get();
+    }
+
+
+    void SetTargetFile(const std::string& path)
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+      assert(loggingContext_.get() != NULL);
+
+      loggingContext_->file_.reset(new std::ofstream(path.c_str(), std::fstream::app));
+      CheckFile(loggingContext_->file_);
+
+      loggingContext_->targetFile_ = path;
+      loggingContext_->targetFolder_.clear();
+      loggingContext_->warning_ = loggingContext_->file_.get();
+      loggingContext_->error_ = loggingContext_->file_.get();
+      loggingContext_->info_ = loggingContext_->file_.get();
+    }
+
+
+    InternalLogger::InternalLogger(const char* level,
+                                   const char* file,
+                                   int line) : 
+      lock_(loggingMutex_), 
+      stream_(&null_)  // By default, logging to "/dev/null" is simulated
+    {
+      if (loggingContext_.get() == NULL)
+      {
+        fprintf(stderr, "ERROR: Trying to log a message after the finalization of the logging engine\n");
+        return;
+      }
+
+      try
+      {
+        LogLevel l = StringToLogLevel(level);
+      
+        if ((l == LogLevel_Info  && !loggingContext_->infoEnabled_) ||
+            (l == LogLevel_Trace && !loggingContext_->traceEnabled_))
+        {
+          // This logging level is disabled, directly exit and unlock
+          // the mutex to speed-up things. The stream is set to "/dev/null"
+          lock_.unlock();
+          return;
+        }
+
+        // Compute the header of the line, temporary release the lock as
+        // this is a time-consuming operation
+        lock_.unlock();
+        std::string header;
+
+        {
+          boost::filesystem::path path(file);
+          boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time();
+          boost::posix_time::time_duration duration = now.time_of_day();
+
+          /**
+             From Google Log documentation:
+
+             "Log lines have this form:
+
+             Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg...
+
+             where the fields are defined as follows:
+
+             L                A single character, representing the log level (eg 'I' for INFO)
+             mm               The month (zero padded; ie May is '05')
+             dd               The day (zero padded)
+             hh:mm:ss.uuuuuu  Time in hours, minutes and fractional seconds
+             threadid         The space-padded thread ID as returned by GetTID() (this matches the PID on Linux)
+             file             The file name
+             line             The line number
+             msg              The user-supplied message"
+
+             In this implementation, "threadid" is not printed.
+          **/
+
+          char date[32];
+          sprintf(date, "%c%02d%02d %02d:%02d:%02d.%06d ",
+                  level[0],
+                  now.date().month().as_number(),
+                  now.date().day().as_number(),
+                  static_cast<int>(duration.hours()),
+                  static_cast<int>(duration.minutes()),
+                  static_cast<int>(duration.seconds()),
+                  static_cast<int>(duration.fractional_seconds()));
+
+          header = std::string(date) + path.filename().string() + ":" + boost::lexical_cast<std::string>(line) + "] ";
+        }
+
+
+        // The header is computed, we now re-lock the mutex to access
+        // the stream objects. Pay attention that "loggingContext_",
+        // "infoEnabled_" or "traceEnabled_" might have changed while
+        // the mutex was unlocked.
+        lock_.lock();
+
+        if (loggingContext_.get() == NULL)
+        {
+          fprintf(stderr, "ERROR: Trying to log a message after the finalization of the logging engine\n");
+          return;
+        }
+
+        switch (l)
+        {
+          case LogLevel_Error:
+            stream_ = loggingContext_->error_;
+            break;
+
+          case LogLevel_Warning:
+            stream_ = loggingContext_->warning_;
+            break;
+
+          case LogLevel_Info:
+            if (loggingContext_->infoEnabled_)
+            {
+              stream_ = loggingContext_->info_;
+            }
+
+            break;
+
+          case LogLevel_Trace:
+            if (loggingContext_->traceEnabled_)
+            {
+              stream_ = loggingContext_->info_;
+            }
+
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_InternalError);
+        }
+
+        if (stream_ == &null_)
+        {
+          // The logging is disabled for this level. The stream is the
+          // "null_" member of this object, so we can release the global
+          // mutex.
+          lock_.unlock();
+        }
+
+        (*stream_) << header;
+      }
+      catch (...)
+      { 
+        // Something is going really wrong, probably running out of
+        // memory. Fallback to a degraded mode.
+        stream_ = loggingContext_->error_;
+        (*stream_) << "E???? ??:??:??.?????? ] ";
+      }
+    }
+
+
+    InternalLogger::~InternalLogger()
+    {
+      if (stream_ != &null_)
+      {
+#if defined(_WIN32)
+        *stream_ << "\r\n";
+#else
+        *stream_ << "\n";
+#endif
+
+        stream_->flush();
+      }
+    }
+      
+
+    void Flush()
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+
+      if (loggingContext_.get() != NULL &&
+          loggingContext_->file_.get() != NULL)
+      {
+        loggingContext_->file_->flush();
+      }
+    }
+  }
+}
+
+#endif   // ORTHANC_ENABLE_LOGGING
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Logging.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,203 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 <iostream>
+
+#if !defined(ORTHANC_ENABLE_LOGGING)
+#  error The macro ORTHANC_ENABLE_LOGGING must be defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_LOGGING_PLUGIN)
+#  if ORTHANC_ENABLE_LOGGING == 1
+#    error The macro ORTHANC_ENABLE_LOGGING_PLUGIN must be defined
+#  else
+#    define ORTHANC_ENABLE_LOGGING_PLUGIN 0
+#  endif
+#endif
+
+#if !defined(ORTHANC_ENABLE_LOGGING_STDIO)
+#  if ORTHANC_ENABLE_LOGGING == 1
+#    error The macro ORTHANC_ENABLE_LOGGING_STDIO must be defined
+#  else
+#    define ORTHANC_ENABLE_LOGGING_STDIO 0
+#  endif
+#endif
+
+#if ORTHANC_ENABLE_LOGGING_PLUGIN == 1
+#  include <orthanc/OrthancCPlugin.h>
+#endif
+
+namespace Orthanc
+{
+  namespace Logging
+  {
+#if ORTHANC_ENABLE_LOGGING_PLUGIN == 1
+    void Initialize(OrthancPluginContext* context);
+#else
+    void Initialize();
+#endif
+
+    void Finalize();
+
+    void Reset();
+
+    void Flush();
+
+    void EnableInfoLevel(bool enabled);
+
+    void EnableTraceLevel(bool enabled);
+
+    void SetTargetFile(const std::string& path);
+
+    void SetTargetFolder(const std::string& path);
+
+    struct NullStream : public std::ostream 
+    {
+      NullStream() : 
+        std::ios(0), 
+        std::ostream(0)
+      {
+      }
+      
+      std::ostream& operator<< (const std::string& message)
+      {
+        return *this;
+      }
+
+      // This overload fixes build problems with Visual Studio 2015
+      std::ostream& operator<< (const char* message)
+      {
+        return *this;
+      }
+    };
+  }
+}
+
+
+#if ORTHANC_ENABLE_LOGGING != 1
+
+#  define LOG(level)   ::Orthanc::Logging::NullStream()
+#  define VLOG(level)  ::Orthanc::Logging::NullStream()
+
+
+#elif (ORTHANC_ENABLE_LOGGING_PLUGIN == 1 ||    \
+       ORTHANC_ENABLE_LOGGING_STDIO == 1)
+
+#  include <boost/noncopyable.hpp>
+#  include <boost/lexical_cast.hpp>
+#  define LOG(level)  ::Orthanc::Logging::InternalLogger \
+  (::Orthanc::Logging::level, __FILE__, __LINE__)
+#  define VLOG(level) ::Orthanc::Logging::InternalLogger \
+  (::Orthanc::Logging::TRACE, __FILE__, __LINE__)
+
+namespace Orthanc
+{
+  namespace Logging
+  {
+    enum Level
+    {
+      ERROR,
+      WARNING,
+      INFO,
+      TRACE
+    };
+    
+    class InternalLogger : public boost::noncopyable
+    {
+    private:
+      Level       level_;
+      std::string message_;
+
+    public:
+      InternalLogger(Level level,
+                     const char* file,
+                     int line);
+
+      ~InternalLogger();
+      
+      template <typename T>
+      InternalLogger& operator<< (T message)
+      {
+        message_ += boost::lexical_cast<std::string>(message);
+        return *this;
+      }
+    };
+  }
+}
+
+
+
+
+#else  /* ORTHANC_ENABLE_LOGGING_PLUGIN == 0 && 
+          ORTHANC_ENABLE_LOGGING_STDIO == 0 && 
+          ORTHANC_ENABLE_LOGGING == 1 */
+
+#  include <boost/thread/mutex.hpp>
+#  define LOG(level)  ::Orthanc::Logging::InternalLogger(#level,  __FILE__, __LINE__)
+#  define VLOG(level) ::Orthanc::Logging::InternalLogger("TRACE", __FILE__, __LINE__)
+
+namespace Orthanc
+{
+  namespace Logging
+  {
+    class InternalLogger
+    {
+    private:
+      boost::mutex::scoped_lock lock_;
+      NullStream                null_;
+      std::ostream*             stream_;
+
+    public:
+      InternalLogger(const char* level,
+                     const char* file,
+                     int line);
+
+      ~InternalLogger();
+      
+      std::ostream& operator<< (const std::string& message)
+      {
+        return (*stream_) << message;
+      }
+
+      // This overload fixes build problems with Visual Studio 2015
+      std::ostream& operator<< (const char* message)
+      {
+        return (*stream_) << message;
+      }
+    };
+  }
+}
+
+#endif  // ORTHANC_ENABLE_LOGGING
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/MultiThreading/BagOfTasks.h	Fri Mar 23 11:04:03 2018 +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-2018 Osimis S.A., 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 "../ICommand.h"
+
+#include <list>
+#include <cstddef>
+
+namespace Orthanc
+{
+  class BagOfTasks : public boost::noncopyable
+  {
+  private:
+    typedef std::list<ICommand*>  Tasks;
+
+    Tasks  tasks_;
+
+  public:
+    ~BagOfTasks()
+    {
+      for (Tasks::iterator it = tasks_.begin(); it != tasks_.end(); ++it)
+      {
+        delete *it;
+      }
+    }
+
+    ICommand* Pop()
+    {
+      ICommand* task = tasks_.front();
+      tasks_.pop_front();
+      return task;
+    }
+
+    void Push(ICommand* task)   // Takes ownership
+    {
+      if (task != NULL)
+      {
+        tasks_.push_back(task);
+      }
+    }
+
+    size_t GetSize() const
+    {
+      return tasks_.size();
+    }
+
+    bool IsEmpty() const
+    {
+      return tasks_.empty();
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/MultiThreading/BagOfTasksProcessor.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,277 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "BagOfTasksProcessor.h"
+
+#include "../Logging.h"
+#include "../OrthancException.h"
+
+#include <stdio.h>
+
+namespace Orthanc
+{
+  class BagOfTasksProcessor::Task : public IDynamicObject
+  {
+  private:
+    uint64_t                 bag_;
+    std::auto_ptr<ICommand>  command_;
+
+  public:
+    Task(uint64_t  bag,
+         ICommand* command) :
+      bag_(bag),
+      command_(command)
+    {
+    }
+
+    bool Execute()
+    {
+      try
+      {
+        return command_->Execute();
+      }
+      catch (OrthancException& e)
+      {
+        LOG(ERROR) << "Exception while processing a bag of tasks: " << e.What();
+        return false;
+      }
+      catch (std::runtime_error& e)
+      {
+        LOG(ERROR) << "Runtime exception while processing a bag of tasks: " << e.what();
+        return false;
+      }
+      catch (...)
+      {
+        LOG(ERROR) << "Native exception while processing a bag of tasks";
+        return false;
+      }
+    }
+
+    uint64_t GetBag()
+    {
+      return bag_;
+    }
+  };
+
+
+  void BagOfTasksProcessor::SignalProgress(Task& task,
+                                           Bag& bag)
+  {
+    assert(bag.done_ < bag.size_);
+
+    bag.done_ += 1;
+
+    if (bag.done_ == bag.size_)
+    {
+      exitStatus_[task.GetBag()] = (bag.status_ == BagStatus_Running);
+      bagFinished_.notify_all();
+    }
+  }
+
+  void BagOfTasksProcessor::Worker(BagOfTasksProcessor* that)
+  {
+    while (that->continue_)
+    {
+      std::auto_ptr<IDynamicObject> obj(that->queue_.Dequeue(100));
+      if (obj.get() != NULL)
+      {
+        Task& task = *dynamic_cast<Task*>(obj.get());
+
+        {
+          boost::mutex::scoped_lock lock(that->mutex_);
+
+          Bags::iterator bag = that->bags_.find(task.GetBag());
+          assert(bag != that->bags_.end());
+          assert(bag->second.done_ < bag->second.size_);
+
+          if (bag->second.status_ != BagStatus_Running)
+          {
+            // Do not execute this task, as its parent bag of tasks
+            // has failed or is tagged as canceled
+            that->SignalProgress(task, bag->second);
+            continue;
+          }
+        }
+
+        bool success = task.Execute();
+
+        {
+          boost::mutex::scoped_lock lock(that->mutex_);
+
+          Bags::iterator bag = that->bags_.find(task.GetBag());
+          assert(bag != that->bags_.end());
+
+          if (!success)
+          {
+            bag->second.status_ = BagStatus_Failed;
+          }
+
+          that->SignalProgress(task, bag->second);
+        }
+      }
+    }
+  }
+
+
+  void BagOfTasksProcessor::Cancel(int64_t bag)
+  {
+    boost::mutex::scoped_lock  lock(mutex_);
+
+    Bags::iterator it = bags_.find(bag);
+    if (it != bags_.end())
+    {
+      it->second.status_ = BagStatus_Canceled;
+    }
+  }
+
+
+  bool BagOfTasksProcessor::Join(int64_t bag)
+  {
+    boost::mutex::scoped_lock  lock(mutex_);
+
+    while (continue_)
+    {
+      ExitStatus::iterator it = exitStatus_.find(bag);
+      if (it == exitStatus_.end())  // The bag is still running
+      {
+        bagFinished_.wait(lock);
+      }
+      else
+      {
+        bool status = it->second;
+        exitStatus_.erase(it);
+        return status;
+      }
+    }
+
+    return false;   // The processor is stopping
+  }
+
+
+  float BagOfTasksProcessor::GetProgress(int64_t bag)
+  {
+    boost::mutex::scoped_lock  lock(mutex_);
+
+    Bags::const_iterator it = bags_.find(bag);
+    if (it == bags_.end())
+    {
+      // The bag of tasks has finished
+      return 1.0f;
+    }
+    else
+    {
+      return (static_cast<float>(it->second.done_) / 
+              static_cast<float>(it->second.size_));
+    }
+  }
+
+
+  bool BagOfTasksProcessor::Handle::Join()
+  {
+    if (hasJoined_)
+    {
+      return status_;
+    }
+    else
+    {
+      status_ = that_.Join(bag_);
+      hasJoined_ = true;
+      return status_;
+    }
+  }
+
+
+  BagOfTasksProcessor::BagOfTasksProcessor(size_t countThreads) : 
+    countBags_(0),
+    continue_(true)
+  {
+    if (countThreads == 0)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    threads_.resize(countThreads);
+
+    for (size_t i = 0; i < threads_.size(); i++)
+    {
+      threads_[i] = new boost::thread(Worker, this);
+    }
+  }
+
+
+  BagOfTasksProcessor::~BagOfTasksProcessor()
+  {
+    continue_ = false;
+
+    bagFinished_.notify_all();   // Wakes up all the pending "Join()"
+
+    for (size_t i = 0; i < threads_.size(); i++)
+    {
+      if (threads_[i])
+      {
+        if (threads_[i]->joinable())
+        {
+          threads_[i]->join();
+        }
+
+        delete threads_[i];
+        threads_[i] = NULL;
+      }
+    }
+  }
+
+
+  BagOfTasksProcessor::Handle* BagOfTasksProcessor::Submit(BagOfTasks& tasks)
+  {
+    if (tasks.GetSize() == 0)
+    {
+      return new Handle(*this, 0, true);
+    }
+
+    boost::mutex::scoped_lock lock(mutex_);
+
+    uint64_t id = countBags_;
+    countBags_ += 1;
+
+    Bag bag(tasks.GetSize());
+    bags_[id] = bag;
+
+    while (!tasks.IsEmpty())
+    {
+      queue_.Enqueue(new Task(id, tasks.Pop()));
+    }
+
+    return new Handle(*this, id, false);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/MultiThreading/BagOfTasksProcessor.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,150 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "BagOfTasks.h"
+#include "SharedMessageQueue.h"
+
+#include <stdint.h>
+#include <map>
+
+namespace Orthanc
+{
+  class BagOfTasksProcessor : public boost::noncopyable
+  {
+  private:
+    enum BagStatus
+    {
+      BagStatus_Running,
+      BagStatus_Canceled,
+      BagStatus_Failed
+    };
+
+
+    struct Bag
+    {
+      size_t    size_;
+      size_t    done_;
+      BagStatus status_;
+
+      Bag() :
+        size_(0),
+        done_(0),
+        status_(BagStatus_Failed)
+      {
+      }
+
+      explicit Bag(size_t size) : 
+        size_(size),
+        done_(0),
+        status_(BagStatus_Running)
+      {
+      }
+    };
+
+    class Task;
+
+
+    typedef std::map<uint64_t, Bag>   Bags;
+    typedef std::map<uint64_t, bool>  ExitStatus;
+
+    SharedMessageQueue  queue_;
+
+    boost::mutex  mutex_;
+    uint64_t  countBags_;
+    Bags bags_;
+    std::vector<boost::thread*>   threads_;
+    ExitStatus  exitStatus_;
+    bool continue_;
+
+    boost::condition_variable  bagFinished_;
+
+    static void Worker(BagOfTasksProcessor* that);
+
+    void Cancel(int64_t bag);
+
+    bool Join(int64_t bag);
+
+    float GetProgress(int64_t bag);
+
+    void SignalProgress(Task& task,
+                        Bag& bag);
+
+  public:
+    class Handle : public boost::noncopyable
+    {
+      friend class BagOfTasksProcessor;
+
+    private:
+      BagOfTasksProcessor&  that_;
+      uint64_t              bag_;
+      bool                  hasJoined_;
+      bool                  status_;
+ 
+      Handle(BagOfTasksProcessor&  that,
+             uint64_t bag,
+             bool empty) : 
+        that_(that),
+        bag_(bag),
+        hasJoined_(empty)
+      {
+      }
+
+    public:
+      ~Handle()
+      {
+        Join();
+      }
+
+      void Cancel()
+      {
+        that_.Cancel(bag_);
+      }
+
+      bool Join();
+
+      float GetProgress()
+      {
+        return that_.GetProgress(bag_);
+      }
+    };
+  
+
+    explicit BagOfTasksProcessor(size_t countThreads);
+
+    ~BagOfTasksProcessor();
+
+    Handle* Submit(BagOfTasks& tasks);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/MultiThreading/ILockable.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,54 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 ILockable : public boost::noncopyable
+  {
+    friend class Locker;
+
+  protected:
+    virtual void Lock() = 0;
+
+    virtual void Unlock() = 0;
+
+  public:
+    virtual ~ILockable()
+    {
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/MultiThreading/IRunnableBySteps.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,56 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "../IDynamicObject.h"
+
+namespace Orthanc
+{
+  class IRunnableBySteps : public IDynamicObject
+  {
+  public:
+    virtual ~IRunnableBySteps()
+    {
+    }
+
+    // Must return "true" if the runnable wishes to continue. Must
+    // return "false" if the runnable has not finished its job.
+    virtual bool Step() = 0;
+
+    static void RunUntilDone(IRunnableBySteps& runnable)
+    {
+      while (runnable.Step());
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/MultiThreading/Mutex.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,122 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "Mutex.h"
+
+#include "../OrthancException.h"
+
+#if defined(_WIN32)
+#include <windows.h>
+#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__)
+#include <pthread.h>
+#else
+#error Support your platform here
+#endif
+
+namespace Orthanc
+{
+#if defined (_WIN32)
+
+  struct Mutex::PImpl
+  {
+    CRITICAL_SECTION criticalSection_;
+  };
+
+  Mutex::Mutex()
+  {
+    pimpl_ = new PImpl;
+    ::InitializeCriticalSection(&pimpl_->criticalSection_);
+  }
+
+  Mutex::~Mutex()
+  {
+    ::DeleteCriticalSection(&pimpl_->criticalSection_);
+    delete pimpl_;
+  }
+
+  void Mutex::Lock()
+  {
+    ::EnterCriticalSection(&pimpl_->criticalSection_);
+  }
+
+  void Mutex::Unlock()
+  {
+    ::LeaveCriticalSection(&pimpl_->criticalSection_);
+  }
+
+
+#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__)
+
+  struct Mutex::PImpl
+  {
+    pthread_mutex_t mutex_;
+  };
+
+  Mutex::Mutex()
+  {
+    pimpl_ = new PImpl;
+
+    if (pthread_mutex_init(&pimpl_->mutex_, NULL) != 0)
+    {
+      delete pimpl_;
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+  Mutex::~Mutex()
+  {
+    pthread_mutex_destroy(&pimpl_->mutex_);
+    delete pimpl_;
+  }
+
+  void Mutex::Lock()
+  {
+    if (pthread_mutex_lock(&pimpl_->mutex_) != 0)
+    {
+      throw OrthancException(ErrorCode_InternalError);    
+    }
+  }
+
+  void Mutex::Unlock()
+  {
+    if (pthread_mutex_unlock(&pimpl_->mutex_) != 0)
+    {
+      throw OrthancException(ErrorCode_InternalError);    
+    }
+  }
+
+#else
+#error Support your plateform here
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/MultiThreading/Mutex.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,57 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "ILockable.h"
+
+namespace Orthanc
+{
+  class Mutex : public ILockable
+  {
+  private:
+    struct PImpl;
+
+    PImpl *pimpl_;
+
+  protected:
+    virtual void Lock();
+
+    virtual void Unlock();
+    
+  public:
+    Mutex();
+
+    ~Mutex();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/MultiThreading/ReaderWriterLock.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,126 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "ReaderWriterLock.h"
+
+#include <boost/thread/shared_mutex.hpp>
+
+namespace Orthanc
+{
+  namespace
+  {
+    // Anonymous namespace to avoid clashes between compilation
+    // modules.
+
+    class ReaderLockable : public ILockable
+    {
+    private:
+      boost::shared_mutex& lock_;
+
+    protected:
+      virtual void Lock()
+      {
+        lock_.lock_shared();
+      }
+
+      virtual void Unlock()
+      {
+        lock_.unlock_shared();        
+      }
+
+    public:
+      explicit ReaderLockable(boost::shared_mutex& lock) : lock_(lock)
+      {
+      }
+    };
+
+
+    class WriterLockable : public ILockable
+    {
+    private:
+      boost::shared_mutex& lock_;
+
+    protected:
+      virtual void Lock()
+      {
+        lock_.lock();
+      }
+
+      virtual void Unlock()
+      {
+        lock_.unlock();        
+      }
+
+    public:
+      explicit WriterLockable(boost::shared_mutex& lock) : lock_(lock)
+      {
+      }
+    };
+  }
+
+  struct ReaderWriterLock::PImpl
+  {
+    boost::shared_mutex lock_;
+    ReaderLockable reader_;
+    WriterLockable writer_;
+
+    PImpl() : reader_(lock_), writer_(lock_)
+    {
+    }
+  };
+
+
+  ReaderWriterLock::ReaderWriterLock()
+  {
+    pimpl_ = new PImpl;
+  }
+
+
+  ReaderWriterLock::~ReaderWriterLock()
+  {
+    delete pimpl_;
+  }
+
+
+  ILockable&  ReaderWriterLock::ForReader()
+  {
+    return pimpl_->reader_;
+  }
+
+
+  ILockable&  ReaderWriterLock::ForWriter()
+  {
+    return pimpl_->writer_;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/MultiThreading/ReaderWriterLock.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,58 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "ILockable.h"
+
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class ReaderWriterLock : public boost::noncopyable
+  {
+  private:
+    struct PImpl;
+
+    PImpl *pimpl_;
+
+  public:
+    ReaderWriterLock();
+
+    virtual ~ReaderWriterLock();
+
+    ILockable& ForReader();
+
+    ILockable& ForWriter();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/MultiThreading/RunnableWorkersPool.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,170 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "RunnableWorkersPool.h"
+
+#include "SharedMessageQueue.h"
+#include "../OrthancException.h"
+#include "../Logging.h"
+
+namespace Orthanc
+{
+  struct RunnableWorkersPool::PImpl
+  {
+    class Worker
+    {
+    private:
+      const bool&           continue_;
+      SharedMessageQueue&   queue_;
+      boost::thread         thread_;
+ 
+      static void WorkerThread(Worker* that)
+      {
+        while (that->continue_)
+        {
+          try
+          {
+            std::auto_ptr<IDynamicObject>  obj(that->queue_.Dequeue(100));
+            if (obj.get() != NULL)
+            {
+              IRunnableBySteps& runnable = *dynamic_cast<IRunnableBySteps*>(obj.get());
+              
+              bool wishToContinue = runnable.Step();
+              
+              if (wishToContinue)
+              {
+                // The runnable wishes to continue, reinsert it at the beginning of the queue
+                that->queue_.Enqueue(obj.release());
+              }
+            }
+          }
+          catch (OrthancException& e)
+          {
+            LOG(ERROR) << "Exception while handling some runnable object: " << e.What();
+          }
+          catch (std::bad_alloc&)
+          {
+            LOG(ERROR) << "Not enough memory to handle some runnable object";
+          }
+          catch (std::exception& e)
+          {
+            LOG(ERROR) << "std::exception while handling some runnable object: " << e.what();
+          }
+          catch (...)
+          {
+            LOG(ERROR) << "Native exception while handling some runnable object";
+          }
+        }
+      }
+
+    public:
+      Worker(const bool& globalContinue,
+             SharedMessageQueue& queue) : 
+        continue_(globalContinue),
+        queue_(queue)
+      {
+        thread_ = boost::thread(WorkerThread, this);
+      }
+
+      void Join()
+      {
+        if (thread_.joinable())
+        {
+          thread_.join();
+        }
+      }
+    };
+
+
+    bool                  continue_;
+    std::vector<Worker*>  workers_;
+    SharedMessageQueue    queue_;
+  };
+
+
+
+  RunnableWorkersPool::RunnableWorkersPool(size_t countWorkers) : pimpl_(new PImpl)
+  {
+    pimpl_->continue_ = true;
+
+    if (countWorkers == 0)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    pimpl_->workers_.resize(countWorkers);
+
+    for (size_t i = 0; i < countWorkers; i++)
+    {
+      pimpl_->workers_[i] = new PImpl::Worker(pimpl_->continue_, pimpl_->queue_);
+    }
+  }
+
+
+  void RunnableWorkersPool::Stop()
+  {
+    if (pimpl_->continue_)
+    {
+      pimpl_->continue_ = false;
+
+      for (size_t i = 0; i < pimpl_->workers_.size(); i++)
+      {
+        PImpl::Worker* worker = pimpl_->workers_[i];
+
+        if (worker != NULL)
+        {
+          worker->Join();
+          delete worker;
+        }
+      }
+    }
+  }
+
+
+  RunnableWorkersPool::~RunnableWorkersPool()
+  {
+    Stop();
+  }
+
+
+  void RunnableWorkersPool::Add(IRunnableBySteps* runnable)
+  {
+    if (!pimpl_->continue_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    pimpl_->queue_.Enqueue(runnable);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/MultiThreading/RunnableWorkersPool.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,57 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "IRunnableBySteps.h"
+
+#include <boost/shared_ptr.hpp>
+
+namespace Orthanc
+{
+  class RunnableWorkersPool : public boost::noncopyable
+  {
+  private:
+    struct PImpl;
+    boost::shared_ptr<PImpl> pimpl_;
+
+    void Stop();
+
+  public:
+    explicit RunnableWorkersPool(size_t countWorkers);
+
+    ~RunnableWorkersPool();
+
+    void Add(IRunnableBySteps* runnable);  // Takes the ownership
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/MultiThreading/Semaphore.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,65 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "Semaphore.h"
+
+#include "../OrthancException.h"
+
+
+namespace Orthanc
+{
+  Semaphore::Semaphore(unsigned int count) : count_(count)
+  {
+  }
+
+  void Semaphore::Release()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    count_++;
+    condition_.notify_one(); 
+  }
+
+  void Semaphore::Acquire()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    while (count_ == 0)
+    {
+      condition_.wait(lock);
+    }
+
+    count_++;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/MultiThreading/Semaphore.h	Fri Mar 23 11:04:03 2018 +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-2018 Osimis S.A., 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 <boost/thread.hpp>
+
+namespace Orthanc
+{
+  class Semaphore : public boost::noncopyable
+  {
+  private:
+    unsigned int count_;
+    boost::mutex mutex_;
+    boost::condition_variable condition_;
+
+  public:
+    explicit Semaphore(unsigned int count);
+
+    void Release();
+
+    void Acquire();
+
+    class Locker : public boost::noncopyable
+    {
+    private:
+      Semaphore&  that_;
+
+    public:
+      explicit Locker(Semaphore& that) :
+        that_(that)
+      {
+        that_.Acquire();
+      }
+
+      ~Locker()
+      {
+        that_.Release();
+      }
+    };
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/MultiThreading/SharedMessageQueue.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,209 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "SharedMessageQueue.h"
+
+
+
+/**
+ * FIFO (queue):
+ * 
+ *            back                         front
+ *            +--+--+--+--+--+--+--+--+--+--+--+
+ * Enqueue -> |  |  |  |  |  |  |  |  |  |  |  |
+ *            |  |  |  |  |  |  |  |  |  |  |  | -> Dequeue
+ *            +--+--+--+--+--+--+--+--+--+--+--+
+ *                                            ^
+ *                                            |
+ *                                      Make room here
+ *
+ *
+ * LIFO (stack):
+ * 
+ *            back                         front
+ *            +--+--+--+--+--+--+--+--+--+--+--+
+ *            |  |  |  |  |  |  |  |  |  |  |  | <- Enqueue
+ *            |  |  |  |  |  |  |  |  |  |  |  | -> Dequeue
+ *            +--+--+--+--+--+--+--+--+--+--+--+
+ *              ^
+ *              |
+ *        Make room here
+ **/
+
+
+namespace Orthanc
+{
+  SharedMessageQueue::SharedMessageQueue(unsigned int maxSize) :
+    isFifo_(true),
+    maxSize_(maxSize)
+  {
+  }
+
+
+  SharedMessageQueue::~SharedMessageQueue()
+  {
+    for (Queue::iterator it = queue_.begin(); it != queue_.end(); ++it)
+    {
+      delete *it;
+    }
+  }
+
+
+  void SharedMessageQueue::Enqueue(IDynamicObject* message)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (maxSize_ != 0 && queue_.size() > maxSize_)
+    {
+      if (isFifo_)
+      {
+        // Too many elements in the queue: Make room
+        delete queue_.front();
+        queue_.pop_front();
+      }
+      else
+      {
+        // Too many elements in the stack: Make room
+        delete queue_.back();
+        queue_.pop_back();
+      }
+    }
+
+    if (isFifo_)
+    {
+      // Queue policy (FIFO)
+      queue_.push_back(message);
+    }
+    else
+    {
+      // Stack policy (LIFO)
+      queue_.push_front(message);
+    }
+
+    elementAvailable_.notify_one();
+  }
+
+
+  IDynamicObject* SharedMessageQueue::Dequeue(int32_t millisecondsTimeout)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    // Wait for a message to arrive in the FIFO queue
+    while (queue_.empty())
+    {
+      if (millisecondsTimeout == 0)
+      {
+        elementAvailable_.wait(lock);
+      }
+      else
+      {
+        bool success = elementAvailable_.timed_wait
+          (lock, boost::posix_time::milliseconds(millisecondsTimeout));
+        if (!success)
+        {
+          return NULL;
+        }
+      }
+    }
+
+    std::auto_ptr<IDynamicObject> message(queue_.front());
+    queue_.pop_front();
+
+    if (queue_.empty())
+    {
+      emptied_.notify_all();
+    }
+
+    return message.release();
+  }
+
+
+
+  bool SharedMessageQueue::WaitEmpty(int32_t millisecondsTimeout)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    
+    // Wait for the queue to become empty
+    while (!queue_.empty())
+    {
+      if (millisecondsTimeout == 0)
+      {
+        emptied_.wait(lock);
+      }
+      else
+      {
+        if (!emptied_.timed_wait
+            (lock, boost::posix_time::milliseconds(millisecondsTimeout)))
+        {
+          return false;
+        }
+      }
+    }
+
+    return true;
+  }
+
+
+  void SharedMessageQueue::SetFifoPolicy()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    isFifo_ = true;
+  }
+
+  void SharedMessageQueue::SetLifoPolicy()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    isFifo_ = false;
+  }
+
+  void SharedMessageQueue::Clear()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    if (queue_.empty())
+    {
+      return;
+    }
+    else
+    {
+      while (!queue_.empty())
+      {
+        std::auto_ptr<IDynamicObject> message(queue_.front());
+        queue_.pop_front();
+      }
+
+      emptied_.notify_all();
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/MultiThreading/SharedMessageQueue.h	Fri Mar 23 11:04:03 2018 +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-2018 Osimis S.A., 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 "../IDynamicObject.h"
+
+#include <stdint.h>
+#include <list>
+#include <boost/thread.hpp>
+
+namespace Orthanc
+{
+  class SharedMessageQueue : public boost::noncopyable
+  {
+  private:
+    typedef std::list<IDynamicObject*>  Queue;
+
+    bool isFifo_;
+    unsigned int maxSize_;
+    Queue queue_;
+    boost::mutex mutex_;
+    boost::condition_variable elementAvailable_;
+    boost::condition_variable emptied_;
+
+  public:
+    explicit SharedMessageQueue(unsigned int maxSize = 0);
+    
+    ~SharedMessageQueue();
+
+    // This transfers the ownership of the message
+    void Enqueue(IDynamicObject* message);
+
+    // The caller is responsible to delete the dequeud message!
+    IDynamicObject* Dequeue(int32_t millisecondsTimeout);
+
+    bool WaitEmpty(int32_t millisecondsTimeout);
+
+    bool IsFifoPolicy() const
+    {
+      return isFifo_;
+    }
+
+    bool IsLifoPolicy() const
+    {
+      return !isFifo_;
+    }
+
+    void SetFifoPolicy();
+
+    void SetLifoPolicy();
+
+    void Clear();
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/OrthancException.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,77 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 <stdint.h>
+#include <string>
+#include "Enumerations.h"
+
+namespace Orthanc
+{
+  class OrthancException
+  {
+  protected:
+    ErrorCode  errorCode_;
+    HttpStatus httpStatus_;
+
+  public:
+    explicit OrthancException(ErrorCode errorCode) : 
+      errorCode_(errorCode),
+      httpStatus_(ConvertErrorCodeToHttpStatus(errorCode))
+    {
+    }
+
+    OrthancException(ErrorCode errorCode,
+                     HttpStatus httpStatus) :
+      errorCode_(errorCode),
+      httpStatus_(httpStatus)
+    {
+    }
+
+    ErrorCode GetErrorCode() const
+    {
+      return errorCode_;
+    }
+
+    HttpStatus GetHttpStatus() const
+    {
+      return httpStatus_;
+    }
+
+    const char* What() const
+    {
+      return EnumerationToString(errorCode_);
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/PrecompiledHeaders.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,34 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/PrecompiledHeaders.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,76 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 defined(_WIN32) && !defined(NOMINMAX)
+#define NOMINMAX
+#endif
+
+#if ORTHANC_USE_PRECOMPILED_HEADERS == 1
+
+#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/thread.hpp>
+#include <boost/thread/shared_mutex.hpp>
+
+#include <json/value.h>
+
+#if ORTHANC_ENABLE_PUGIXML == 1
+#  include <pugixml.hpp>
+#endif
+
+#include "Enumerations.h"
+#include "Logging.h"
+#include "OrthancException.h"
+#include "Toolbox.h"
+
+#if ORTHANC_ENABLE_DCMTK == 1
+// Headers from DCMTK used in Orthanc headers 
+#  include <dcmtk/dcmdata/dcdatset.h>
+#  include <dcmtk/dcmdata/dcfilefo.h>
+#  include <dcmtk/dcmdata/dcmetinf.h>
+#  include <dcmtk/dcmdata/dcpixseq.h>
+#endif
+
+#if ORTHANC_ENABLE_DCMTK_NETWORKING == 1
+#  include "DicomNetworking/DicomServer.h"
+
+// Headers from DCMTK used in Orthanc headers 
+#  include <dcmtk/dcmnet/dimse.h>
+#endif
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/SharedLibrary.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,136 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "SharedLibrary.h"
+
+#include "Logging.h"
+#include "OrthancException.h"
+
+#include <boost/filesystem.hpp>
+
+#if defined(_WIN32)
+#include <windows.h>
+#elif defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__)
+#include <dlfcn.h>
+#else
+#error Support your platform here
+#endif
+
+namespace Orthanc
+{
+  SharedLibrary::SharedLibrary(const std::string& path) : 
+    path_(path), 
+    handle_(NULL)
+  {
+#if defined(_WIN32)
+    handle_ = ::LoadLibraryA(path_.c_str());
+    if (handle_ == NULL)
+    {
+      LOG(ERROR) << "LoadLibrary(" << path_ << ") failed: Error " << ::GetLastError();
+      throw OrthancException(ErrorCode_SharedLibrary);
+    }
+
+#elif defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__)
+    handle_ = ::dlopen(path_.c_str(), RTLD_NOW);
+    if (handle_ == NULL) 
+    {
+      std::string explanation;
+      const char *tmp = ::dlerror();
+      if (tmp)
+      {
+        explanation = ": Error " + std::string(tmp);
+      }
+
+      LOG(ERROR) << "dlopen(" << path_ << ") failed" << explanation;
+      throw OrthancException(ErrorCode_SharedLibrary);
+    }
+
+#else
+#error Support your platform here
+#endif   
+  }
+
+  SharedLibrary::~SharedLibrary()
+  {
+    if (handle_)
+    {
+#if defined(_WIN32)
+      ::FreeLibrary((HMODULE)handle_);
+#elif defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__)
+      ::dlclose(handle_);
+#else
+#error Support your platform here
+#endif
+    }
+  }
+
+
+  SharedLibrary::FunctionPointer SharedLibrary::GetFunctionInternal(const std::string& name)
+  {
+    if (!handle_)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+#if defined(_WIN32)
+    return ::GetProcAddress((HMODULE)handle_, name.c_str());
+#elif defined(__linux__) || (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__)
+    return ::dlsym(handle_, name.c_str());
+#else
+#error Support your platform here
+#endif
+  }
+
+
+  SharedLibrary::FunctionPointer SharedLibrary::GetFunction(const std::string& name)
+  {
+    SharedLibrary::FunctionPointer result = GetFunctionInternal(name);
+  
+    if (result == NULL)
+    {
+      LOG(ERROR) << "Shared library does not expose function \"" << name << "\"";
+      throw OrthancException(ErrorCode_SharedLibrary);
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  bool SharedLibrary::HasFunction(const std::string& name)
+  {
+    return GetFunctionInternal(name) != NULL;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/SharedLibrary.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,82 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 !defined(ORTHANC_SANDBOXED)
+#  error The macro ORTHANC_SANDBOXED must be defined
+#endif
+
+#if ORTHANC_SANDBOXED == 1
+#  error The namespace SystemToolbox cannot be used in sandboxed environments
+#endif
+
+#if defined(_WIN32)
+#include <windows.h>
+#endif
+
+#include <string>
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class SharedLibrary : public boost::noncopyable
+  {
+  public:
+#if defined(_WIN32)
+    typedef FARPROC FunctionPointer;
+#else
+    typedef void* FunctionPointer;
+#endif
+
+  private:
+    std::string path_;
+    void *handle_;
+
+    FunctionPointer GetFunctionInternal(const std::string& name);
+
+  public:
+    explicit SharedLibrary(const std::string& path);
+
+    ~SharedLibrary();
+
+    const std::string& GetPath() const
+    {
+      return path_;
+    }
+
+    bool HasFunction(const std::string& name);
+
+    FunctionPointer GetFunction(const std::string& name);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/SystemToolbox.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,598 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "SystemToolbox.h"
+
+
+#if defined(_WIN32)
+#  include <windows.h>
+#  include <process.h>   // For "_spawnvp()" and "_getpid()"
+#else
+#  include <unistd.h>    // For "execvp()"
+#  include <sys/wait.h>  // For "waitpid()"
+#endif
+
+
+#if defined(__APPLE__) && defined(__MACH__)
+#  include <mach-o/dyld.h> /* _NSGetExecutablePath */
+#  include <limits.h>      /* PATH_MAX */
+#endif
+
+
+#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
+#  include <limits.h>      /* PATH_MAX */
+#  include <signal.h>
+#  include <unistd.h>
+#endif
+
+
+#if defined(__OpenBSD__)
+#  include <sys/sysctl.h>  // For "sysctl", "CTL_KERN" and "KERN_PROC_ARGS"
+#endif
+
+
+// Inclusions for UUID
+// http://stackoverflow.com/a/1626302
+
+extern "C"
+{
+#if defined(_WIN32)
+#  include <rpc.h>
+#else
+#  include <uuid/uuid.h>
+#endif
+}
+
+
+#include "Logging.h"
+#include "OrthancException.h"
+#include "Toolbox.h"
+
+#include <boost/filesystem.hpp>
+#include <boost/filesystem/fstream.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+
+namespace Orthanc
+{
+  static bool finish_;
+  static ServerBarrierEvent barrierEvent_;
+
+#if defined(_WIN32)
+  static BOOL WINAPI ConsoleControlHandler(DWORD dwCtrlType)
+  {
+    // http://msdn.microsoft.com/en-us/library/ms683242(v=vs.85).aspx
+    finish_ = true;
+    return true;
+  }
+#else
+  static void SignalHandler(int signal)
+  {
+    if (signal == SIGHUP)
+    {
+      barrierEvent_ = ServerBarrierEvent_Reload;
+    }
+
+    finish_ = true;
+  }
+#endif
+
+
+  static ServerBarrierEvent ServerBarrierInternal(const bool* stopFlag)
+  {
+#if defined(_WIN32)
+    SetConsoleCtrlHandler(ConsoleControlHandler, true);
+#else
+    signal(SIGINT, SignalHandler);
+    signal(SIGQUIT, SignalHandler);
+    signal(SIGTERM, SignalHandler);
+    signal(SIGHUP, SignalHandler);
+#endif
+  
+    // Active loop that awakens every 100ms
+    finish_ = false;
+    barrierEvent_ = ServerBarrierEvent_Stop;
+    while (!(*stopFlag || finish_))
+    {
+      SystemToolbox::USleep(100 * 1000);
+    }
+
+#if defined(_WIN32)
+    SetConsoleCtrlHandler(ConsoleControlHandler, false);
+#else
+    signal(SIGINT, NULL);
+    signal(SIGQUIT, NULL);
+    signal(SIGTERM, NULL);
+    signal(SIGHUP, NULL);
+#endif
+
+    return barrierEvent_;
+  }
+
+
+  ServerBarrierEvent SystemToolbox::ServerBarrier(const bool& stopFlag)
+  {
+    return ServerBarrierInternal(&stopFlag);
+  }
+
+
+  ServerBarrierEvent SystemToolbox::ServerBarrier()
+  {
+    const bool stopFlag = false;
+    return ServerBarrierInternal(&stopFlag);
+  }
+
+
+  void SystemToolbox::USleep(uint64_t microSeconds)
+  {
+#if defined(_WIN32)
+    ::Sleep(static_cast<DWORD>(microSeconds / static_cast<uint64_t>(1000)));
+#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__native_client__)
+    usleep(microSeconds);
+#else
+#error Support your platform here
+#endif
+  }
+
+
+  static std::streamsize GetStreamSize(std::istream& f)
+  {
+    // http://www.cplusplus.com/reference/iostream/istream/tellg/
+    f.seekg(0, std::ios::end);
+    std::streamsize size = f.tellg();
+    f.seekg(0, std::ios::beg);
+
+    return size;
+  }
+
+
+  void SystemToolbox::ReadFile(std::string& content,
+                               const std::string& path) 
+  {
+    if (!IsRegularFile(path))
+    {
+      LOG(ERROR) << std::string("The path does not point to a regular file: ") << path;
+      throw OrthancException(ErrorCode_RegularFileExpected);
+    }
+
+    boost::filesystem::ifstream f;
+    f.open(path, std::ifstream::in | std::ifstream::binary);
+    if (!f.good())
+    {
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+
+    std::streamsize size = GetStreamSize(f);
+    content.resize(static_cast<size_t>(size));
+    if (size != 0)
+    {
+      f.read(reinterpret_cast<char*>(&content[0]), size);
+    }
+
+    f.close();
+  }
+
+
+  bool SystemToolbox::ReadHeader(std::string& header,
+                                 const std::string& path,
+                                 size_t headerSize)
+  {
+    if (!IsRegularFile(path))
+    {
+      LOG(ERROR) << std::string("The path does not point to a regular file: ") << path;
+      throw OrthancException(ErrorCode_RegularFileExpected);
+    }
+
+    boost::filesystem::ifstream f;
+    f.open(path, std::ifstream::in | std::ifstream::binary);
+    if (!f.good())
+    {
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+
+    bool full = true;
+
+    {
+      std::streamsize size = GetStreamSize(f);
+      if (size <= 0)
+      {
+        headerSize = 0;
+        full = false;
+      }
+      else if (static_cast<size_t>(size) < headerSize)
+      {
+        headerSize = static_cast<size_t>(size);  // Truncate to the size of the file
+        full = false;
+      }
+    }
+
+    header.resize(headerSize);
+    if (headerSize != 0)
+    {
+      f.read(reinterpret_cast<char*>(&header[0]), headerSize);
+    }
+
+    f.close();
+
+    return full;
+  }
+
+
+  void SystemToolbox::WriteFile(const void* content,
+                                size_t size,
+                                const std::string& path)
+  {
+    boost::filesystem::ofstream f;
+    f.open(path, std::ofstream::out | std::ofstream::binary);
+    if (!f.good())
+    {
+      throw OrthancException(ErrorCode_CannotWriteFile);
+    }
+
+    if (size != 0)
+    {
+      f.write(reinterpret_cast<const char*>(content), size);
+
+      if (!f.good())
+      {
+        f.close();
+        throw OrthancException(ErrorCode_FileStorageCannotWrite);
+      }
+    }
+
+    f.close();
+  }
+
+
+  void SystemToolbox::WriteFile(const std::string& content,
+                                const std::string& path)
+  {
+    WriteFile(content.size() > 0 ? content.c_str() : NULL,
+              content.size(), path);
+  }
+
+
+  void SystemToolbox::RemoveFile(const std::string& path)
+  {
+    if (boost::filesystem::exists(path))
+    {
+      if (IsRegularFile(path))
+      {
+        boost::filesystem::remove(path);
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_RegularFileExpected);
+      }
+    }
+  }
+
+
+  uint64_t SystemToolbox::GetFileSize(const std::string& path)
+  {
+    try
+    {
+      return static_cast<uint64_t>(boost::filesystem::file_size(path));
+    }
+    catch (boost::filesystem::filesystem_error&)
+    {
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+  }
+
+
+  void SystemToolbox::MakeDirectory(const std::string& path)
+  {
+    if (boost::filesystem::exists(path))
+    {
+      if (!boost::filesystem::is_directory(path))
+      {
+        throw OrthancException(ErrorCode_DirectoryOverFile);
+      }
+    }
+    else
+    {
+      if (!boost::filesystem::create_directories(path))
+      {
+        throw OrthancException(ErrorCode_MakeDirectory);
+      }
+    }
+  }
+
+
+  bool SystemToolbox::IsExistingFile(const std::string& path)
+  {
+    return boost::filesystem::exists(path);
+  }
+
+
+#if defined(_WIN32)
+  static std::string GetPathToExecutableInternal()
+  {
+    // Yes, this is ugly, but there is no simple way to get the 
+    // required buffer size, so we use a big constant
+    std::vector<char> buffer(32768);
+    /*int bytes =*/ GetModuleFileNameA(NULL, &buffer[0], static_cast<DWORD>(buffer.size() - 1));
+    return std::string(&buffer[0]);
+  }
+
+#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
+  static std::string GetPathToExecutableInternal()
+  {
+    // NOTE: For FreeBSD, using KERN_PROC_PATHNAME might be a better alternative
+
+    std::vector<char> buffer(PATH_MAX + 1);
+    ssize_t bytes = readlink("/proc/self/exe", &buffer[0], buffer.size() - 1);
+    if (bytes == 0)
+    {
+      throw OrthancException(ErrorCode_PathToExecutable);
+    }
+
+    return std::string(&buffer[0]);
+  }
+
+#elif defined(__APPLE__) && defined(__MACH__)
+  static std::string GetPathToExecutableInternal()
+  {
+    char pathbuf[PATH_MAX + 1];
+    unsigned int  bufsize = static_cast<int>(sizeof(pathbuf));
+
+    _NSGetExecutablePath( pathbuf, &bufsize);
+
+    return std::string(pathbuf);
+  }
+
+#elif defined(__OpenBSD__)
+  static std::string GetPathToExecutableInternal()
+  {
+    // This is an adapted version of the patch proposed in issue #64
+    // without an explicit call to "malloc()" to prevent memory leak
+    // https://bitbucket.org/sjodogne/orthanc/issues/64/add-openbsd-support
+    // https://stackoverflow.com/q/31494901/881731
+
+    const int mib[4] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV };
+
+    size_t len;
+    if (sysctl(mib, 4, NULL, &len, NULL, 0) == -1) 
+    {
+      throw OrthancException(ErrorCode_PathToExecutable);
+    }
+
+    std::string tmp;
+    tmp.resize(len);
+
+    char** buffer = reinterpret_cast<char**>(&tmp[0]);
+
+    if (sysctl(mib, 4, buffer, &len, NULL, 0) == -1) 
+    {
+      throw OrthancException(ErrorCode_PathToExecutable);
+    }
+    else
+    {
+      return std::string(buffer[0]);
+    }
+  }
+
+#else
+#error Support your platform here
+#endif
+
+
+  std::string SystemToolbox::GetPathToExecutable()
+  {
+    boost::filesystem::path p(GetPathToExecutableInternal());
+    return boost::filesystem::absolute(p).string();
+  }
+
+
+  std::string SystemToolbox::GetDirectoryOfExecutable()
+  {
+    boost::filesystem::path p(GetPathToExecutableInternal());
+    return boost::filesystem::absolute(p.parent_path()).string();
+  }
+
+
+  void SystemToolbox::ExecuteSystemCommand(const std::string& command,
+                                           const std::vector<std::string>& arguments)
+  {
+    // Convert the arguments as a C array
+    std::vector<char*>  args(arguments.size() + 2);
+
+    args.front() = const_cast<char*>(command.c_str());
+
+    for (size_t i = 0; i < arguments.size(); i++)
+    {
+      args[i + 1] = const_cast<char*>(arguments[i].c_str());
+    }
+
+    args.back() = NULL;
+
+    int status;
+
+#if defined(_WIN32)
+    // http://msdn.microsoft.com/en-us/library/275khfab.aspx
+    status = static_cast<int>(_spawnvp(_P_OVERLAY, command.c_str(), &args[0]));
+
+#else
+    int pid = fork();
+
+    if (pid == -1)
+    {
+      // Error in fork()
+#if ORTHANC_ENABLE_LOGGING == 1
+      LOG(ERROR) << "Cannot fork a child process";
+#endif
+
+      throw OrthancException(ErrorCode_SystemCommand);
+    }
+    else if (pid == 0)
+    {
+      // Execute the system command in the child process
+      execvp(command.c_str(), &args[0]);
+
+      // We should never get here
+      _exit(1);
+    }
+    else
+    {
+      // Wait for the system command to exit
+      waitpid(pid, &status, 0);
+    }
+#endif
+
+    if (status != 0)
+    {
+#if ORTHANC_ENABLE_LOGGING == 1
+      LOG(ERROR) << "System command failed with status code " << status;
+#endif
+
+      throw OrthancException(ErrorCode_SystemCommand);
+    }
+  }
+
+
+  int SystemToolbox::GetProcessId()
+  {
+#if defined(_WIN32)
+    return static_cast<int>(_getpid());
+#else
+    return static_cast<int>(getpid());
+#endif
+  }
+
+
+  bool SystemToolbox::IsRegularFile(const std::string& path)
+  {
+    namespace fs = boost::filesystem;
+
+    try
+    {
+      if (fs::exists(path))
+      {
+        fs::file_status status = fs::status(path);
+        return (status.type() == boost::filesystem::regular_file ||
+                status.type() == boost::filesystem::reparse_file);   // Fix BitBucket issue #11
+      }
+    }
+    catch (fs::filesystem_error&)
+    {
+    }
+
+    return false;
+  }
+
+
+  FILE* SystemToolbox::OpenFile(const std::string& path,
+                                FileMode mode)
+  {
+#if defined(_WIN32)
+    // TODO Deal with special characters by converting to the current locale
+#endif
+
+    const char* m;
+    switch (mode)
+    {
+      case FileMode_ReadBinary:
+        m = "rb";
+        break;
+
+      case FileMode_WriteBinary:
+        m = "wb";
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    return fopen(path.c_str(), m);
+  }
+
+
+  std::string SystemToolbox::GenerateUuid()
+  {
+#ifdef WIN32
+    UUID uuid;
+    UuidCreate ( &uuid );
+
+    unsigned char * str;
+    UuidToStringA ( &uuid, &str );
+
+    std::string s( ( char* ) str );
+
+    RpcStringFreeA ( &str );
+#else
+    uuid_t uuid;
+    uuid_generate_random ( uuid );
+    char s[37];
+    uuid_unparse ( uuid, s );
+#endif
+    return s;
+  }
+
+
+  static boost::posix_time::ptime GetNow(bool utc)
+  {
+    if (utc)
+    {
+      return boost::posix_time::second_clock::universal_time();
+    }
+    else
+    {
+      return boost::posix_time::second_clock::local_time();
+    }
+  }
+
+
+  std::string SystemToolbox::GetNowIsoString(bool utc)
+  {
+    return boost::posix_time::to_iso_string(GetNow(utc));
+  }
+
+  
+  void SystemToolbox::GetNowDicom(std::string& date,
+                                  std::string& time,
+                                  bool utc)
+  {
+    boost::posix_time::ptime now = GetNow(utc);
+    tm tm = boost::posix_time::to_tm(now);
+
+    char s[32];
+    sprintf(s, "%04d%02d%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
+    date.assign(s);
+
+    // TODO milliseconds
+    sprintf(s, "%02d%02d%02d.%06d", tm.tm_hour, tm.tm_min, tm.tm_sec, 0);
+    time.assign(s);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/SystemToolbox.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,104 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 !defined(ORTHANC_SANDBOXED)
+#  error The macro ORTHANC_SANDBOXED must be defined
+#endif
+
+#if ORTHANC_SANDBOXED == 1
+#  error The namespace SystemToolbox cannot be used in sandboxed environments
+#endif
+
+#include "Enumerations.h"
+
+#include <vector>
+#include <string>
+#include <stdint.h>
+
+namespace Orthanc
+{
+  namespace SystemToolbox
+  {
+    void USleep(uint64_t microSeconds);
+
+    ServerBarrierEvent ServerBarrier(const bool& stopFlag);
+
+    ServerBarrierEvent ServerBarrier();
+
+    void ReadFile(std::string& content,
+                  const std::string& path);
+
+    bool ReadHeader(std::string& header,
+                    const std::string& path,
+                    size_t headerSize);
+
+    void WriteFile(const void* content,
+                   size_t size,
+                   const std::string& path);
+
+    void WriteFile(const std::string& content,
+                   const std::string& path);
+
+    void RemoveFile(const std::string& path);
+
+    uint64_t GetFileSize(const std::string& path);
+
+    void MakeDirectory(const std::string& path);
+
+    bool IsExistingFile(const std::string& path);
+
+    std::string GetPathToExecutable();
+
+    std::string GetDirectoryOfExecutable();
+
+    void ExecuteSystemCommand(const std::string& command,
+                              const std::vector<std::string>& arguments);
+
+    int GetProcessId();
+
+    bool IsRegularFile(const std::string& path);
+
+    FILE* OpenFile(const std::string& path,
+                   FileMode mode);
+
+    std::string GenerateUuid();
+
+    std::string GetNowIsoString(bool utc);
+
+    void GetNowDicom(std::string& date,
+                     std::string& time,
+                     bool utc);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/TemporaryFile.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,95 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "TemporaryFile.h"
+
+#include "SystemToolbox.h"
+#include "Toolbox.h"
+
+#include <boost/filesystem.hpp>
+
+namespace Orthanc
+{
+  static std::string CreateTemporaryPath(const char* extension)
+  {
+#if BOOST_HAS_FILESYSTEM_V3 == 1
+    boost::filesystem::path tmpDir = boost::filesystem::temp_directory_path();
+#elif defined(__linux__)
+    boost::filesystem::path tmpDir("/tmp");
+#else
+#error Support your platform here
+#endif
+
+    // We use UUID to create unique path to temporary files
+    std::string filename = "Orthanc-" + Orthanc::SystemToolbox::GenerateUuid();
+
+    if (extension != NULL)
+    {
+      filename.append(extension);
+    }
+
+    tmpDir /= filename;
+    return tmpDir.string();
+  }
+
+
+  TemporaryFile::TemporaryFile() : 
+    path_(CreateTemporaryPath(NULL))
+  {
+  }
+
+
+  TemporaryFile::TemporaryFile(const char* extension) :
+    path_(CreateTemporaryPath(extension))
+  {
+  }
+
+
+  TemporaryFile::~TemporaryFile()
+  {
+    boost::filesystem::remove(path_);
+  }
+
+
+  void TemporaryFile::Write(const std::string& content)
+  {
+    SystemToolbox::WriteFile(content, path_);
+  }
+
+
+  void TemporaryFile::Read(std::string& content) const
+  {
+    SystemToolbox::ReadFile(content, path_);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/TemporaryFile.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,69 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 !defined(ORTHANC_SANDBOXED)
+#  error The macro ORTHANC_SANDBOXED must be defined
+#endif
+
+#if ORTHANC_SANDBOXED == 1
+#  error The class TemporaryFile cannot be used in sandboxed environments
+#endif
+
+#include <string>
+
+namespace Orthanc
+{
+  class TemporaryFile
+  {
+  private:
+    std::string path_;
+
+  public:
+    TemporaryFile();
+
+    TemporaryFile(const char* extension);
+
+    ~TemporaryFile();
+
+    const std::string& GetPath() const
+    {
+      return path_;
+    }
+
+    void Write(const std::string& content);
+
+    void Read(std::string& content) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Toolbox.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,1379 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "Toolbox.h"
+
+#include "OrthancException.h"
+#include "Logging.h"
+
+#include <boost/algorithm/string/case_conv.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/regex.hpp> 
+#include <boost/uuid/sha1.hpp>
+ 
+#include <string>
+#include <stdint.h>
+#include <string.h>
+#include <algorithm>
+#include <ctype.h>
+
+
+#if ORTHANC_ENABLE_MD5 == 1
+#  include "../Resources/ThirdParty/md5/md5.h"
+#endif
+
+#if ORTHANC_ENABLE_BASE64 == 1
+#  include "../Resources/ThirdParty/base64/base64.h"
+#endif
+
+#if ORTHANC_ENABLE_LOCALE == 1
+#  include <boost/locale.hpp>
+#endif
+
+
+#if defined(_MSC_VER) && (_MSC_VER < 1800)
+// Patch for the missing "_strtoll" symbol when compiling with Visual Studio < 2013
+extern "C"
+{
+  int64_t _strtoi64(const char *nptr, char **endptr, int base);
+  int64_t strtoll(const char *nptr, char **endptr, int base)
+  {
+    return _strtoi64(nptr, endptr, base);
+  } 
+}
+#endif
+
+
+#if defined(_WIN32)
+#  include <windows.h>   // For ::Sleep
+#endif
+
+
+#if ORTHANC_ENABLE_PUGIXML == 1
+#  include "ChunkedBuffer.h"
+#  include <pugixml.hpp>
+#endif
+
+
+namespace Orthanc
+{
+  void Toolbox::ToUpperCase(std::string& s)
+  {
+    std::transform(s.begin(), s.end(), s.begin(), toupper);
+  }
+
+
+  void Toolbox::ToLowerCase(std::string& s)
+  {
+    std::transform(s.begin(), s.end(), s.begin(), tolower);
+  }
+
+
+  void Toolbox::ToUpperCase(std::string& result,
+                            const std::string& source)
+  {
+    result = source;
+    ToUpperCase(result);
+  }
+
+  void Toolbox::ToLowerCase(std::string& result,
+                            const std::string& source)
+  {
+    result = source;
+    ToLowerCase(result);
+  }
+
+
+  void Toolbox::SplitUriComponents(UriComponents& components,
+                                   const std::string& uri)
+  {
+    static const char URI_SEPARATOR = '/';
+
+    components.clear();
+
+    if (uri.size() == 0 ||
+        uri[0] != URI_SEPARATOR)
+    {
+      throw OrthancException(ErrorCode_UriSyntax);
+    }
+
+    // Count the number of slashes in the URI to make an assumption
+    // about the number of components in the URI
+    unsigned int estimatedSize = 0;
+    for (unsigned int i = 0; i < uri.size(); i++)
+    {
+      if (uri[i] == URI_SEPARATOR)
+        estimatedSize++;
+    }
+
+    components.reserve(estimatedSize - 1);
+
+    unsigned int start = 1;
+    unsigned int end = 1;
+    while (end < uri.size())
+    {
+      // This is the loop invariant
+      assert(uri[start - 1] == '/' && (end >= start));
+
+      if (uri[end] == '/')
+      {
+        components.push_back(std::string(&uri[start], end - start));
+        end++;
+        start = end;
+      }
+      else
+      {
+        end++;
+      }
+    }
+
+    if (start < uri.size())
+    {
+      components.push_back(std::string(&uri[start], end - start));
+    }
+
+    for (size_t i = 0; i < components.size(); i++)
+    {
+      if (components[i].size() == 0)
+      {
+        // Empty component, as in: "/coucou//e"
+        throw OrthancException(ErrorCode_UriSyntax);
+      }
+    }
+  }
+
+
+  void Toolbox::TruncateUri(UriComponents& target,
+                            const UriComponents& source,
+                            size_t fromLevel)
+  {
+    target.clear();
+
+    if (source.size() > fromLevel)
+    {
+      target.resize(source.size() - fromLevel);
+
+      size_t j = 0;
+      for (size_t i = fromLevel; i < source.size(); i++, j++)
+      {
+        target[j] = source[i];
+      }
+
+      assert(j == target.size());
+    }
+  }
+  
+
+
+  bool Toolbox::IsChildUri(const UriComponents& baseUri,
+                           const UriComponents& testedUri)
+  {
+    if (testedUri.size() < baseUri.size())
+    {
+      return false;
+    }
+
+    for (size_t i = 0; i < baseUri.size(); i++)
+    {
+      if (baseUri[i] != testedUri[i])
+        return false;
+    }
+
+    return true;
+  }
+
+
+  std::string Toolbox::AutodetectMimeType(const std::string& path)
+  {
+    std::string contentType;
+    size_t lastDot = path.rfind('.');
+    size_t lastSlash = path.rfind('/');
+
+    if (lastDot == std::string::npos ||
+        (lastSlash != std::string::npos && lastDot < lastSlash))
+    {
+      // No trailing dot, unable to detect the content type
+    }
+    else
+    {
+      const char* extension = &path[lastDot + 1];
+    
+      // http://en.wikipedia.org/wiki/Mime_types
+      // Text types
+      if (!strcmp(extension, "txt"))
+        contentType = "text/plain";
+      else if (!strcmp(extension, "html"))
+        contentType = "text/html";
+      else if (!strcmp(extension, "xml"))
+        contentType = "text/xml";
+      else if (!strcmp(extension, "css"))
+        contentType = "text/css";
+
+      // Application types
+      else if (!strcmp(extension, "js"))
+        contentType = "application/javascript";
+      else if (!strcmp(extension, "json"))
+        contentType = "application/json";
+      else if (!strcmp(extension, "pdf"))
+        contentType = "application/pdf";
+
+      // Images types
+      else if (!strcmp(extension, "jpg") || !strcmp(extension, "jpeg"))
+        contentType = "image/jpeg";
+      else if (!strcmp(extension, "gif"))
+        contentType = "image/gif";
+      else if (!strcmp(extension, "png"))
+        contentType = "image/png";
+    }
+
+    return contentType;
+  }
+
+
+  std::string Toolbox::FlattenUri(const UriComponents& components,
+                                  size_t fromLevel)
+  {
+    if (components.size() <= fromLevel)
+    {
+      return "/";
+    }
+    else
+    {
+      std::string r;
+
+      for (size_t i = fromLevel; i < components.size(); i++)
+      {
+        r += "/" + components[i];
+      }
+
+      return r;
+    }
+  }
+
+
+#if ORTHANC_ENABLE_MD5 == 1
+  static char GetHexadecimalCharacter(uint8_t value)
+  {
+    assert(value < 16);
+
+    if (value < 10)
+    {
+      return value + '0';
+    }
+    else
+    {
+      return (value - 10) + 'a';
+    }
+  }
+
+
+  void Toolbox::ComputeMD5(std::string& result,
+                           const std::string& data)
+  {
+    if (data.size() > 0)
+    {
+      ComputeMD5(result, &data[0], data.size());
+    }
+    else
+    {
+      ComputeMD5(result, NULL, 0);
+    }
+  }
+
+
+  void Toolbox::ComputeMD5(std::string& result,
+                           const void* data,
+                           size_t size)
+  {
+    md5_state_s state;
+    md5_init(&state);
+
+    if (size > 0)
+    {
+      md5_append(&state, 
+                 reinterpret_cast<const md5_byte_t*>(data), 
+                 static_cast<int>(size));
+    }
+
+    md5_byte_t actualHash[16];
+    md5_finish(&state, actualHash);
+
+    result.resize(32);
+    for (unsigned int i = 0; i < 16; i++)
+    {
+      result[2 * i] = GetHexadecimalCharacter(static_cast<uint8_t>(actualHash[i] / 16));
+      result[2 * i + 1] = GetHexadecimalCharacter(static_cast<uint8_t>(actualHash[i] % 16));
+    }
+  }
+#endif
+
+
+#if ORTHANC_ENABLE_BASE64 == 1
+  void Toolbox::EncodeBase64(std::string& result, 
+                             const std::string& data)
+  {
+    result = base64_encode(data);
+  }
+
+  void Toolbox::DecodeBase64(std::string& result, 
+                             const std::string& data)
+  {
+    for (size_t i = 0; i < data.length(); i++)
+    {
+      if (!isalnum(data[i]) &&
+          data[i] != '+' &&
+          data[i] != '/' &&
+          data[i] != '=')
+      {
+        // This is not a valid character for a Base64 string
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+    }
+
+    result = base64_decode(data);
+  }
+
+
+  bool Toolbox::DecodeDataUriScheme(std::string& mime,
+                                    std::string& content,
+                                    const std::string& source)
+  {
+    boost::regex pattern("data:([^;]+);base64,([a-zA-Z0-9=+/]*)",
+                         boost::regex::icase /* case insensitive search */);
+
+    boost::cmatch what;
+    if (regex_match(source.c_str(), what, pattern))
+    {
+      mime = what[1];
+      DecodeBase64(content, what[2]);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  void Toolbox::EncodeDataUriScheme(std::string& result,
+                                    const std::string& mime,
+                                    const std::string& content)
+  {
+    result = "data:" + mime + ";base64," + base64_encode(content);
+  }
+
+#endif
+
+
+#if ORTHANC_ENABLE_LOCALE == 1
+  static const char* GetBoostLocaleEncoding(const Encoding sourceEncoding)
+  {
+    switch (sourceEncoding)
+    {
+      case Encoding_Utf8:
+        return "UTF-8";
+
+      case Encoding_Ascii:
+        return "ASCII";
+
+      case Encoding_Latin1:
+        return "ISO-8859-1";
+        break;
+
+      case Encoding_Latin2:
+        return "ISO-8859-2";
+        break;
+
+      case Encoding_Latin3:
+        return "ISO-8859-3";
+        break;
+
+      case Encoding_Latin4:
+        return "ISO-8859-4";
+        break;
+
+      case Encoding_Latin5:
+        return "ISO-8859-9";
+        break;
+
+      case Encoding_Cyrillic:
+        return "ISO-8859-5";
+        break;
+
+      case Encoding_Windows1251:
+        return "WINDOWS-1251";
+        break;
+
+      case Encoding_Arabic:
+        return "ISO-8859-6";
+        break;
+
+      case Encoding_Greek:
+        return "ISO-8859-7";
+        break;
+
+      case Encoding_Hebrew:
+        return "ISO-8859-8";
+        break;
+        
+      case Encoding_Japanese:
+        return "SHIFT-JIS";
+        break;
+
+      case Encoding_Chinese:
+        return "GB18030";
+        break;
+
+      case Encoding_Thai:
+        return "TIS620.2533-0";
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+#endif
+
+
+#if ORTHANC_ENABLE_LOCALE == 1
+  std::string Toolbox::ConvertToUtf8(const std::string& source,
+                                     Encoding sourceEncoding)
+  {
+    if (sourceEncoding == Encoding_Utf8)
+    {
+      // Already in UTF-8: No conversion is required
+      return source;
+    }
+
+    if (sourceEncoding == Encoding_Ascii)
+    {
+      return ConvertToAscii(source);
+    }
+
+    const char* encoding = GetBoostLocaleEncoding(sourceEncoding);
+
+    try
+    {
+      return boost::locale::conv::to_utf<char>(source, encoding);
+    }
+    catch (std::runtime_error&)
+    {
+      // Bad input string or bad encoding
+      return ConvertToAscii(source);
+    }
+  }
+#endif
+  
+
+#if ORTHANC_ENABLE_LOCALE == 1
+  std::string Toolbox::ConvertFromUtf8(const std::string& source,
+                                       Encoding targetEncoding)
+  {
+    if (targetEncoding == Encoding_Utf8)
+    {
+      // Already in UTF-8: No conversion is required
+      return source;
+    }
+
+    if (targetEncoding == Encoding_Ascii)
+    {
+      return ConvertToAscii(source);
+    }
+
+    const char* encoding = GetBoostLocaleEncoding(targetEncoding);
+
+    try
+    {
+      return boost::locale::conv::from_utf<char>(source, encoding);
+    }
+    catch (std::runtime_error&)
+    {
+      // Bad input string or bad encoding
+      return ConvertToAscii(source);
+    }
+  }
+#endif
+
+
+  bool Toolbox::IsAsciiString(const void* data,
+                              size_t size)
+  {
+    const uint8_t* p = reinterpret_cast<const uint8_t*>(data);
+
+    for (size_t i = 0; i < size; i++, p++)
+    {
+      if (*p > 127 || *p == 0 || iscntrl(*p))
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+  bool Toolbox::IsAsciiString(const std::string& s)
+  {
+    return IsAsciiString(s.c_str(), s.size());
+  }
+  
+
+  std::string Toolbox::ConvertToAscii(const std::string& source)
+  {
+    std::string result;
+
+    result.reserve(source.size() + 1);
+    for (size_t i = 0; i < source.size(); i++)
+    {
+      if (source[i] <= 127 && source[i] >= 0 && !iscntrl(source[i]))
+      {
+        result.push_back(source[i]);
+      }
+    }
+
+    return result;
+  }
+
+
+  void Toolbox::ComputeSHA1(std::string& result,
+                            const void* data,
+                            size_t size)
+  {
+    boost::uuids::detail::sha1 sha1;
+
+    if (size > 0)
+    {
+      sha1.process_bytes(data, size);
+    }
+
+    unsigned int digest[5];
+
+    // Sanity check for the memory layout: A SHA-1 digest is 160 bits wide
+    assert(sizeof(unsigned int) == 4 && sizeof(digest) == (160 / 8)); 
+    
+    sha1.get_digest(digest);
+
+    result.resize(8 * 5 + 4);
+    sprintf(&result[0], "%08x-%08x-%08x-%08x-%08x",
+            digest[0],
+            digest[1],
+            digest[2],
+            digest[3],
+            digest[4]);
+  }
+
+  void Toolbox::ComputeSHA1(std::string& result,
+                            const std::string& data)
+  {
+    if (data.size() > 0)
+    {
+      ComputeSHA1(result, data.c_str(), data.size());
+    }
+    else
+    {
+      ComputeSHA1(result, NULL, 0);
+    }
+  }
+
+
+  bool Toolbox::IsSHA1(const char* str,
+                       size_t size)
+  {
+    if (size == 0)
+    {
+      return false;
+    }
+
+    const char* start = str;
+    const char* end = str + size;
+
+    // Trim the beginning of the string
+    while (start < end)
+    {
+      if (*start == '\0' ||
+          isspace(*start))
+      {
+        start++;
+      }
+      else
+      {
+        break;
+      }
+    }
+
+    // Trim the trailing of the string
+    while (start < end)
+    {
+      if (*(end - 1) == '\0' ||
+          isspace(*(end - 1)))
+      {
+        end--;
+      }
+      else
+      {
+        break;
+      }
+    }
+
+    if (end - start != 44)
+    {
+      return false;
+    }
+
+    for (unsigned int i = 0; i < 44; i++)
+    {
+      if (i == 8 ||
+          i == 17 ||
+          i == 26 ||
+          i == 35)
+      {
+        if (start[i] != '-')
+          return false;
+      }
+      else
+      {
+        if (!isalnum(start[i]))
+          return false;
+      }
+    }
+
+    return true;
+  }
+
+
+  bool Toolbox::IsSHA1(const std::string& s)
+  {
+    if (s.size() == 0)
+    {
+      return false;
+    }
+    else
+    {
+      return IsSHA1(s.c_str(), s.size());
+    }
+  }
+
+
+  std::string Toolbox::StripSpaces(const std::string& source)
+  {
+    size_t first = 0;
+
+    while (first < source.length() &&
+           isspace(source[first]))
+    {
+      first++;
+    }
+
+    if (first == source.length())
+    {
+      // String containing only spaces
+      return "";
+    }
+
+    size_t last = source.length();
+    while (last > first &&
+           isspace(source[last - 1]))
+    {
+      last--;
+    }          
+    
+    assert(first <= last);
+    return source.substr(first, last - first);
+  }
+
+
+  static char Hex2Dec(char c)
+  {
+    return ((c >= '0' && c <= '9') ? c - '0' :
+            ((c >= 'a' && c <= 'f') ? c - 'a' + 10 : c - 'A' + 10));
+  }
+
+  void Toolbox::UrlDecode(std::string& s)
+  {
+    // http://en.wikipedia.org/wiki/Percent-encoding
+    // http://www.w3schools.com/tags/ref_urlencode.asp
+    // http://stackoverflow.com/questions/154536/encode-decode-urls-in-c
+
+    if (s.size() == 0)
+    {
+      return;
+    }
+
+    size_t source = 0;
+    size_t target = 0;
+
+    while (source < s.size())
+    {
+      if (s[source] == '%' &&
+          source + 2 < s.size() &&
+          isalnum(s[source + 1]) &&
+          isalnum(s[source + 2]))
+      {
+        s[target] = (Hex2Dec(s[source + 1]) << 4) | Hex2Dec(s[source + 2]);
+        source += 3;
+        target += 1;
+      }
+      else
+      {
+        if (s[source] == '+')
+          s[target] = ' ';
+        else
+          s[target] = s[source];
+
+        source++;
+        target++;
+      }
+    }
+
+    s.resize(target);
+  }
+
+
+  Endianness Toolbox::DetectEndianness()
+  {
+    // http://sourceforge.net/p/predef/wiki/Endianness/
+
+    uint8_t buffer[4];
+
+    buffer[0] = 0x00;
+    buffer[1] = 0x01;
+    buffer[2] = 0x02;
+    buffer[3] = 0x03;
+
+    switch (*((uint32_t *)buffer)) 
+    {
+      case 0x00010203: 
+        return Endianness_Big;
+
+      case 0x03020100: 
+        return Endianness_Little;
+        
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  std::string Toolbox::WildcardToRegularExpression(const std::string& source)
+  {
+    // TODO - Speed up this with a regular expression
+
+    std::string result = source;
+
+    // Escape all special characters
+    boost::replace_all(result, "\\", "\\\\");
+    boost::replace_all(result, "^", "\\^");
+    boost::replace_all(result, ".", "\\.");
+    boost::replace_all(result, "$", "\\$");
+    boost::replace_all(result, "|", "\\|");
+    boost::replace_all(result, "(", "\\(");
+    boost::replace_all(result, ")", "\\)");
+    boost::replace_all(result, "[", "\\[");
+    boost::replace_all(result, "]", "\\]");
+    boost::replace_all(result, "+", "\\+");
+    boost::replace_all(result, "/", "\\/");
+    boost::replace_all(result, "{", "\\{");
+    boost::replace_all(result, "}", "\\}");
+
+    // Convert wildcards '*' and '?' to their regex equivalents
+    boost::replace_all(result, "?", ".");
+    boost::replace_all(result, "*", ".*");
+
+    return result;
+  }
+
+
+  void Toolbox::TokenizeString(std::vector<std::string>& result,
+                               const std::string& value,
+                               char separator)
+  {
+    result.clear();
+
+    std::string currentItem;
+
+    for (size_t i = 0; i < value.size(); i++)
+    {
+      if (value[i] == separator)
+      {
+        result.push_back(currentItem);
+        currentItem.clear();
+      }
+      else
+      {
+        currentItem.push_back(value[i]);
+      }
+    }
+
+    result.push_back(currentItem);
+  }
+
+
+#if ORTHANC_ENABLE_PUGIXML == 1
+  class ChunkedBufferWriter : public pugi::xml_writer
+  {
+  private:
+    ChunkedBuffer buffer_;
+
+  public:
+    virtual void write(const void *data, size_t size)
+    {
+      if (size > 0)
+      {
+        buffer_.AddChunk(reinterpret_cast<const char*>(data), size);
+      }
+    }
+
+    void Flatten(std::string& s)
+    {
+      buffer_.Flatten(s);
+    }
+  };
+
+
+  static void JsonToXmlInternal(pugi::xml_node& target,
+                                const Json::Value& source,
+                                const std::string& arrayElement)
+  {
+    // http://jsoncpp.sourceforge.net/value_8h_source.html#l00030
+
+    switch (source.type())
+    {
+      case Json::nullValue:
+      {
+        target.append_child(pugi::node_pcdata).set_value("null");
+        break;
+      }
+
+      case Json::intValue:
+      {
+        std::string s = boost::lexical_cast<std::string>(source.asInt());
+        target.append_child(pugi::node_pcdata).set_value(s.c_str());
+        break;
+      }
+
+      case Json::uintValue:
+      {
+        std::string s = boost::lexical_cast<std::string>(source.asUInt());
+        target.append_child(pugi::node_pcdata).set_value(s.c_str());
+        break;
+      }
+
+      case Json::realValue:
+      {
+        std::string s = boost::lexical_cast<std::string>(source.asFloat());
+        target.append_child(pugi::node_pcdata).set_value(s.c_str());
+        break;
+      }
+
+      case Json::stringValue:
+      {
+        target.append_child(pugi::node_pcdata).set_value(source.asString().c_str());
+        break;
+      }
+
+      case Json::booleanValue:
+      {
+        target.append_child(pugi::node_pcdata).set_value(source.asBool() ? "true" : "false");
+        break;
+      }
+
+      case Json::arrayValue:
+      {
+        for (Json::Value::ArrayIndex i = 0; i < source.size(); i++)
+        {
+          pugi::xml_node node = target.append_child();
+          node.set_name(arrayElement.c_str());
+          JsonToXmlInternal(node, source[i], arrayElement);
+        }
+        break;
+      }
+        
+      case Json::objectValue:
+      {
+        Json::Value::Members members = source.getMemberNames();
+
+        for (size_t i = 0; i < members.size(); i++)
+        {
+          pugi::xml_node node = target.append_child();
+          node.set_name(members[i].c_str());
+          JsonToXmlInternal(node, source[members[i]], arrayElement);          
+        }
+
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void Toolbox::JsonToXml(std::string& target,
+                          const Json::Value& source,
+                          const std::string& rootElement,
+                          const std::string& arrayElement)
+  {
+    pugi::xml_document doc;
+
+    pugi::xml_node n = doc.append_child(rootElement.c_str());
+    JsonToXmlInternal(n, source, arrayElement);
+
+    pugi::xml_node decl = doc.prepend_child(pugi::node_declaration);
+    decl.append_attribute("version").set_value("1.0");
+    decl.append_attribute("encoding").set_value("utf-8");
+
+    ChunkedBufferWriter writer;
+    doc.save(writer, "  ", pugi::format_default, pugi::encoding_utf8);
+    writer.Flatten(target);
+  }
+
+#endif
+
+
+  
+  bool Toolbox::IsInteger(const std::string& str)
+  {
+    std::string s = StripSpaces(str);
+
+    if (s.size() == 0)
+    {
+      return false;
+    }
+
+    size_t pos = 0;
+    if (s[0] == '-')
+    {
+      if (s.size() == 1)
+      {
+        return false;
+      }
+
+      pos = 1;
+    }
+
+    while (pos < s.size())
+    {
+      if (!isdigit(s[pos]))
+      {
+        return false;
+      }
+
+      pos++;
+    }
+
+    return true;
+  }
+
+
+  void Toolbox::CopyJsonWithoutComments(Json::Value& target,
+                                        const Json::Value& source)
+  {
+    switch (source.type())
+    {
+      case Json::nullValue:
+        target = Json::nullValue;
+        break;
+
+      case Json::intValue:
+        target = source.asInt64();
+        break;
+
+      case Json::uintValue:
+        target = source.asUInt64();
+        break;
+
+      case Json::realValue:
+        target = source.asDouble();
+        break;
+
+      case Json::stringValue:
+        target = source.asString();
+        break;
+
+      case Json::booleanValue:
+        target = source.asBool();
+        break;
+
+      case Json::arrayValue:
+      {
+        target = Json::arrayValue;
+        for (Json::Value::ArrayIndex i = 0; i < source.size(); i++)
+        {
+          Json::Value& item = target.append(Json::nullValue);
+          CopyJsonWithoutComments(item, source[i]);
+        }
+
+        break;
+      }
+
+      case Json::objectValue:
+      {
+        target = Json::objectValue;
+        Json::Value::Members members = source.getMemberNames();
+        for (Json::Value::ArrayIndex i = 0; i < members.size(); i++)
+        {
+          const std::string item = members[i];
+          CopyJsonWithoutComments(target[item], source[item]);
+        }
+
+        break;
+      }
+
+      default:
+        break;
+    }
+  }
+
+
+  bool Toolbox::StartsWith(const std::string& str,
+                           const std::string& prefix)
+  {
+    if (str.size() < prefix.size())
+    {
+      return false;
+    }
+    else
+    {
+      return str.compare(0, prefix.size(), prefix) == 0;
+    }
+  }
+  
+
+  static bool IsUnreservedCharacter(char c)
+  {
+    // This function checks whether "c" is an unserved character
+    // wrt. an URI percent-encoding
+    // https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding%5Fin%5Fa%5FURI
+
+    return ((c >= 'A' && c <= 'Z') ||
+            (c >= 'a' && c <= 'z') ||
+            (c >= '0' && c <= '9') ||
+            c == '-' ||
+            c == '_' ||
+            c == '.' ||
+            c == '~');
+  }
+
+  void Toolbox::UriEncode(std::string& target,
+                          const std::string& source)
+  {
+    // Estimate the length of the percent-encoded URI
+    size_t length = 0;
+
+    for (size_t i = 0; i < source.size(); i++)
+    {
+      if (IsUnreservedCharacter(source[i]))
+      {
+        length += 1;
+      }
+      else
+      {
+        // This character must be percent-encoded
+        length += 3;
+      }
+    }
+
+    target.clear();
+    target.reserve(length);
+
+    for (size_t i = 0; i < source.size(); i++)
+    {
+      if (IsUnreservedCharacter(source[i]))
+      {
+        target.push_back(source[i]);
+      }
+      else
+      {
+        // This character must be percent-encoded
+        uint8_t byte = static_cast<uint8_t>(source[i]);
+        uint8_t a = byte >> 4;
+        uint8_t b = byte & 0x0f;
+
+        target.push_back('%');
+        target.push_back(a < 10 ? a + '0' : a - 10 + 'A');
+        target.push_back(b < 10 ? b + '0' : b - 10 + 'A');
+      }
+    }
+  }
+
+
+  static bool HasField(const Json::Value& json,
+                       const std::string& key,
+                       Json::ValueType expectedType)
+  {
+    if (json.type() != Json::objectValue ||
+        !json.isMember(key))
+    {
+      return false;
+    }
+    else if (json[key].type() == expectedType)
+    {
+      return true;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+  }
+
+
+  std::string Toolbox::GetJsonStringField(const Json::Value& json,
+                                          const std::string& key,
+                                          const std::string& defaultValue)
+  {
+    if (HasField(json, key, Json::stringValue))
+    {
+      return json[key].asString();
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  bool Toolbox::GetJsonBooleanField(const ::Json::Value& json,
+                                    const std::string& key,
+                                    bool defaultValue)
+  {
+    if (HasField(json, key, Json::booleanValue))
+    {
+      return json[key].asBool();
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  int Toolbox::GetJsonIntegerField(const ::Json::Value& json,
+                                   const std::string& key,
+                                   int defaultValue)
+  {
+    if (HasField(json, key, Json::intValue))
+    {
+      return json[key].asInt();
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  unsigned int Toolbox::GetJsonUnsignedIntegerField(const ::Json::Value& json,
+                                                    const std::string& key,
+                                                    unsigned int defaultValue)
+  {
+    int v = GetJsonIntegerField(json, key, defaultValue);
+
+    if (v < 0)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return static_cast<unsigned int>(v);
+    }
+  }
+
+
+  bool Toolbox::IsUuid(const std::string& str)
+  {
+    if (str.size() != 36)
+    {
+      return false;
+    }
+
+    for (size_t i = 0; i < str.length(); i++)
+    {
+      if (i == 8 || i == 13 || i == 18 || i == 23)
+      {
+        if (str[i] != '-')
+          return false;
+      }
+      else
+      {
+        if (!isalnum(str[i]))
+          return false;
+      }
+    }
+
+    return true;
+  }
+
+
+  bool Toolbox::StartsWithUuid(const std::string& str)
+  {
+    if (str.size() < 36)
+    {
+      return false;
+    }
+
+    if (str.size() == 36)
+    {
+      return IsUuid(str);
+    }
+
+    assert(str.size() > 36);
+    if (!isspace(str[36]))
+    {
+      return false;
+    }
+
+    return IsUuid(str.substr(0, 36));
+  }
+
+
+#if ORTHANC_ENABLE_LOCALE == 1
+  static std::auto_ptr<std::locale>  globalLocale_;
+
+  static bool SetGlobalLocale(const char* locale)
+  {
+    globalLocale_.reset(NULL);
+
+    try
+    {
+      if (locale == NULL)
+      {
+        LOG(WARNING) << "Falling back to system-wide default locale";
+        globalLocale_.reset(new std::locale());
+      }
+      else
+      {
+        LOG(INFO) << "Using locale: \"" << locale << "\" for case-insensitive comparison of strings";
+        globalLocale_.reset(new std::locale(locale));
+      }
+    }
+    catch (std::runtime_error&)
+    {
+    }
+
+    return (globalLocale_.get() != NULL);
+  }
+  
+  void Toolbox::InitializeGlobalLocale(const char* locale)
+  {
+    // Make Orthanc use English, United States locale
+    // Linux: use "en_US.UTF-8"
+    // Windows: use ""
+    // Wine: use NULL
+    
+#if defined(__MINGW32__)
+    // Visibly, there is no support of locales in MinGW yet
+    // http://mingw.5.n7.nabble.com/How-to-use-std-locale-global-with-MinGW-correct-td33048.html
+    static const char* DEFAULT_LOCALE = NULL;
+#elif defined(_WIN32)
+    // For Windows: use default locale (using "en_US" does not work)
+    static const char* DEFAULT_LOCALE = "";
+#else
+    // For Linux & cie
+    static const char* DEFAULT_LOCALE = "en_US.UTF-8";
+#endif
+
+    bool ok;
+    
+    if (locale == NULL)
+    {
+      ok = SetGlobalLocale(DEFAULT_LOCALE);
+
+#if defined(__MINGW32__)
+      LOG(WARNING) << "This is a MinGW build, case-insensitive comparison of "
+                   << "strings with accents will not work outside of Wine";
+#endif
+    }
+    else
+    {
+      ok = SetGlobalLocale(locale);
+    }
+
+    if (!ok &&
+        !SetGlobalLocale(NULL))
+    {
+      LOG(ERROR) << "Cannot initialize global locale";
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+  }
+
+
+  void Toolbox::FinalizeGlobalLocale()
+  {
+    globalLocale_.reset();
+  }
+
+  
+  std::string Toolbox::ToUpperCaseWithAccents(const std::string& source)
+  {
+    if (globalLocale_.get() == NULL)
+    {
+      LOG(ERROR) << "No global locale was set, call Toolbox::InitializeGlobalLocale()";
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    /**
+     * A few notes about locales:
+     *
+     * (1) We don't use "case folding":
+     * http://www.boost.org/doc/libs/1_64_0/libs/locale/doc/html/conversions.html
+     *
+     * Characters are made uppercase one by one. This is because, in
+     * static builds, we are using iconv, which is visibly not
+     * supported correctly (TODO: Understand why). Case folding seems
+     * to be working correctly if using the default backend under
+     * Linux (ICU or POSIX?). If one wishes to use case folding, one
+     * would use:
+     *
+     *   boost::locale::generator gen;
+     *   std::locale::global(gen(DEFAULT_LOCALE));
+     *   return boost::locale::to_upper(source);
+     *
+     * (2) The function "boost::algorithm::to_upper_copy" does not
+     * make use of the "std::locale::global()". We therefore create a
+     * global variable "globalLocale_".
+     * 
+     * (3) The variant of "boost::algorithm::to_upper_copy()" that
+     * uses std::string does not work properly. We need to apply it
+     * one wide strings (std::wstring). This explains the two calls to
+     * "utf_to_utf" in order to convert to/from std::wstring.
+     **/
+
+    std::wstring w = boost::locale::conv::utf_to_utf<wchar_t>(source);
+    w = boost::algorithm::to_upper_copy<std::wstring>(w, *globalLocale_);
+    return boost::locale::conv::utf_to_utf<char>(w);
+  }
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Toolbox.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,218 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "Enumerations.h"
+
+#include <stdint.h>
+#include <vector>
+#include <string>
+#include <json/json.h>
+
+
+#if !defined(ORTHANC_ENABLE_BASE64)
+#  error The macro ORTHANC_ENABLE_BASE64 must be defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_LOCALE)
+#  error The macro ORTHANC_ENABLE_LOCALE must be defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_MD5)
+#  error The macro ORTHANC_ENABLE_MD5 must be defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_PUGIXML)
+#  error The macro ORTHANC_ENABLE_PUGIXML must be defined
+#endif
+
+
+/**
+ * NOTE: GUID vs. UUID
+ * The simple answer is: no difference, they are the same thing. Treat
+ * them as a 16 byte (128 bits) value that is used as a unique
+ * value. In Microsoft-speak they are called GUIDs, but call them
+ * UUIDs when not using Microsoft-speak.
+ * http://stackoverflow.com/questions/246930/is-there-any-difference-between-a-guid-and-a-uuid
+ **/
+
+
+
+namespace Orthanc
+{
+  typedef std::vector<std::string> UriComponents;
+
+  class NullType
+  {
+  };
+
+  namespace Toolbox
+  {
+    void ToUpperCase(std::string& s);  // Inplace version
+
+    void ToLowerCase(std::string& s);  // Inplace version
+
+    void ToUpperCase(std::string& result,
+                     const std::string& source);
+
+    void ToLowerCase(std::string& result,
+                     const std::string& source);
+
+    void SplitUriComponents(UriComponents& components,
+                            const std::string& uri);
+  
+    void TruncateUri(UriComponents& target,
+                     const UriComponents& source,
+                     size_t fromLevel);
+  
+    bool IsChildUri(const UriComponents& baseUri,
+                    const UriComponents& testedUri);
+
+    std::string AutodetectMimeType(const std::string& path);
+
+    std::string FlattenUri(const UriComponents& components,
+                           size_t fromLevel = 0);
+
+#if ORTHANC_ENABLE_MD5 == 1
+    void ComputeMD5(std::string& result,
+                    const std::string& data);
+
+    void ComputeMD5(std::string& result,
+                    const void* data,
+                    size_t size);
+#endif
+
+    void ComputeSHA1(std::string& result,
+                     const std::string& data);
+
+    void ComputeSHA1(std::string& result,
+                     const void* data,
+                     size_t size);
+
+    bool IsSHA1(const char* str,
+                size_t size);
+
+    bool IsSHA1(const std::string& s);
+
+#if ORTHANC_ENABLE_BASE64 == 1
+    void DecodeBase64(std::string& result, 
+                      const std::string& data);
+
+    void EncodeBase64(std::string& result, 
+                      const std::string& data);
+
+    bool DecodeDataUriScheme(std::string& mime,
+                             std::string& content,
+                             const std::string& source);
+
+    void EncodeDataUriScheme(std::string& result,
+                             const std::string& mime,
+                             const std::string& content);
+#endif
+
+#if ORTHANC_ENABLE_LOCALE == 1
+    std::string ConvertToUtf8(const std::string& source,
+                              Encoding sourceEncoding);
+
+    std::string ConvertFromUtf8(const std::string& source,
+                                Encoding targetEncoding);
+#endif
+
+    bool IsAsciiString(const void* data,
+                       size_t size);
+
+    bool IsAsciiString(const std::string& s);
+
+    std::string ConvertToAscii(const std::string& source);
+
+    std::string StripSpaces(const std::string& source);
+
+    // In-place percent-decoding for URL
+    void UrlDecode(std::string& s);
+
+    Endianness DetectEndianness();
+
+    std::string WildcardToRegularExpression(const std::string& s);
+
+    void TokenizeString(std::vector<std::string>& result,
+                        const std::string& source,
+                        char separator);
+
+#if ORTHANC_ENABLE_PUGIXML == 1
+    void JsonToXml(std::string& target,
+                   const Json::Value& source,
+                   const std::string& rootElement = "root",
+                   const std::string& arrayElement = "item");
+#endif
+
+    bool IsInteger(const std::string& str);
+
+    void CopyJsonWithoutComments(Json::Value& target,
+                                 const Json::Value& source);
+
+    bool StartsWith(const std::string& str,
+                    const std::string& prefix);
+
+    void UriEncode(std::string& target,
+                   const std::string& source);
+
+    std::string GetJsonStringField(const ::Json::Value& json,
+                                   const std::string& key,
+                                   const std::string& defaultValue);
+
+    bool GetJsonBooleanField(const ::Json::Value& json,
+                             const std::string& key,
+                             bool defaultValue);
+
+    int GetJsonIntegerField(const ::Json::Value& json,
+                            const std::string& key,
+                            int defaultValue);
+
+    unsigned int GetJsonUnsignedIntegerField(const ::Json::Value& json,
+                                             const std::string& key,
+                                             unsigned int defaultValue);
+
+    bool IsUuid(const std::string& str);
+
+    bool StartsWithUuid(const std::string& str);
+
+#if ORTHANC_ENABLE_LOCALE == 1
+    void InitializeGlobalLocale(const char* locale);
+
+    void FinalizeGlobalLocale();
+
+    std::string ToUpperCaseWithAccents(const std::string& source);
+#endif
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/WebServiceParameters.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,273 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "WebServiceParameters.h"
+
+#include "../Core/Logging.h"
+#include "../Core/OrthancException.h"
+
+#if ORTHANC_SANDBOXED == 0
+#  include "../Core/SystemToolbox.h"
+#endif
+
+#include <cassert>
+
+namespace Orthanc
+{
+  WebServiceParameters::WebServiceParameters() : 
+    advancedFormat_(false),
+    url_("http://127.0.0.1:8042/"),
+    pkcs11Enabled_(false)
+  {
+  }
+
+
+  void WebServiceParameters::ClearClientCertificate()
+  {
+    certificateFile_.clear();
+    certificateKeyFile_.clear();
+    certificateKeyPassword_.clear();
+  }
+
+
+#if ORTHANC_SANDBOXED == 0
+  void WebServiceParameters::SetClientCertificate(const std::string& certificateFile,
+                                                  const std::string& certificateKeyFile,
+                                                  const std::string& certificateKeyPassword)
+  {
+    if (certificateFile.empty())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    if (!SystemToolbox::IsRegularFile(certificateFile))
+    {
+      LOG(ERROR) << "Cannot open certificate file: " << certificateFile;
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+
+    if (!certificateKeyFile.empty() && 
+        !SystemToolbox::IsRegularFile(certificateKeyFile))
+    {
+      LOG(ERROR) << "Cannot open key file: " << certificateKeyFile;
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+
+    advancedFormat_ = true;
+    certificateFile_ = certificateFile;
+    certificateKeyFile_ = certificateKeyFile;
+    certificateKeyPassword_ = certificateKeyPassword;
+  }
+#endif
+
+
+  static void AddTrailingSlash(std::string& url)
+  {
+    if (url.size() != 0 && 
+        url[url.size() - 1] != '/')
+    {
+      url += '/';
+    }
+  }
+
+
+  void WebServiceParameters::FromJsonArray(const Json::Value& peer)
+  {
+    assert(peer.isArray());
+
+    advancedFormat_ = false;
+    pkcs11Enabled_ = false;
+
+    if (peer.size() != 1 && 
+        peer.size() != 3)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    std::string url = peer.get(0u, "").asString();
+    if (url.empty())
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    AddTrailingSlash(url);
+    SetUrl(url);
+
+    if (peer.size() == 1)
+    {
+      SetUsername("");
+      SetPassword("");
+    }
+    else if (peer.size() == 3)
+    {
+      SetUsername(peer.get(1u, "").asString());
+      SetPassword(peer.get(2u, "").asString());
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  static std::string GetStringMember(const Json::Value& peer,
+                                     const std::string& key,
+                                     const std::string& defaultValue)
+  {
+    if (!peer.isMember(key))
+    {
+      return defaultValue;
+    }
+    else if (peer[key].type() != Json::stringValue)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+    else
+    {
+      return peer[key].asString();
+    }
+  }
+
+
+  void WebServiceParameters::FromJsonObject(const Json::Value& peer)
+  {
+    assert(peer.isObject());
+    advancedFormat_ = true;
+
+    std::string url = GetStringMember(peer, "Url", "");
+    if (url.empty())
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+
+    AddTrailingSlash(url);
+    SetUrl(url);
+
+    SetUsername(GetStringMember(peer, "Username", ""));
+    SetPassword(GetStringMember(peer, "Password", ""));
+
+#if ORTHANC_SANDBOXED == 0
+    if (peer.isMember("CertificateFile"))
+    {
+      SetClientCertificate(GetStringMember(peer, "CertificateFile", ""),
+                           GetStringMember(peer, "CertificateKeyFile", ""),
+                           GetStringMember(peer, "CertificateKeyPassword", ""));
+    }
+#endif
+
+    if (peer.isMember("Pkcs11"))
+    {
+      if (peer["Pkcs11"].type() == Json::booleanValue)
+      {
+        pkcs11Enabled_ = peer["Pkcs11"].asBool();
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+    }
+  }
+
+
+  void WebServiceParameters::FromJson(const Json::Value& peer)
+  {
+    try
+    {
+      if (peer.isArray())
+      {
+        FromJsonArray(peer);
+      }
+      else if (peer.isObject())
+      {
+        FromJsonObject(peer);
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+    }
+    catch (OrthancException&)
+    {
+      throw;
+    }
+    catch (...)
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+
+
+  void WebServiceParameters::ToJson(Json::Value& value) const
+  {
+    if (advancedFormat_)
+    {
+      value = Json::objectValue;
+      value["Url"] = url_;
+
+      if (!username_.empty() ||
+          !password_.empty())
+      {
+        value["Username"] = username_;
+        value["Password"] = password_;
+      }
+
+      if (!certificateFile_.empty())
+      {
+        value["CertificateFile"] = certificateFile_;
+      }
+
+      if (!certificateKeyFile_.empty())
+      {
+        value["CertificateKeyFile"] = certificateKeyFile_;
+      }
+
+      if (!certificateKeyPassword_.empty())
+      {
+        value["CertificateKeyPassword"] = certificateKeyPassword_;
+      }
+    }
+    else
+    {
+      value = Json::arrayValue;
+      value.append(url_);
+
+      if (!username_.empty() ||
+          !password_.empty())
+      {
+        value.append(username_);
+        value.append(password_);
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/WebServiceParameters.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,131 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 !defined(ORTHANC_SANDBOXED)
+#  error The macro ORTHANC_SANDBOXED must be defined
+#endif
+
+#include <string>
+#include <json/json.h>
+
+namespace Orthanc
+{
+  class WebServiceParameters
+  {
+  private:
+    bool        advancedFormat_;
+    std::string url_;
+    std::string username_;
+    std::string password_;
+    std::string certificateFile_;
+    std::string certificateKeyFile_;
+    std::string certificateKeyPassword_;
+    bool        pkcs11Enabled_;
+
+    void FromJsonArray(const Json::Value& peer);
+
+    void FromJsonObject(const Json::Value& peer);
+
+  public:
+    WebServiceParameters();
+
+    const std::string& GetUrl() const
+    {
+      return url_;
+    }
+
+    void SetUrl(const std::string& url)
+    {
+      url_ = url;
+    }
+
+    const std::string& GetUsername() const
+    {
+      return username_;
+    }
+
+    void SetUsername(const std::string& username)
+    {
+      username_ = username;
+    }
+    
+    const std::string& GetPassword() const
+    {
+      return password_;
+    }
+
+    void SetPassword(const std::string& password)
+    {
+      password_ = password;
+    }
+
+    void ClearClientCertificate();
+
+#if ORTHANC_SANDBOXED == 0
+    void SetClientCertificate(const std::string& certificateFile,
+                              const std::string& certificateKeyFile,
+                              const std::string& certificateKeyPassword);
+#endif
+
+    const std::string& GetCertificateFile() const
+    {
+      return certificateFile_;
+    }
+
+    const std::string& GetCertificateKeyFile() const
+    {
+      return certificateKeyFile_;
+    }
+
+    const std::string& GetCertificateKeyPassword() const
+    {
+      return certificateKeyPassword_;
+    }
+
+    void SetPkcs11Enabled(bool pkcs11Enabled)
+    {
+      pkcs11Enabled_ = pkcs11Enabled;
+    }
+
+    bool IsPkcs11Enabled() const
+    {
+      return pkcs11Enabled_;
+    }
+
+    void FromJson(const Json::Value& peer);
+
+    void ToJson(Json::Value& value) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/NEWS	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,942 @@
+Pending changes in the mainline
+===============================
+
+REST API
+--------
+
+* "/system" URI returns the version of the Orthanc REST API
+* "/tools/now" returns the current UTC (universal) time
+* "/tools/now-local" returns the curent local time.
+  This was the behavior of "/tools/now" until release 1.3.1.
+* Added "?expand" GET argument to "/peers" and "/modalities" routes
+* New URI: "/tools/create-media-extended" to generate a DICOMDIR
+  archive from several resources, including additional type-3 tags
+
+Lua
+---
+
+* New CMake option: "-DENABLE_LUA_MODULES=ON" to enable support for
+  loading external Lua modules if the Lua engine is statically linked
+
+Plugins
+-------
+
+* New error code: DatabaseUnavailable
+
+Maintenance
+-----------
+
+* Orthanc now uses UTC (universal time) instead of local time in its database
+* Fix to allow creating DICOM instances with empty Specific Character Set (0008,0005)
+* Upgrade to curl 7.57.0 for static and Windows builds
+* Support of Linux Standard Base
+* Static linking against libuuid (from e2fsprogs)
+* Fix static build on CentOS 6
+* Upgrade to JsonCpp 1.8.4 for static builds
+* Possibility of using JsonCpp 0.10.6 if the compiler does not support C++11
+  with the "-DUSE_LEGACY_JSONCPP=ON" CMake option
+
+
+Version 1.3.1 (2017-11-29)
+==========================
+
+General
+-------
+
+* Built-in decoding of palette images
+
+REST API
+--------
+
+* New URI: "/instances/.../frames/.../raw.gz" to compress raw frames using gzip
+* New argument "ignore-length" to force the inclusion of too long tags in JSON
+* New argument "/.../media?extended" to include additional type-3 tags in DICOMDIR
+
+Plugins
+-------
+
+* New pixel formats exposed in SDK: BGRA32, Float32, Grayscale32, RGB48
+
+Maintenance
+-----------
+
+* Creation of ./Resources/CMake/OrthancFramework*.cmake to reuse the Orthanc
+  C++ framework in other projects
+* New security-related options: "DicomAlwaysAllowEcho"
+* Use "GBK" (frequently used in China) as an alias for "GB18030"
+* Experimental support of actively maintained Civetweb to replace Mongoose 3.8
+* Fix issue 31 for good (create new modality types for Philips ADW, GE Xeleris, GE AWServer)
+* Fix issue 64 (OpenBSD support)
+* Fix static compilation of DCMTK 3.6.2 on Fedora
+* Upgrade to Boost 1.65.1 in static builds
+* Upgrade to SQLite amalgamation 3.21.0 in static builds
+
+
+Version 1.3.0 (2017-07-19)
+==========================
+
+General
+-------
+
+* Orthanc now anonymizes according to Basic Profile of PS 3.15-2017c Table E.1-1
+* In the "DicomModalities" configuration:
+  - Manufacturer type MedInria is now obsolete
+  - Manufacturer types AgfaImpax and SyngoVia are obsolete too
+    (use GenericNoWildcardInDates instead)
+  - Obsolete manufacturers are still accepted but might disappear in the future
+  - Added new manufacturer: GenericNoUniversalWildcard to replace all '*' by '' in
+    outgoing C-Find requests
+* New security-related options: "DicomAlwaysAllowStore" and "DicomCheckModalityHost"
+
+REST API
+--------
+
+* Argument "Since" in URI "/tools/find" (related to issue 53)
+* Argument "DicomVersion" in URIs "/{...}/{...}/anonymization"
+
+Plugins
+-------
+
+* New function: "OrthancPluginRegisterIncomingHttpRequestFilter2()"
+
+Lua
+---
+
+* Added HTTP headers support for Lua HttpPost/HttpGet/HttpPut/HttpDelete
+
+Orthanc Explorer
+----------------
+
+* Query/retrieve: Added button for "DR" modality
+
+Maintenance
+-----------
+
+* Ability to retrieve raw frames encoded as unsigned 32-bits integers
+* Fix issue 29 (more consistent handling of the "--upgrade" argument)
+* Fix issue 31 (create new modality types for Philips ADW, GE Xeleris, GE AWServer)
+* Fix issue 35 (AET name is not transferred to Orthanc using DCMTK 3.6.0)
+* Fix issue 44 (bad interpretation of photometric interpretation MONOCHROME1)
+* Fix issue 45 (crash when providing a folder to "--config" command-line option)
+* Fix issue 46 (PHI remaining after anonymization)
+* Fix issue 49 (worklists: accentuated characters are removed from C-Find responses)
+* Fix issue 52 (DICOM level security association problems)
+* Fix issue 55 (modification/anonymization of tags that might break the database
+  model now requires the "Force" parameter to be set to "true" in the query)
+* Fix issue 56 (case-insensitive matching over accents)
+* Fix Debian #865606 (orthanc FTBFS with libdcmtk-dev 3.6.1~20170228-2)
+* Fix XSS inside DICOM in Orthanc Explorer (as reported by Victor Pasnkel, Morphus Labs)
+* Upgrade to DCMTK 3.6.2 in static builds (released on 2017-07-17)
+* Upgrade to Boost 1.64.0 in static builds
+* New advanced "Locale" configuration option
+* Removed configuration option "USE_DCMTK_361_PRIVATE_DIC"
+
+
+Version 1.2.0 (2016/12/13)
+==========================
+
+General
+-------
+
+* Handling of private tags/creators in the "Dictionary" configuration option
+* New configuration options: "LoadPrivateDictionary", "DicomScuTimeout" and "DicomScpTimeout"
+* New metadata automatically computed at the instance level: "TransferSyntax" and "SopClassUid"
+
+REST API
+--------
+
+* "/tools/invalidate-tags" to invalidate the JSON summary of all the DICOM files
+  (useful if private tags are registered, or if changing the default encoding)
+* "Permissive" flag for URI "/modalities/{...}/store" to ignore C-STORE transfer errors
+* "Asynchronous" flag for URIs "/modalities/{...}/store" and "/peers/{...}/store"
+  to avoid waiting for the completion of image transfers
+* Possibility to DELETE "dicom-as-json" attachments to reconstruct the JSON summaries
+  (useful if "Dictionary" has changed)
+* "Keep" option for modifications to keep original DICOM identifiers (advanced feature)
+* "/tools/default-encoding" to get or temporarily change the default encoding
+* "/{resource}/{id}/reconstruct" to reconstruct the main DICOM tags, the JSON summary and
+  the metadata of a resource (useful to compute new metadata, or if using "Keep" above)
+
+Plugins
+-------
+
+* New function: "OrthancPluginRegisterPrivateDictionaryTag()" to register private tags
+* More control over client cache in the ServeFolders plugin
+* New C++ help wrappers in "Plugins/Samples/Common/" to read DICOM datasets from REST API
+* New data structure: "OrthancPluginFindMatcher" to match DICOM against C-FIND queries
+
+Maintenance
+-----------
+
+* Fix handling of encodings in C-FIND requests (including for worklists)
+* Use of DCMTK 3.6.1 dictionary of private tags in standalone builds
+* Avoid hard crash if not enough memory (handling of std::bad_alloc)
+* Improved robustness of Orthanc Explorer wrt. query/retrieve (maybe fix issue 24)
+* Fix serious performance issue with C-FIND
+* Fix extraction of the symbolic name of the private tags
+* Performance warning if runtime debug assertions are turned on
+* Improved robustness against files with no PatientID
+* Upgrade to curl 7.50.3 for static and Windows builds
+* Content-Type for JSON documents is now "application/json; charset=utf-8"
+* Ignore "Group Length" tags in C-FIND queries
+* Fix handling of worklist SCP with ReferencedStudySequence and ReferencedPatientSequence
+* Fix handling of Move Originator AET and ID in C-MOVE SCP
+* Fix vulnerability ZSL-2016-5379 "Unquoted Service Path Privilege Escalation" in the
+  Windows service
+* Fix vulnerability ZSL-2016-5380 "Remote Memory Corruption Vulnerability" in DCMTK 3.6.0
+
+
+Version 1.1.0 (2016/06/27)
+==========================
+
+General
+-------
+
+* HTTPS client certificates can be associated with Orthanc peers to enhance security over Internet
+* Possibility to use PKCS#11 authentication for hardware security modules with Orthanc peers
+* New command-line option "--logfile" to output the Orthanc log to the given file
+* Support of SIGHUP signal (restart Orthanc only if the configuration files have changed)
+
+REST API
+--------
+
+* New URI: "/instances/.../frames/.../raw" to access the raw frames (bypass image decoding)
+* New URI "/modalities/.../move" to issue C-MOVE SCU requests
+* "MoveOriginatorID" can be specified for "/modalities/.../store"
+
+Dicom protocol
+--------------
+
+* Support of optional tags for counting resources in C-FIND:
+  0008-0061, 0008-0062, 0020-1200, 0020-1202, 0020-1204, 0020-1206, 0020-1208, 0020-1209
+* Support of Move Originator Message ID (0000,1031) in C-STORE responses driven by C-MOVE
+
+Plugins
+-------
+
+* Speedup in plugins by removing unnecessary locks
+* New callback to filter incoming HTTP requests: OrthancPluginRegisterIncomingHttpRequestFilter()
+* New callback to handle non-worklists C-FIND requests: OrthancPluginRegisterFindCallback()
+* New callback to handle C-MOVE requests: OrthancPluginRegisterMoveCallback()
+* New function: "OrthancPluginHttpClient()" to do HTTP requests with full control
+* New function: "OrthancPluginGenerateUuid()" to generate a UUID
+* More than one custom image decoder can be installed (e.g. to handle different transfer syntaxes)
+
+Lua
+---
+
+* Possibility to dynamically fix outgoing C-FIND requests using "OutgoingFindRequestFilter()"
+* Access to the HTTP headers in the "IncomingHttpRequestFilter()" callback
+
+Image decoding
+--------------
+
+* Huge speedup if decoding the family of JPEG transfer syntaxes
+* Refactoring leading to speedups with custom image decoders (including Web viewer plugin)
+* Support decoding of RLE Lossless transfer syntax
+* Support of signed 16bpp images in ParsedDicomFile
+
+Maintenance
+-----------
+
+* New logo of Orthanc
+* Fix issue 11 (is_regular_file() fails for FILE_ATTRIBUTE_REPARSE_POINT)
+* Fix issue 16 ("Limit" parameter error in REST API /tools/find method)
+* Fix of Debian bug #818512 ("FTBFS: Please install libdcmtk*-dev")
+* Fix of Debian bug #823139 ("orthanc: Please provide RecoverCompressedFile.cpp")
+* Replaced "localhost" by "127.0.0.1", as it might impact performance on Windows
+* Compatibility with CMake >= 3.5.0
+* Possibility to use forthcoming DCMTK 3.6.1 in static builds (instead of 3.6.0)
+* Upgrade to Boost 1.60.0 for static builds
+* Use of HTTP status 403 Forbidden (instead of 401) if access to a REST resource is disallowed
+* Option "HttpsVerifyPeers" can be used to connect against self-signed HTTPS certificates
+* New configuration option "AllowFindSopClassesInStudy"
+* Macro "__linux" (now obsolete) replaced by macro "__linux__" (maybe solves Debian bug #821011)
+* Modification of instances can now replace PixelData (resp. EncapsulatedDocument) with 
+  provided a PNG/JPEG image (resp. PDF file) if it is encoded using Data URI Scheme
+* Dropped support of Google Log
+
+
+Version 1.0.0 (2015/12/15)
+==========================
+
+* Lua: "IncomingFindRequestFilter()" to apply filters to incoming C-FIND requests
+* New function in plugin SDK: "OrthancPluginSendMultipartItem2()"
+* Fix of DICOMDIR generation with DCMTK 3.6.1, support of encodings
+* Fix range search if the lower or upper limit is absent
+* Fix modality worklists lookups if tags with UN (unknown) VR are present
+* Warn about badly formatted modality/peer definitions in configuration file at startup
+
+
+Version 0.9.6 (2015/12/08)
+==========================
+
+* Promiscuous mode (accept unknown SOP class UID) is now turned off by default
+* Fix serialization of DICOM buffers that might contain garbage trailing
+* Fix modality worklists server if some fields are null
+* More tolerant "/series/.../ordered-slices" with broken series
+* Improved logging information if upgrade fails
+* Fix formatting of multipart HTTP answers (bis)
+
+
+Version 0.9.5 (2015/12/02)
+==========================
+
+Major
+-----
+
+* Experimental support of DICOM C-FIND SCP for modality worklists through plugins
+* Support of DICOM C-FIND SCU for modality worklists ("/modalities/{dicom}/find-worklist")
+
+REST API
+--------
+
+* New URIs:
+  - "/series/.../ordered-slices" to order the slices of a 2D+t or 3D series
+  - "/tools/shutdown" to stop Orthanc from the REST API
+  - ".../compress", ".../uncompress" and ".../is-compressed" for attachments
+  - "/tools/create-archive" to create ZIP from a set of resources
+  - "/tools/create-media" to create ZIP+DICOMDIR from a set of resources
+  - "/instances/.../header" to get the meta information (header) of the DICOM instance
+* "/tools/create-dicom":
+  - Support of binary tags encoded using data URI scheme
+  - Support of hierarchical structures (creation of sequences)
+  - Create tags with unknown VR
+* "/modify" can insert/modify sequences
+* ".../preview" and ".../image-uint8" can return JPEG images if the HTTP Accept Header asks so
+* "Origin" metadata for the instances
+
+Minor
+-----
+
+* New configuration options:
+  - "UnknownSopClassAccepted" to disable promiscuous mode (accept unknown SOP class UID)
+  - New configuration option: "Dictionary" to declare custom DICOM tags
+* Add ".dcm" suffix to files in ZIP archives (cf. URI ".../archive")
+* MIME content type can be associated to custom attachments (cf. "UserContentType")
+
+Plugins
+-------
+
+* New functions:
+  - "OrthancPluginRegisterDecodeImageCallback()" to replace the built-in image decoder
+  - "OrthancPluginDicomInstanceToJson()" to convert DICOM to JSON
+  - "OrthancPluginDicomBufferToJson()" to convert DICOM to JSON
+  - "OrthancPluginRegisterErrorCode()" to declare custom error codes
+  - "OrthancPluginRegisterDictionaryTag()" to declare custom DICOM tags
+  - "OrthancPluginLookupDictionary()" to get information about some DICOM tag
+  - "OrthancPluginRestApiGet2()" to provide HTTP headers when calling Orthanc API
+  - "OrthancPluginGetInstanceOrigin()" to know through which mechanism an instance was received
+  - "OrthancPluginCreateImage()" and "OrthancPluginCreateImageAccessor()" to create images
+  - "OrthancPluginDecodeDicomImage()" to decode DICOM images
+  - "OrthancPluginComputeMd5()" and "OrthancPluginComputeSha1()" to compute MD5/SHA-1 hash
+* New events in change callbacks:
+  - "OrthancStarted"
+  - "OrthancStopped"
+  - "UpdatedAttachment" 
+  - "UpdatedMetadata"
+* "/system" URI gives information about the plugins used for storage area and DB back-end
+* Plugin callbacks must now return explicit "OrthancPluginErrorCode" (instead of integers)
+
+Lua
+---
+
+* Optional argument "keepStrings" in "DumpJson()"
+
+Maintenance
+-----------
+
+* Full indexation of the patient/study tags to speed up searches and C-FIND
+* Many refactorings, notably of the searching features and of the image decoding
+* C-MOVE SCP for studies using AccessionNumber tag
+* Fix issue 4 (C-STORE Association not renegotiated on Specific-to-specific transfer syntax change)
+* Fix formatting of multipart HTTP answers
+* "--logdir" flag creates a single log file instead of 3 separate files for errors/warnings/infos
+* "--errors" flag lists the error codes that could be returned by Orthanc
+* Under Windows, the exit status of Orthanc corresponds to the encountered error code
+* New "AgfaImpax", "EFilm2" and "Vitrea" modality manufacturers
+* C-FIND SCP will return tags with sequence value representation
+* Upgrade to Boost 1.59.0 for static builds
+
+
+Version 0.9.4 (2015/09/16)
+==========================
+
+* Preview of PDF files encapsulated in DICOM from Orthanc Explorer
+* Creation of DICOM files with encapsulated PDF through "/tools/create-dicom"
+* "limit" and "since" arguments while retrieving DICOM resources in the REST API
+* Support of "deflate" and "gzip" content-types in HTTP requests
+* Options to validate peers against CA certificates in HTTPS requests
+* New configuration option: "HttpTimeout" to set the default timeout for HTTP requests
+
+Lua
+---
+
+* More information about the origin request in the "OnStoredInstance()" and
+  "ReceivedInstanceFilter()" callbacks. WARNING: This can result in
+  incompatibilities wrt. previous versions of Orthanc.
+* New function "GetOrthancConfiguration()" to get the Orthanc configuration
+
+Plugins
+-------
+
+* New functions to compress/uncompress images using PNG and JPEG
+* New functions to issue HTTP requests from plugins
+* New function "OrthancPluginBufferCompression()" to (un)compress memory buffers
+* New function "OrthancPluginReadFile()" to read files from the filesystem
+* New function "OrthancPluginWriteFile()" to write files to the filesystem
+* New function "OrthancPluginGetErrorDescription()" to convert error codes to strings
+* New function "OrthancPluginSendHttpStatus()" to send HTTP status with a body
+* New function "OrthancPluginRegisterRestCallbackNoLock()" for high-performance plugins
+* Plugins have access to explicit error codes 
+* Improvements to the sample "ServeFolders" plugin
+* Primitives to upgrade the database version in plugins
+
+Maintenance
+-----------
+
+* Many code refactorings
+* Improved error codes (no more custom descriptions in exceptions)
+* If error while calling the REST API, the answer body contains description of the error
+  (this feature can be disabled with the "HttpDescribeErrors" option)
+* Upgrade to curl 7.44.0 for static and Windows builds
+* Upgrade to libcurl 1.0.2d for static and Windows builds
+* Depends on libjpeg 9a
+* Bypass zlib uncompression if "StorageCompression" is enabled and HTTP client supports deflate
+
+
+Version 0.9.3 (2015/08/07)
+==========================
+
+* C-Echo testing can be triggered from Orthanc Explorer (in the query/retrieve page)
+* Removal of the dependency upon Google Log, Orthanc now uses its internal logger 
+  (use -DENABLE_GOOGLE_LOG=ON to re-enable Google Log)
+* Upgrade to JsonCpp 0.10.5 for static and Windows builds
+
+
+Version 0.9.2 (2015/08/02)
+==========================
+
+* Upgrade to Boost 1.58.0 for static and Windows builds
+* Source code repository moved from Google Code to BitBucket
+* Inject version information into Windows binaries
+* Fix access to binary data in HTTP/REST requests by Lua scripts
+* Fix potential deadlock in the callbacks of plugins
+
+
+Version 0.9.1 (2015/07/02)
+==========================
+
+General
+-------
+
+* The configuration can be splitted into several files stored inside the same folder
+* Custom setting of the local AET during C-STORE SCU (both in Lua and in the REST API)
+* Many code refactorings
+
+Lua
+---
+
+* Access to the REST API of Orthanc (RestApiGet, RestApiPost, RestApiPut, RestApiDelete)
+* Functions to convert between Lua values and JSON strings: "ParseJson" and "DumpJson"
+* New events: "OnStablePatient", "OnStableStudy", "OnStableSeries", "Initialize", "Finalize"
+
+Plugins
+-------
+
+* Plugins can retrieve the configuration file directly as a JSON string
+* Plugins can send answers as multipart messages
+
+Fixes
+-----
+
+* Fix compatibility issues for C-FIND SCU to Siemens Syngo.Via modalities SCP
+* Fix issue 15 (Lua scripts making HTTP requests)
+* Fix issue 35 (Characters in PatientID string are not protected for C-FIND)
+* Fix issue 37 (Hyphens trigger range query even if datatype does not support ranges)
+
+
+Version 0.9.0 (2015/06/03)
+==========================
+
+Major
+-----
+
+* DICOM Query/Retrieve available from Orthanc Explorer
+* C-MOVE SCU and C-FIND SCU are accessible through the REST API
+* "?expand" flag for URIs "/patients", "/studies" and "/series"
+* "/tools/find" URI to search for DICOM resources from REST
+* Support of FreeBSD
+* The "Orthanc Client" SDK is now a separate project
+
+Minor
+-----
+
+* Speed-up in Orthanc Explorer for large amount of images
+* Speed-up of the C-FIND SCP server of Orthanc
+* Allow replacing PatientID/StudyInstanceUID/SeriesInstanceUID from Lua scripts
+* Option "CaseSensitivePN" to enable case-insensitive C-FIND SCP
+
+Fixes
+-----
+
+* Prevent freeze on C-FIND if no DICOM tag is to be returned
+* Fix slow C-STORE SCP on recent versions of GNU/Linux, if
+  USE_SYSTEM_DCMTK is set to OFF (http://forum.dcmtk.org/viewtopic.php?f=1&t=4009)
+* Fix issue 30 (QR response missing "Query/Retrieve Level" (008,0052))
+* Fix issue 32 (Cyrillic symbols): Introduction of the "Windows1251" encoding
+* Plugins now receive duplicated GET arguments in their REST callbacks
+
+
+Version 0.8.6 (2015/02/12)
+==========================
+
+Major
+-----
+
+* URIs to get all the parents of a given resource in a single REST call
+* Instances without PatientID are now allowed
+* Support of HTTP proxy to access Orthanc peers
+
+Minor
+-----
+
+* Support of Tudor DICOM in Query/Retrieve
+* More flexible "/modify" and "/anonymize" for single instance
+* Access to called AET and remote AET from Lua scripts ("OnStoredInstance")
+* Option "DicomAssociationCloseDelay" to set delay before closing DICOM association
+* ZIP archives now display the accession number of the studies
+
+Plugins
+-------
+
+* Introspection of plugins (cf. the "/plugins" URI)
+* Plugins can access the command-line arguments used to launch Orthanc
+* Plugins can extend Orthanc Explorer with custom JavaScript
+* Plugins can get/set global properties to save their configuration
+* Plugins can do REST calls to other plugins (cf. "xxxAfterPlugins()")
+* Scan of folders for plugins
+
+Fixes
+-----
+
+* Code refactorings
+* Fix issue 25 (AET with underscore not allowed)
+* Fix replacement and insertion of private DICOM tags
+* Fix anonymization generating non-portable DICOM files
+
+
+Version 0.8.5 (2014/11/04)
+==========================
+
+General
+-------
+
+* Major speed-up thanks to a new database schema
+* Plugins can monitor changes through callbacks
+* Download ZIP + DICOMDIR from Orthanc Explorer
+* Sample plugin framework to serve static resources (./Plugins/Samples/WebSkeleton/)
+
+Fixes
+-----
+
+* Fix issue 19 (YBR_FULL are decoded incorrectly)
+* Fix issue 21 (Microsoft Visual Studio precompiled headers)
+* Fix issue 22 (Error decoding multi-frame instances)
+* Fix issue 24 (Build fails on OSX when directory has .DS_Store files)
+* Fix crash when bad HTTP credentials are provided
+
+
+Version 0.8.4 (2014/09/19)
+==========================
+
+* "/instances-tags" to get the tags of all the child instances of a
+  patient/study/series with a single REST call (bulk tags retrieval)
+* Configuration/Lua to select the accepted C-STORE SCP transfer syntaxes
+* Fix reporting of errors in Orthanc Explorer when sending images to peers/modalities
+* Installation of plugin SDK in CMake
+
+
+Version 0.8.3 (2014/09/11)
+==========================
+
+Major
+-----
+
+* Creation of ZIP archives for media storage, with DICOMDIR
+* URIs to get all the children of a given resource in a single REST call
+* "/tools/lookup" URI to map DICOM UIDs to Orthanc identifiers
+* Support of index-only mode (using the "StoreDicom" option)
+* Plugins can implement a custom storage area
+
+Minor
+-----
+
+* Configuration option to enable HTTP Keep-Alive
+* Configuration option to disable the logging of exported resources in "/exports"
+* Plugins can retrieve the path to Orthanc and to its configuration file
+* "/tools/create-dicom" now accepts the "PatientID" DICOM tag (+ updated sample)
+* Possibility to set HTTP headers from plugins
+* "LastUpdate" metadata is now always returned for patients, studies and series
+
+Maintenance
+-----------
+
+* Refactoring of HttpOutput ("Content-Length" header is now always sent)
+* Upgrade to Mongoose 3.8
+* Fixes for Visual Studio 2013 and Windows 64bit
+* Fix issue 16: Handling of "AT" value representations in JSON
+* Fix issue 17
+
+
+Version 0.8.2 (2014/08/07)
+==========================
+
+* Support of the standard text encodings
+* Hot restart of Orthanc by posting to "/tools/reset"
+* More fault-tolerant commands in Lua scripts
+* Parameter to set the default encoding for DICOM files without SpecificCharacterSet
+* Fix of issue #14 (support of XCode 5.1)
+* Upgrade to Google Test 1.7.0
+
+
+Version 0.8.1 (2014/07/29)
+==========================
+
+General
+-------
+
+* Access patient module at the study level to cope with PatientID collisions
+* On-the-fly conversion of JSON to XML according to the HTTP Accept header
+* C-Echo SCU in the REST API
+* DICOM conformance statement available at URI "/tools/dicom-conformance"
+
+Lua scripts
+-----------
+
+* Lua scripts can do HTTP requests, and thus can call Web services
+* Lua scripts can invoke system commands, with CallSystem()
+
+Plugins
+-------
+
+* Lookup for DICOM UIDs in the plugin SDK
+* Plugins have access to the HTTP headers and can answer with HTTP status codes
+* Callback to react to the incoming of DICOM instances
+
+Fixes
+-----
+
+* Fix build of Google Log with Visual Studio >= 11.0
+* Fix automated generation of the list of resource children in the REST API
+
+
+Version 0.8.0 (2014/07/10)
+==========================
+
+Major changes
+-------------
+
+* Routing images with Lua scripts
+* Introduction of the Orthanc Plugin SDK
+* Official support of OS X (Darwin) 10.8
+
+Minor changes
+-------------
+
+* Extraction of tags for the patient/study/series/instance DICOM modules
+* Extraction of the tags shared by all the instances of a patient/study/series
+* Options to limit the number of results for an incoming C-FIND query
+* Support of kFreeBSD
+* Several code refactorings
+* Fix OrthancCppClient::GetVoxelSizeZ()
+
+
+Version 0.7.6 (2014/06/11)
+==========================
+
+* Support of JPEG and JPEG-LS decompression
+* Download DICOM images as Matlab/Octave arrays
+* Precompiled headers for Microsoft Visual Studio
+
+
+Version 0.7.5 (2014/05/08)
+==========================
+
+* Dynamic negotiation of SOP classes for C-STORE SCU
+* Creation of DICOM instances using the REST API
+* Embedding of images within DICOM instances
+* Adding/removal/modification of remote modalities/peers through REST
+* Reuse of the previous SCU connection to avoid unnecessary handshakes
+* Fix problems with anonymization and modification
+* Fix missing licensing terms about reuse of some code from DCMTK
+* Various code refactorings
+
+
+Version 0.7.4 (2014/04/16)
+==========================
+
+* Switch to openssl-1.0.1g in static builds (cf. Heartbleed exploit)
+* Switch to boost 1.55.0 in static builds (to solve compiling errors)
+* Better logging about nonexistent tags
+* Dcm4Chee manufacturer
+* Automatic discovering of the path to the DICOM dictionaries
+* In the "DicomModalities" config, the port number can be a string
+
+
+Version 0.7.3 (2014/02/14)
+==========================
+
+Major changes
+-------------
+
+* Fixes in the implementation of the C-FIND handler for Query/Retrieve
+* Custom attachment of files to patients, studies, series or instances
+* Access to lowlevel info about the attached files through the REST API
+* Recover pixel data for more transfer syntaxes (notably JPEG)
+
+Minor changes
+-------------
+
+* AET comparison is now case-insensitive by default
+* Possibility to disable the HTTP server or the DICOM server
+* Automatic computation of MD5 hashes for the stored DICOM files
+* Maintenance tool to recover DICOM files compressed by Orthanc
+* The newline characters in the configuration file are fixed for GNU/Linux
+* Capture of the SIGTERM signal in GNU/Linux
+
+
+Version 0.7.2 (2013/11/08)
+==========================
+
+* Support of Query/Retrieve from medInria
+* Accept more transfer syntaxes for C-STORE SCP and SCU (notably JPEG)
+* Create the meta-header when receiving files through C-STORE SCP
+* Fixes and improvements thanks to the static analyzer cppcheck
+
+
+Version 0.7.1 (2013/10/30)
+==========================
+
+* Use ZIP64 only when required to improve compatibility (cf. issue #7)
+* Refactoring of the CMake options
+* Fix for big-endian architectures (RedHat bug #985748)
+* Use filenames with 8 characters in ZIP files for maximum compatibility
+* Possibility to build Orthanc inplace (in the source directory)
+
+
+Version 0.7.0 (2013/10/25)
+==========================
+
+Major changes
+-------------
+
+* DICOM Query/Retrieve is supported
+
+Minor changes
+-------------
+
+* Possibility to keep the PatientID during an anonymization
+* Check whether "unzip", "tar" and/or "7-zip" are installed from CMake
+
+
+Version 0.6.2 (2013/10/04)
+==========================
+
+* Build of the C++ client as a shared library
+* Improvements and documentation of the C++ client API
+* Fix of Debian bug #724947 (licensing issue with the SHA-1 library)
+* Switch to Boost 1.54.0 (cf. issue #9)
+* "make uninstall" is now possible
+
+
+Version 0.6.1 (2013/09/16)
+==========================
+
+* Detection of stable patients/studies/series
+* C-FIND SCU at the instance level
+* Link from modified to original resource in Orthanc Explorer
+* Fix of issue #8
+* Anonymization of the medical alerts tag (0010,2000)
+
+
+Version 0.6.0 (2013/07/16)
+==========================
+
+Major changes
+-------------
+
+* Introduction of the C++ client
+* Send DICOM resources to other Orthanc instances through HTTP
+* Access to signed images (instances/.../image-int16)
+  (Closes: Debian #716958)
+
+Minor changes
+-------------
+
+* Export of DICOM files to the host filesystem (instances/.../export)
+* Statistics about patients, studies, series and instances
+* Link from anonymized to original resource in Orthanc Explorer
+* Fixes for Red Hat and Debian packaging
+* Fixes for history in Orthanc Explorer
+* Fixes for boost::thread, as reported by Cyril Paulus
+* Fix licensing (Closes: Debian #712038)
+
+Metadata
+--------
+
+* Access to the metadata through the REST API (.../metadata)
+* Support of user-defined metadata
+* "LastUpdate" metadata for patients, studies and series
+* "/tools/now" to be used in combination with "LastUpdate"
+* Improved support of series with temporal positions
+
+
+Version 0.5.2 (2013/05/07)
+==========================
+
+* "Bulk" Store-SCU (send several DICOM instances with the same
+  DICOM connection)
+* Store-SCU for patients and studies in Orthanc Explorer
+* Filtering of incoming DICOM instances (through Lua scripting)
+* Filtering of incoming HTTP requests (through Lua scripting)
+* Clearing of "/exports" and "/changes"
+* Check MD5 of third party downloads
+* Faking of the HTTP methods PUT and DELETE
+
+
+Version 0.5.1 (2013/04/17)
+==========================
+
+* Support of RGB images
+* Fix of store SCU in release builds
+* Possibility to store the SQLite index at another place than the
+  DICOM instances (for performance)
+
+
+Version 0.5.0 (2013/01/31)
+==========================
+
+Major changes
+-------------
+
+* Download of modified or anonymized DICOM instances
+* Inplace modification and anonymization of DICOM series, studies and patients
+
+Minor changes
+-------------
+
+* Support of private tags
+* Implementation of the PMSCT_RLE1 image decoding for Philips modalities
+* Generation of random DICOM UID through the REST API (/tools/generate-uid)
+
+
+Version 0.4.0 (2012/12/14)
+==========================
+
+Major changes
+-------------
+
+* Recycling of disk space
+* Raw access to the value of the DICOM tags in the REST API
+
+Minor changes
+-------------
+
+* Protection of patients against recycling (also in Orthanc Explorer)
+* The DICOM dictionaries are embedded in Windows builds
+
+
+Version 0.3.1 (2012/12/05)
+==========================
+
+* Download archives of patients, studies and series as ZIP files
+* Orthanc now checks the version of its database schema before starting
+
+
+Version 0.3.0 (2012/11/30)
+==========================
+
+Major changes
+-------------
+
+* Transparent compression of the DICOM instances on the disk
+* The patient/study/series/instances are now indexed by SHA-1 digests
+  of their DICOM Instance IDs (and not by UUIDs anymore): The same
+  DICOM objects are thus always identified by the same Orthanc IDs
+* Log of exported instances through DICOM C-STORE SCU ("/exported" URI)
+* Full refactoring of the DB schema and of the REST API
+* Introduction of generic classes for REST APIs (in Core/RestApi)
+
+Minor changes
+-------------
+
+* "/statistics" URI
+* "last" flag to retrieve the last change from the "/changes" URI
+* Generate a sample configuration file from command line
+* "CompletedSeries" event in the changes API
+* Thread to continuously flush DB to disk (SQLite checkpoints for
+  improved robustness)
+
+
+Version 0.2.3 (2012/10/26)
+==========================
+
+* Use HTTP Content-Disposition to set a filename when downloading JSON/DCM
+* URI "/system" for general information about Orthanc
+* Versioning info and help on the command line
+* Improved logging
+* Possibility of dynamic linking against jsoncpp, sqlite, boost and dmctk
+  for Debian packaging
+* Fix some bugs
+* Switch to default 8042 port for HTTP
+
+
+Version 0.2.2 (2012/10/04)
+==========================
+
+* Switch to Google Log
+* Fixes to Debian packaging
+
+
+Version 0.2.1 (2012/09/28)
+==========================
+
+* Status of series
+* Continuous Integration Server is up and running
+* Ready for Debian packaging
+
+
+Version 0.2.0 (2012/09/16)
+==========================
+
+Major changes
+-------------
+
+* Renaming to "Orthanc"
+* Focus on security: Support of SSL, HTTP Basic Authentication and
+  interdiction of remote access
+* Access to multi-frame images (for nuclear medicine)
+* Access to the raw PNG images (in 8bpp and 16bpp)
+
+Minor changes
+-------------
+
+* Change of the licensing of the "Core/SQLite" folder to BSD (to
+  reflect the original licensing terms of Chromium, from which the
+  code derives)
+* Standalone build for cross-compilation
+
+
+Version 0.1.1 (2012/07/20)
+==========================
+
+* Fix Windows version
+* Native Windows build with Microsoft Visual Studio 2005
+* Add path to storage in Configuration.json
+
+
+Version 0.1.0 (2012/07/19)
+==========================
+
+* Initial release
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,173 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomDatasetReader.h"
+
+#include "OrthancPluginException.h"
+
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancPlugins
+{
+  // This function is copied-pasted from "../../../Core/Toolbox.cpp",
+  // in order to avoid the dependency of plugins against the Orthanc core
+  static std::string StripSpaces(const std::string& source)
+  {
+    size_t first = 0;
+
+    while (first < source.length() &&
+           isspace(source[first]))
+    {
+      first++;
+    }
+
+    if (first == source.length())
+    {
+      // String containing only spaces
+      return "";
+    }
+
+    size_t last = source.length();
+    while (last > first &&
+           isspace(source[last - 1]))
+    {
+      last--;
+    }          
+    
+    assert(first <= last);
+    return source.substr(first, last - first);
+  }
+
+
+  DicomDatasetReader::DicomDatasetReader(const IDicomDataset& dataset) :
+    dataset_(dataset)
+  {
+  }
+  
+
+  std::string DicomDatasetReader::GetStringValue(const DicomPath& path,
+                                                 const std::string& defaultValue) const
+  {
+    std::string s;
+    if (dataset_.GetStringValue(s, path))
+    {
+      return s;
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  std::string DicomDatasetReader::GetMandatoryStringValue(const DicomPath& path) const
+  {
+    std::string s;
+    if (dataset_.GetStringValue(s, path))
+    {
+      return s;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InexistentTag);
+    }
+  }
+
+
+  template <typename T>
+  static bool GetValueInternal(T& target,
+                               const IDicomDataset& dataset,
+                               const DicomPath& path)
+  {
+    try
+    {
+      std::string s;
+
+      if (dataset.GetStringValue(s, path))
+      {
+        target = boost::lexical_cast<T>(StripSpaces(s));
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+    catch (boost::bad_lexical_cast&)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);        
+    }
+  }
+
+
+  bool DicomDatasetReader::GetIntegerValue(int& target,
+                                           const DicomPath& path) const
+  {
+    return GetValueInternal<int>(target, dataset_, path);
+  }
+
+
+  bool DicomDatasetReader::GetUnsignedIntegerValue(unsigned int& target,
+                                                   const DicomPath& path) const
+  {
+    int value;
+
+    if (!GetIntegerValue(value, path))
+    {
+      return false;
+    }
+    else if (value >= 0)
+    {
+      target = static_cast<unsigned int>(value);
+      return true;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  bool DicomDatasetReader::GetFloatValue(float& target,
+                                         const DicomPath& path) const
+  {
+    return GetValueInternal<float>(target, dataset_, path);
+  }
+
+
+  bool DicomDatasetReader::GetDoubleValue(double& target,
+                                          const DicomPath& path) const
+  {
+    return GetValueInternal<double>(target, dataset_, path);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/Samples/Common/DicomDatasetReader.h	Fri Mar 23 11:04:03 2018 +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-2018 Osimis S.A., 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 "IDicomDataset.h"
+
+#include <memory>
+#include <vector>
+
+namespace OrthancPlugins
+{
+  class DicomDatasetReader : public boost::noncopyable
+  {
+  private:
+    const IDicomDataset&  dataset_;
+
+  public:
+    DicomDatasetReader(const IDicomDataset& dataset);
+
+    const IDicomDataset& GetDataset() const
+    {
+      return dataset_;
+    }
+
+    std::string GetStringValue(const DicomPath& path,
+                               const std::string& defaultValue) const;
+
+    std::string GetMandatoryStringValue(const DicomPath& path) const;
+
+    bool GetIntegerValue(int& target,
+                         const DicomPath& path) const;
+
+    bool GetUnsignedIntegerValue(unsigned int& target,
+                                 const DicomPath& path) const;
+
+    bool GetFloatValue(float& target,
+                       const DicomPath& path) const;
+
+    bool GetDoubleValue(double& target,
+                        const DicomPath& path) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/Samples/Common/DicomPath.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,87 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomPath.h"
+
+#include "OrthancPluginException.h"
+
+namespace OrthancPlugins
+{
+  const DicomPath::Prefix& DicomPath::GetPrefixItem(size_t depth) const
+  {
+    if (depth >= prefix_.size())
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+    else
+    {
+      return prefix_[depth];
+    }
+  }
+
+
+  DicomPath::DicomPath(const DicomTag& sequence,
+                       size_t index,
+                       const DicomTag& tag) :
+    finalTag_(tag)
+  {
+    AddToPrefix(sequence, index);
+  }
+
+
+  DicomPath::DicomPath(const DicomTag& sequence1,
+                       size_t index1,
+                       const DicomTag& sequence2,
+                       size_t index2,
+                       const DicomTag& tag) :
+    finalTag_(tag)
+  {
+    AddToPrefix(sequence1, index1);
+    AddToPrefix(sequence2, index2);
+  }
+
+
+  DicomPath::DicomPath(const DicomTag& sequence1,
+                       size_t index1,
+                       const DicomTag& sequence2,
+                       size_t index2,
+                       const DicomTag& sequence3,
+                       size_t index3,
+                       const DicomTag& tag) :
+    finalTag_(tag)
+  {
+    AddToPrefix(sequence1, index1);
+    AddToPrefix(sequence2, index2);
+    AddToPrefix(sequence3, index3);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/Samples/Common/DicomPath.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,108 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomTag.h"
+
+#include <vector>
+#include <stddef.h>
+
+namespace OrthancPlugins
+{
+  class DicomPath
+  {
+  private:
+    typedef std::pair<DicomTag, size_t>  Prefix;
+
+    std::vector<Prefix>  prefix_;
+    DicomTag             finalTag_;
+
+    const Prefix& GetPrefixItem(size_t depth) const;
+
+  public:
+    DicomPath(const DicomTag& finalTag) :
+    finalTag_(finalTag)
+    {
+    }
+
+    DicomPath(const DicomTag& sequence,
+              size_t index,
+              const DicomTag& tag);
+
+    DicomPath(const DicomTag& sequence1,
+              size_t index1,
+              const DicomTag& sequence2,
+              size_t index2,
+              const DicomTag& tag);
+
+    DicomPath(const DicomTag& sequence1,
+              size_t index1,
+              const DicomTag& sequence2,
+              size_t index2,
+              const DicomTag& sequence3,
+              size_t index3,
+              const DicomTag& tag);
+
+    void AddToPrefix(const DicomTag& tag,
+                     size_t position)
+    {
+      prefix_.push_back(std::make_pair(tag, position));
+    }
+
+    size_t GetPrefixLength() const
+    {
+      return prefix_.size();
+    }
+    
+    DicomTag GetPrefixTag(size_t depth) const
+    {
+      return GetPrefixItem(depth).first;
+    }
+
+    size_t GetPrefixIndex(size_t depth) const
+    {
+      return GetPrefixItem(depth).second;
+    }
+    
+    const DicomTag& GetFinalTag() const
+    {
+      return finalTag_;
+    }
+
+    void SetFinalTag(const DicomTag& tag)
+    {
+      finalTag_ = tag;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/Samples/Common/DicomTag.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,106 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 <stdint.h>
+
+namespace OrthancPlugins
+{
+  class DicomTag
+  {
+  private:
+    uint16_t  group_;
+    uint16_t  element_;
+
+    DicomTag();  // Forbidden
+
+  public:
+    DicomTag(uint16_t group,
+             uint16_t element) :
+      group_(group),
+      element_(element)
+    {
+    }
+
+    uint16_t GetGroup() const
+    {
+      return group_;
+    }
+
+    uint16_t GetElement() const
+    {
+      return element_;
+    }
+
+    const char* GetName() const;
+
+    bool operator== (const DicomTag& other) const
+    {
+      return group_ == other.group_ && element_ == other.element_;
+    }
+
+    bool operator!= (const DicomTag& other) const
+    {
+      return !(*this == other);
+    }
+  };
+
+
+  static const DicomTag DICOM_TAG_BITS_STORED(0x0028, 0x0101);
+  static const DicomTag DICOM_TAG_COLUMNS(0x0028, 0x0011);
+  static const DicomTag DICOM_TAG_COLUMN_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX(0x0048, 0x021e);
+  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_MODALITY(0x0008, 0x0060);
+  static const DicomTag DICOM_TAG_NUMBER_OF_FRAMES(0x0028, 0x0008);
+  static const DicomTag DICOM_TAG_PER_FRAME_FUNCTIONAL_GROUPS_SEQUENCE(0x5200, 0x9230);
+  static const DicomTag DICOM_TAG_PHOTOMETRIC_INTERPRETATION(0x0028, 0x0004);
+  static const DicomTag DICOM_TAG_PIXEL_REPRESENTATION(0x0028, 0x0103);
+  static const DicomTag DICOM_TAG_PIXEL_SPACING(0x0028, 0x0030);
+  static const DicomTag DICOM_TAG_PLANE_POSITION_SLIDE_SEQUENCE(0x0048, 0x021a);
+  static const DicomTag DICOM_TAG_RESCALE_INTERCEPT(0x0028, 0x1052);
+  static const DicomTag DICOM_TAG_RESCALE_SLOPE(0x0028, 0x1053);
+  static const DicomTag DICOM_TAG_ROWS(0x0028, 0x0010);
+  static const DicomTag DICOM_TAG_ROW_POSITION_IN_TOTAL_IMAGE_PIXEL_MATRIX(0x0048, 0x021f);
+  static const DicomTag DICOM_TAG_SAMPLES_PER_PIXEL(0x0028, 0x0002);
+  static const DicomTag DICOM_TAG_SERIES_INSTANCE_UID(0x0020, 0x000e);
+  static const DicomTag DICOM_TAG_SLICE_THICKNESS(0x0018, 0x0050);
+  static const DicomTag DICOM_TAG_SOP_CLASS_UID(0x0008, 0x0016);
+  static const DicomTag DICOM_TAG_SOP_INSTANCE_UID(0x0008, 0x0018);
+  static const DicomTag DICOM_TAG_TOTAL_PIXEL_MATRIX_COLUMNS(0x0048, 0x0006);
+  static const DicomTag DICOM_TAG_TOTAL_PIXEL_MATRIX_ROWS(0x0048, 0x0007);
+  static const DicomTag DICOM_TAG_TRANSFER_SYNTAX_UID(0x0002, 0x0010);
+  static const DicomTag DICOM_TAG_WINDOW_CENTER(0x0028, 0x1050);
+  static const DicomTag DICOM_TAG_WINDOW_WIDTH(0x0028, 0x1051);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,215 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "FullOrthancDataset.h"
+
+#include "OrthancPluginException.h"
+
+#include <stdio.h>
+#include <cassert>
+
+namespace OrthancPlugins
+{
+  static const Json::Value* AccessTag(const Json::Value& dataset,
+                                      const DicomTag& tag) 
+  {
+    if (dataset.type() != Json::objectValue)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+
+    char name[16];
+    sprintf(name, "%04x,%04x", tag.GetGroup(), tag.GetElement());
+
+    if (!dataset.isMember(name))
+    {
+      return NULL;
+    }
+
+    const Json::Value& value = dataset[name];
+    if (value.type() != Json::objectValue ||
+        !value.isMember("Name") ||
+        !value.isMember("Type") ||
+        !value.isMember("Value") ||
+        value["Name"].type() != Json::stringValue ||
+        value["Type"].type() != Json::stringValue)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+
+    return &value;
+  }
+
+
+  static const Json::Value& GetSequenceContent(const Json::Value& sequence)
+  {
+    assert(sequence.type() == Json::objectValue);
+    assert(sequence.isMember("Type"));
+    assert(sequence.isMember("Value"));
+
+    const Json::Value& value = sequence["Value"];
+      
+    if (sequence["Type"].asString() != "Sequence" ||
+        value.type() != Json::arrayValue)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+    else
+    {
+      return value;
+    }
+  }
+
+
+  static bool GetStringInternal(std::string& result,
+                                const Json::Value& tag)
+  {
+    assert(tag.type() == Json::objectValue);
+    assert(tag.isMember("Type"));
+    assert(tag.isMember("Value"));
+
+    const Json::Value& value = tag["Value"];
+      
+    if (tag["Type"].asString() != "String" ||
+        value.type() != Json::stringValue)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+    else
+    {
+      result = value.asString();
+      return true;
+    }
+  }
+
+
+  const Json::Value* FullOrthancDataset::LookupPath(const DicomPath& path) const
+  {
+    const Json::Value* content = &root_;
+                                  
+    for (unsigned int depth = 0; depth < path.GetPrefixLength(); depth++)
+    {
+      const Json::Value* sequence = AccessTag(*content, path.GetPrefixTag(depth));
+      if (sequence == NULL)
+      {
+        return NULL;
+      }
+
+      const Json::Value& nextContent = GetSequenceContent(*sequence);
+
+      size_t index = path.GetPrefixIndex(depth);
+      if (index >= nextContent.size())
+      {
+        return NULL;
+      }
+      else
+      {
+        content = &nextContent[static_cast<Json::Value::ArrayIndex>(index)];
+      }
+    }
+
+    return AccessTag(*content, path.GetFinalTag());
+  }
+
+
+  void FullOrthancDataset::CheckRoot() const
+  {
+    if (root_.type() != Json::objectValue)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  FullOrthancDataset::FullOrthancDataset(IOrthancConnection& orthanc,
+                                         const std::string& uri)
+  {
+    IOrthancConnection::RestApiGet(root_, orthanc, uri);
+    CheckRoot();
+  }
+
+
+  FullOrthancDataset::FullOrthancDataset(const std::string& content)
+  {
+    IOrthancConnection::ParseJson(root_, content);
+    CheckRoot();
+  }
+
+
+  FullOrthancDataset::FullOrthancDataset(const void* content,
+                                         size_t size)
+  {
+    IOrthancConnection::ParseJson(root_, content, size);
+    CheckRoot();
+  }
+
+
+  FullOrthancDataset::FullOrthancDataset(const Json::Value& root) :
+    root_(root)
+  {
+    CheckRoot();
+  }
+
+
+  bool FullOrthancDataset::GetStringValue(std::string& result,
+                                          const DicomPath& path) const
+  {
+    const Json::Value* value = LookupPath(path);
+
+    if (value == NULL)
+    {
+      return false;
+    }
+    else
+    {
+      return GetStringInternal(result, *value);
+    }
+  }
+
+
+  bool FullOrthancDataset::GetSequenceSize(size_t& size,
+                                           const DicomPath& path) const
+  {
+    const Json::Value* sequence = LookupPath(path);
+
+    if (sequence == NULL)
+    {
+      return false;
+    }
+    else
+    {
+      size = GetSequenceContent(*sequence).size();
+      return true;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/Samples/Common/FullOrthancDataset.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,69 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "IOrthancConnection.h"
+#include "IDicomDataset.h"
+
+#include <json/value.h>
+
+namespace OrthancPlugins
+{
+  class FullOrthancDataset : public IDicomDataset
+  {
+  private:
+    Json::Value   root_;
+
+    const Json::Value* LookupPath(const DicomPath& path) const;
+
+    void CheckRoot() const;
+
+  public:
+    FullOrthancDataset(IOrthancConnection& orthanc,
+                       const std::string& uri);
+
+    FullOrthancDataset(const std::string& content);
+
+    FullOrthancDataset(const void* content,
+                       size_t size);
+
+    FullOrthancDataset(const Json::Value& root);
+
+    virtual bool GetStringValue(std::string& result,
+                                const DicomPath& path) const;
+
+    virtual bool GetSequenceSize(size_t& size,
+                                 const DicomPath& path) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/Samples/Common/IDicomDataset.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,56 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "DicomPath.h"
+
+#include <boost/noncopyable.hpp>
+#include <string>
+
+namespace OrthancPlugins
+{
+  class IDicomDataset : public boost::noncopyable
+  {
+  public:
+    virtual ~IDicomDataset()
+    {
+    }
+
+    virtual bool GetStringValue(std::string& result,
+                                const DicomPath& path) const = 0;
+
+    virtual bool GetSequenceSize(size_t& size,
+                                 const DicomPath& path) const = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,98 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "IOrthancConnection.h"
+
+#include "OrthancPluginException.h"
+
+#include <json/reader.h>
+
+namespace OrthancPlugins
+{
+  void IOrthancConnection::ParseJson(Json::Value& result,
+                                     const std::string& content)
+  {
+    Json::Reader reader;
+    
+    if (!reader.parse(content, result))
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  void IOrthancConnection::ParseJson(Json::Value& result,
+                                     const void* content,
+                                     size_t size)
+  {
+    Json::Reader reader;
+    
+    if (!reader.parse(reinterpret_cast<const char*>(content),
+                      reinterpret_cast<const char*>(content) + size, result))
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  void IOrthancConnection::RestApiGet(Json::Value& result,
+                                      IOrthancConnection& orthanc,
+                                      const std::string& uri)
+  {
+    std::string content;
+    orthanc.RestApiGet(content, uri);
+    ParseJson(result, content);
+  }
+
+
+  void IOrthancConnection::RestApiPost(Json::Value& result,
+                                       IOrthancConnection& orthanc,
+                                       const std::string& uri,
+                                       const std::string& body)
+  {
+    std::string content;
+    orthanc.RestApiPost(content, uri, body);
+    ParseJson(result, content);
+  }
+
+
+  void IOrthancConnection::RestApiPut(Json::Value& result,
+                                      IOrthancConnection& orthanc,
+                                      const std::string& uri,
+                                      const std::string& body)
+  {
+    std::string content;
+    orthanc.RestApiPut(content, uri, body);
+    ParseJson(result, content);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/Samples/Common/IOrthancConnection.h	Fri Mar 23 11:04:03 2018 +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-2018 Osimis S.A., 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 "DicomPath.h"
+
+#include <boost/noncopyable.hpp>
+#include <string>
+#include <json/value.h>
+
+namespace OrthancPlugins
+{
+  class IOrthancConnection : public boost::noncopyable
+  {
+  public:
+    virtual ~IOrthancConnection()
+    {
+    }
+
+    virtual void RestApiGet(std::string& result,
+                            const std::string& uri) = 0;
+
+    virtual void RestApiPost(std::string& result,
+                             const std::string& uri,
+                             const std::string& body) = 0;
+
+    virtual void RestApiPut(std::string& result,
+                            const std::string& uri,
+                            const std::string& body) = 0;
+
+    virtual void RestApiDelete(const std::string& uri) = 0;
+
+    static void ParseJson(Json::Value& result,
+                          const std::string& content);
+
+    static void ParseJson(Json::Value& result,
+                          const void* content,
+                          size_t size);
+
+    static void RestApiGet(Json::Value& result,
+                           IOrthancConnection& orthanc,
+                           const std::string& uri);
+
+    static void RestApiPost(Json::Value& result,
+                            IOrthancConnection& orthanc,
+                            const std::string& uri,
+                            const std::string& body);
+
+    static void RestApiPut(Json::Value& result,
+                           IOrthancConnection& orthanc,
+                           const std::string& uri,
+                           const std::string& body);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/Samples/Common/OrthancHttpConnection.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,108 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "OrthancHttpConnection.h"
+
+namespace OrthancPlugins
+{
+  void OrthancHttpConnection::Setup()
+  {
+    url_ = client_.GetUrl();
+
+    // Don't follow 3xx HTTP (avoid redirections to "unsupported.png" in Orthanc)
+    client_.SetRedirectionFollowed(false);  
+  }
+
+
+  OrthancHttpConnection::OrthancHttpConnection() :
+    client_(Orthanc::WebServiceParameters(), "")
+  {
+    Setup();
+  }
+
+
+  OrthancHttpConnection::OrthancHttpConnection(const Orthanc::WebServiceParameters& parameters) :
+    client_(parameters, "")
+  {
+    Setup();
+  }
+
+
+  void OrthancHttpConnection::RestApiGet(std::string& result,
+                                         const std::string& uri)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    client_.SetMethod(Orthanc::HttpMethod_Get);
+    client_.SetUrl(url_ + uri);
+    client_.ApplyAndThrowException(result);
+  }
+
+
+  void OrthancHttpConnection::RestApiPost(std::string& result,
+                                          const std::string& uri,
+                                          const std::string& body)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    client_.SetMethod(Orthanc::HttpMethod_Post);
+    client_.SetUrl(url_ + uri);
+    client_.SetBody(body);
+    client_.ApplyAndThrowException(result);
+  }
+
+
+  void OrthancHttpConnection::RestApiPut(std::string& result,
+                                         const std::string& uri,
+                                         const std::string& body)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    client_.SetMethod(Orthanc::HttpMethod_Put);
+    client_.SetUrl(url_ + uri);
+    client_.SetBody(body);
+    client_.ApplyAndThrowException(result);
+  }
+
+
+  void OrthancHttpConnection::RestApiDelete(const std::string& uri)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    std::string result;
+
+    client_.SetMethod(Orthanc::HttpMethod_Delete);
+    client_.SetUrl(url_ + uri);
+    client_.ApplyAndThrowException(result);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/Samples/Common/OrthancHttpConnection.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,76 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 "IOrthancConnection.h"
+
+#if HAS_ORTHANC_EXCEPTION != 1
+#  error The macro HAS_ORTHANC_EXCEPTION must be set to 1 if using this header
+#endif
+
+#include "../../../Core/HttpClient.h"
+
+#include <boost/thread/mutex.hpp>
+
+namespace OrthancPlugins
+{
+  // This class is thread-safe
+  class OrthancHttpConnection : public IOrthancConnection
+  {
+  private:
+    boost::mutex         mutex_;
+    Orthanc::HttpClient  client_;
+    std::string          url_;
+
+    void Setup();
+
+  public:
+    OrthancHttpConnection();
+
+    OrthancHttpConnection(const Orthanc::WebServiceParameters& parameters);
+
+    virtual void RestApiGet(std::string& result,
+                            const std::string& uri);
+
+    virtual void RestApiPost(std::string& result,
+                             const std::string& uri,
+                             const std::string& body);
+
+    virtual void RestApiPut(std::string& result,
+                            const std::string& uri,
+                            const std::string& body);
+
+    virtual void RestApiDelete(const std::string& uri);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/Samples/Common/OrthancPluginException.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,101 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2018 Osimis S.A., 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 !defined(HAS_ORTHANC_EXCEPTION)
+#  error The macro HAS_ORTHANC_EXCEPTION must be defined
+#endif
+
+
+#if HAS_ORTHANC_EXCEPTION == 1
+#  include "../../../Core/OrthancException.h"
+#  define ORTHANC_PLUGINS_ERROR_ENUMERATION     ::Orthanc::ErrorCode
+#  define ORTHANC_PLUGINS_EXCEPTION_CLASS       ::Orthanc::OrthancException
+#  define ORTHANC_PLUGINS_GET_ERROR_CODE(code)  ::Orthanc::ErrorCode_ ## code
+#else
+#  include <orthanc/OrthancCPlugin.h>
+#  define ORTHANC_PLUGINS_ERROR_ENUMERATION     ::OrthancPluginErrorCode
+#  define ORTHANC_PLUGINS_EXCEPTION_CLASS       ::OrthancPlugins::PluginException
+#  define ORTHANC_PLUGINS_GET_ERROR_CODE(code)  ::OrthancPluginErrorCode_ ## code
+#endif
+
+
+#define ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code)                   \
+  throw ORTHANC_PLUGINS_EXCEPTION_CLASS(static_cast<ORTHANC_PLUGINS_ERROR_ENUMERATION>(code));
+
+
+#define ORTHANC_PLUGINS_THROW_EXCEPTION(code)                           \
+  throw ORTHANC_PLUGINS_EXCEPTION_CLASS(ORTHANC_PLUGINS_GET_ERROR_CODE(code));
+                                                  
+
+#define ORTHANC_PLUGINS_CHECK_ERROR(code)                           \
+  if (code != ORTHANC_PLUGINS_GET_ERROR_CODE(Success))              \
+  {                                                                 \
+    ORTHANC_PLUGINS_THROW_EXCEPTION(code);                          \
+  }
+
+
+namespace OrthancPlugins
+{
+#if HAS_ORTHANC_EXCEPTION == 0
+  class PluginException
+  {
+  private:
+    OrthancPluginErrorCode  code_;
+
+  public:
+    explicit PluginException(OrthancPluginErrorCode code) : code_(code)
+    {
+    }
+
+    OrthancPluginErrorCode GetErrorCode() const
+    {
+      return code_;
+    }
+
+    const char* What(OrthancPluginContext* context) const
+    {
+      const char* description = OrthancPluginGetErrorDescription(context, code_);
+      if (description)
+      {
+        return description;
+      }
+      else
+      {
+        return "No description available";
+      }
+    }
+  };
+#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/README.txt	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,3 @@
+This folder contains an excerpt of the source code of Orthanc. It is
+automatically generated using the "../Resources/SyncOrthancFolder.py"
+script.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/CMake/AutoGeneratedCode.cmake	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,53 @@
+set(AUTOGENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/AUTOGENERATED")
+set(AUTOGENERATED_SOURCES)
+
+file(MAKE_DIRECTORY ${AUTOGENERATED_DIR})
+include_directories(${AUTOGENERATED_DIR})
+
+macro(EmbedResources)
+  # Convert a semicolon separated list to a whitespace separated string
+  set(SCRIPT_OPTIONS)
+  set(SCRIPT_ARGUMENTS)
+  set(DEPENDENCIES)
+  set(IS_PATH_NAME false)
+
+  # Loop over the arguments of the function
+  foreach(arg ${ARGN})
+    # Extract the first character of the argument
+    string(SUBSTRING "${arg}" 0 1 FIRST_CHAR)
+    if (${FIRST_CHAR} STREQUAL "-")
+      # If the argument starts with a dash "-", this is an option to
+      # EmbedResources.py
+      list(APPEND SCRIPT_OPTIONS ${arg})
+    else()
+      if (${IS_PATH_NAME})
+        list(APPEND SCRIPT_ARGUMENTS "${arg}")
+        list(APPEND DEPENDENCIES "${arg}")
+        set(IS_PATH_NAME false)
+      else()
+        list(APPEND SCRIPT_ARGUMENTS "${arg}")
+        set(IS_PATH_NAME true)
+      endif()
+    endif()
+  endforeach()
+
+  set(TARGET_BASE "${AUTOGENERATED_DIR}/EmbeddedResources")
+  add_custom_command(
+    OUTPUT
+    "${TARGET_BASE}.h"
+    "${TARGET_BASE}.cpp"
+    COMMAND 
+    ${PYTHON_EXECUTABLE}
+    "${ORTHANC_ROOT}/Resources/EmbedResources.py"
+    ${SCRIPT_OPTIONS}
+    "${AUTOGENERATED_DIR}/EmbeddedResources"
+    ${SCRIPT_ARGUMENTS}
+    DEPENDS
+    "${ORTHANC_ROOT}/Resources/EmbedResources.py"
+    ${DEPENDENCIES}
+    )
+
+  list(APPEND AUTOGENERATED_SOURCES
+    "${AUTOGENERATED_DIR}/EmbeddedResources.cpp"
+    ) 
+endmacro()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/CMake/BoostConfiguration.cmake	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,306 @@
+if (STATIC_BUILD OR NOT USE_SYSTEM_BOOST)
+  set(BOOST_STATIC 1)
+else()
+  include(FindBoost)
+
+  set(BOOST_STATIC 0)
+  #set(Boost_DEBUG 1)
+  #set(Boost_USE_STATIC_LIBS ON)
+
+  if (ENABLE_LOCALE)
+    list(APPEND ORTHANC_BOOST_COMPONENTS locale)
+  endif()
+
+  list(APPEND ORTHANC_BOOST_COMPONENTS filesystem thread system date_time regex)
+  find_package(Boost COMPONENTS "${ORTHANC_BOOST_COMPONENTS}")
+
+  if (NOT Boost_FOUND)
+    foreach (item ${ORTHANC_BOOST_COMPONENTS})
+      string(TOUPPER ${item} tmp)
+
+      if (Boost_${tmp}_FOUND)
+        set(tmp2 "found")
+      else()
+        set(tmp2 "missing")
+      endif()
+      
+      message("Boost component ${item} - ${tmp2}")
+    endforeach()
+    
+    message(FATAL_ERROR "Unable to locate Boost on this system")
+  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)
+    add_definitions(
+      -DBOOST_HAS_FILESYSTEM_V3=0
+      )
+  else()
+    add_definitions(
+      -DBOOST_HAS_FILESYSTEM_V3=1
+      -DBOOST_FILESYSTEM_VERSION=3
+      )
+  endif()
+
+  include_directories(${Boost_INCLUDE_DIRS})
+  link_libraries(${Boost_LIBRARIES})
+endif()
+
+
+if (BOOST_STATIC)
+  ##
+  ## Parameters for static compilation of Boost 
+  ##
+  
+  set(BOOST_NAME boost_1_65_1)
+  set(BOOST_BCP_SUFFIX bcpdigest-1.3.1)
+  set(BOOST_MD5 "92c9c603e56bbd7a450a305f08747d90")
+  set(BOOST_URL "http://www.orthanc-server.com/downloads/third-party/${BOOST_NAME}_${BOOST_BCP_SUFFIX}.tar.gz")
+  set(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME})
+
+  if (IS_DIRECTORY "${BOOST_SOURCES_DIR}")
+    set(FirstRun OFF)
+  else()
+    set(FirstRun ON)
+  endif()
+
+  DownloadPackage(${BOOST_MD5} ${BOOST_URL} "${BOOST_SOURCES_DIR}")
+
+
+  ##
+  ## Generic configuration of Boost
+  ## 
+
+  if (CMAKE_COMPILER_IS_GNUCXX)
+    add_definitions(-isystem ${BOOST_SOURCES_DIR})
+  endif()
+
+  include_directories(
+    ${BOOST_SOURCES_DIR}
+    )
+
+  add_definitions(
+    # Static build of Boost
+    -DBOOST_ALL_NO_LIB 
+    -DBOOST_ALL_NOLIB 
+    -DBOOST_DATE_TIME_NO_LIB 
+    -DBOOST_THREAD_BUILD_LIB
+    -DBOOST_PROGRAM_OPTIONS_NO_LIB
+    -DBOOST_REGEX_NO_LIB
+    -DBOOST_SYSTEM_NO_LIB
+    -DBOOST_LOCALE_NO_LIB
+    )
+
+  set(BOOST_SOURCES
+    ${BOOST_SOURCES_DIR}/libs/system/src/error_code.cpp
+    )
+
+  if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+    add_definitions(-DBOOST_SYSTEM_USE_STRERROR=1)
+    
+    execute_process(
+      COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
+      ${ORTHANC_ROOT}/Resources/Patches/boost-1.65.1-linux-standard-base.patch
+      WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+      RESULT_VARIABLE Failure
+      )
+
+    if (FirstRun AND Failure)
+      message(FATAL_ERROR "Error while patching a file")
+    endif()
+  endif()
+
+  
+  ##
+  ## Configuration of boost::thread
+  ##
+  
+  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 "PNaCl" OR
+      CMAKE_SYSTEM_NAME STREQUAL "NaCl32" OR
+      CMAKE_SYSTEM_NAME STREQUAL "NaCl64")
+    list(APPEND BOOST_SOURCES
+      ${BOOST_SOURCES_DIR}/libs/atomic/src/lockpool.cpp
+      ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/once.cpp
+      ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/thread.cpp
+      )
+
+    if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase" OR
+        CMAKE_SYSTEM_NAME STREQUAL "PNaCl" OR
+        CMAKE_SYSTEM_NAME STREQUAL "NaCl32" OR
+        CMAKE_SYSTEM_NAME STREQUAL "NaCl64")
+      add_definitions(-DBOOST_HAS_SCHED_YIELD=1)
+    endif()
+
+  elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
+    list(APPEND BOOST_SOURCES
+      ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_dll.cpp
+      ${BOOST_SOURCES_DIR}/libs/thread/src/win32/thread.cpp
+      ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_pe.cpp
+      )
+
+  elseif (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
+
+  else()
+    message(FATAL_ERROR "Support your platform here")
+  endif()
+
+
+  ##
+  ## Configuration of boost::regex
+  ##
+  
+  aux_source_directory(${BOOST_SOURCES_DIR}/libs/regex/src BOOST_REGEX_SOURCES)
+
+  list(APPEND BOOST_SOURCES
+    ${BOOST_REGEX_SOURCES}
+    )
+
+
+  ##
+  ## Configuration of boost::datetime
+  ##
+  
+  list(APPEND BOOST_SOURCES
+    ${BOOST_SOURCES_DIR}/libs/date_time/src/gregorian/greg_month.cpp
+    )
+
+
+  ##
+  ## Configuration of boost::filesystem
+  ## 
+
+  if (CMAKE_SYSTEM_NAME STREQUAL "PNaCl" OR
+      CMAKE_SYSTEM_NAME STREQUAL "NaCl32" OR
+      CMAKE_SYSTEM_NAME STREQUAL "NaCl64")
+    # boost::filesystem is not available on PNaCl
+    add_definitions(
+      -DBOOST_HAS_FILESYSTEM_V3=0
+      -D__INTEGRITY=1
+      )
+  else()
+    add_definitions(
+      -DBOOST_HAS_FILESYSTEM_V3=1
+      )
+    list(APPEND BOOST_SOURCES
+      ${BOOST_NAME}/libs/filesystem/src/codecvt_error_category.cpp
+      ${BOOST_NAME}/libs/filesystem/src/operations.cpp
+      ${BOOST_NAME}/libs/filesystem/src/path.cpp
+      ${BOOST_NAME}/libs/filesystem/src/path_traits.cpp
+      )
+
+    if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR
+        CMAKE_SYSTEM_NAME STREQUAL "OpenBSD" OR
+        CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
+     list(APPEND BOOST_SOURCES
+        ${BOOST_SOURCES_DIR}/libs/filesystem/src/utf8_codecvt_facet.cpp
+        )
+
+    elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows")
+      list(APPEND BOOST_SOURCES
+        ${BOOST_NAME}/libs/filesystem/src/windows_file_codecvt.cpp
+        )
+    endif()
+  endif()
+
+
+  ##
+  ## Configuration of boost::locale
+  ## 
+
+  if (NOT ENABLE_LOCALE)
+    message("boost::locale is disabled")
+  else()
+    list(APPEND BOOST_SOURCES
+      ${BOOST_SOURCES_DIR}/libs/locale/src/encoding/codepage.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/generator.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/date_time.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/formatting.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/ids.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/localization_backend.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/message.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/mo_lambda.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/util/codecvt_converter.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/util/default_locale.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/util/gregorian.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/util/info.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/util/locale_data.cpp
+      )        
+
+    if (CMAKE_SYSTEM_NAME STREQUAL "OpenBSD" OR
+        CMAKE_SYSTEM_VERSION STREQUAL "LinuxStandardBase")
+      list(APPEND BOOST_SOURCES
+        ${BOOST_SOURCES_DIR}/libs/locale/src/std/codecvt.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/std/collate.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/std/converter.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/std/numeric.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/std/std_backend.cpp
+        )
+
+      add_definitions(
+        -DBOOST_LOCALE_WITH_ICONV=1
+        -DBOOST_LOCALE_NO_WINAPI_BACKEND=1
+        -DBOOST_LOCALE_NO_POSIX_BACKEND=1
+        )
+      
+    elseif (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 "PNaCl" OR
+            CMAKE_SYSTEM_NAME STREQUAL "NaCl32" OR
+            CMAKE_SYSTEM_NAME STREQUAL "NaCl64")
+      list(APPEND BOOST_SOURCES
+        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/codecvt.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/collate.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/converter.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/numeric.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/posix_backend.cpp
+        )
+
+      add_definitions(
+        -DBOOST_LOCALE_WITH_ICONV=1
+        -DBOOST_LOCALE_NO_WINAPI_BACKEND=1
+        -DBOOST_LOCALE_NO_STD_BACKEND=1
+        )
+      
+    elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows")
+      list(APPEND BOOST_SOURCES
+        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/collate.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/converter.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/lcid.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/numeric.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/win_backend.cpp
+        )
+
+      add_definitions(
+        -DBOOST_LOCALE_NO_POSIX_BACKEND=1
+        -DBOOST_LOCALE_NO_STD_BACKEND=1
+        )
+
+      # Starting with release 0.8.2, Orthanc statically links against
+      # libiconv, even on Windows. Indeed, the "WCONV" library of
+      # Windows XP seems not to support properly several codepages
+      # (notably "Latin3", "Hebrew", and "Arabic"). Set
+      # "USE_BOOST_ICONV" to "OFF" to use WCONV anyway.
+
+      if (USE_BOOST_ICONV)
+        add_definitions(-DBOOST_LOCALE_WITH_ICONV=1)
+      else()
+        add_definitions(-DBOOST_LOCALE_WITH_WCONV=1)
+      endif()
+
+    else()
+      message(FATAL_ERROR "Support your platform here")
+    endif()
+  endif()
+
+  
+  source_group(ThirdParty\\boost REGULAR_EXPRESSION ${BOOST_SOURCES_DIR}/.*)
+
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/CMake/Compiler.cmake	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,214 @@
+# This file sets all the compiler-related flags
+
+if (CMAKE_CROSSCOMPILING OR
+    "${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+  # Cross-compilation necessarily implies standalone and static build
+  SET(STATIC_BUILD ON)
+  SET(STANDALONE_BUILD ON)
+endif()
+
+if (CMAKE_COMPILER_IS_GNUCXX)
+  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wno-long-long")
+
+  # --std=c99 makes libcurl not to compile
+  # -pedantic gives a lot of warnings on OpenSSL 
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -Wno-variadic-macros")
+
+  if (CMAKE_CROSSCOMPILING)
+    # http://stackoverflow.com/a/3543845/881731
+    set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> -O coff -I<CMAKE_CURRENT_SOURCE_DIR> <SOURCE> <OBJECT>")
+  endif()
+
+elseif (MSVC)
+  # Use static runtime under Visual Studio
+  # http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace
+  # http://stackoverflow.com/a/6510446
+  foreach(flag_var
+    CMAKE_C_FLAGS_DEBUG
+    CMAKE_CXX_FLAGS_DEBUG
+    CMAKE_C_FLAGS_RELEASE 
+    CMAKE_CXX_FLAGS_RELEASE
+    CMAKE_C_FLAGS_MINSIZEREL 
+    CMAKE_CXX_FLAGS_MINSIZEREL 
+    CMAKE_C_FLAGS_RELWITHDEBINFO 
+    CMAKE_CXX_FLAGS_RELWITHDEBINFO) 
+    string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
+    string(REGEX REPLACE "/MDd" "/MTd" ${flag_var} "${${flag_var}}")
+  endforeach(flag_var)
+
+  # Add /Zm256 compiler option to Visual Studio to fix PCH errors
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zm256")
+
+  add_definitions(
+    -D_CRT_SECURE_NO_WARNINGS=1
+    -D_CRT_SECURE_NO_DEPRECATE=1
+    )
+
+  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(${ORTHANC_ROOT}/Resources/ThirdParty/VisualStudio)
+  endif()
+
+  link_libraries(netapi32)
+endif()
+
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
+  # In FreeBSD/OpenBSD, the "/usr/local/" folder contains the ports and need to be imported
+  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I/usr/local/include")
+  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I/usr/local/include")
+  SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/local/lib")
+  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -L/usr/local/lib")
+endif()
+
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
+
+  if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
+    # The "--no-undefined" linker flag makes the shared libraries
+    # (plugins ModalityWorklists and ServeFolders) fail to compile on OpenBSD
+    set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined")
+    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
+  endif()
+
+  if (NOT DEFINED ENABLE_PLUGINS_VERSION_SCRIPT OR 
+      ENABLE_PLUGINS_VERSION_SCRIPT)
+    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${ORTHANC_ROOT}/Plugins/Samples/Common/VersionScript.map")
+  endif()
+
+  # Remove the "-rdynamic" option
+  # http://www.mail-archive.com/cmake@cmake.org/msg08837.html
+  set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "")
+  link_libraries(pthread)
+
+  if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
+    link_libraries(rt)
+  endif()
+
+  if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" AND
+      NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
+    link_libraries(dl)
+  endif()
+
+  if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" AND
+      NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
+    # The "--as-needed" linker flag is not available on FreeBSD and OpenBSD
+    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed")
+    set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--as-needed")
+    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--as-needed")
+  endif()
+
+  if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" AND
+      NOT ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
+    # FreeBSD/OpenBSD have just one single interface for file
+    # handling, which is 64bit clean, so there is no need to define macro
+    # for LFS (Large File Support).
+    # https://ohse.de/uwe/articles/lfs.html
+    add_definitions(
+      -D_LARGEFILE64_SOURCE=1 
+      -D_FILE_OFFSET_BITS=64
+      )
+  endif()
+
+elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  if (MSVC)
+    message("MSVC compiler version = " ${MSVC_VERSION} "\n")
+    # Starting Visual Studio 2013 (version 1800), it is not possible
+    # to target Windows XP anymore
+    if (MSVC_VERSION LESS 1800)
+      add_definitions(
+        -DWINVER=0x0501
+        -D_WIN32_WINNT=0x0501
+        )
+    endif()
+  else()
+    add_definitions(
+      -DWINVER=0x0501
+      -D_WIN32_WINNT=0x0501
+      )
+  endif()
+
+  add_definitions(
+    -D_CRT_SECURE_NO_WARNINGS=1
+    )
+  link_libraries(rpcrt4 ws2_32)
+
+  if (CMAKE_COMPILER_IS_GNUCXX)
+    # Some additional C/C++ compiler flags for MinGW
+    SET(MINGW_NO_WARNINGS "-Wno-unused-function -Wno-unused-variable")
+    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_SHARED_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++")
+
+    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")
+      add_definitions(-DHAVE_WIN_PTHREAD=1)
+    else()
+      add_definitions(-DHAVE_WIN_PTHREAD=0)
+    endif()
+  endif()
+
+elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -exported_symbols_list ${ORTHANC_ROOT}/Plugins/Samples/Common/ExportedSymbols.list")
+
+  add_definitions(
+    -D_XOPEN_SOURCE=1
+    )
+  link_libraries(iconv)
+
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
+  message("Building using Emscripten (for WebAssembly or asm.js targets)")
+
+else()
+  message(FATAL_ERROR "Support your platform here")
+endif()
+
+
+if (DEFINED ENABLE_PROFILING AND ENABLE_PROFILING)
+  if (CMAKE_COMPILER_IS_GNUCXX)
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
+    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pg")
+    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
+    set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -pg")
+    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pg")
+  else()
+    message(FATAL_ERROR "Don't know how to enable profiling on your configuration")
+  endif()
+endif()
+
+
+if (CMAKE_COMPILER_IS_GNUCXX)
+  # "When creating a static library using binutils (ar) and there
+  # exist a duplicate object name (e.g. a/Foo.cpp.o, b/Foo.cpp.o), the
+  # resulting static library can end up having only one of the
+  # duplicate objects. [...] This bug only happens if there are many
+  # objects." The trick consists in replacing the "r" argument
+  # ("replace") provided to "ar" (as used in CMake < 3.1) by the "q"
+  # argument ("quick append"). This is because of the fact that CMake
+  # will invoke "ar" several times with several batches of ".o"
+  # objects, and using "r" would overwrite symbols defined in
+  # preceding batches. https://cmake.org/Bug/view.php?id=14874
+  set(CMAKE_CXX_ARCHIVE_APPEND "<CMAKE_AR> <LINK_FLAGS> q <TARGET> <OBJECTS>")
+endif()
+
+
+if (STATIC_BUILD)
+  add_definitions(-DORTHANC_STATIC=1)
+else()
+  add_definitions(-DORTHANC_STATIC=0)
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/CMake/DownloadPackage.cmake	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,172 @@
+macro(GetUrlFilename TargetVariable Url)
+  string(REGEX REPLACE "^.*/" "" ${TargetVariable} "${Url}")
+endmacro()
+
+
+macro(GetUrlExtension TargetVariable Url)
+  #string(REGEX REPLACE "^.*/[^.]*\\." "" TMP "${Url}")
+  string(REGEX REPLACE "^.*\\." "" TMP "${Url}")
+  string(TOLOWER "${TMP}" "${TargetVariable}")
+endmacro()
+
+
+
+##
+## Setup the patch command-line tool
+##
+
+if (NOT ORTHANC_DISABLE_PATCH)
+  if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
+    set(PATCH_EXECUTABLE ${CMAKE_CURRENT_LIST_DIR}/../ThirdParty/patch/patch.exe)
+    if (NOT EXISTS ${PATCH_EXECUTABLE})
+      message(FATAL_ERROR "Unable to find the patch.exe tool that is shipped with Orthanc")
+    endif()
+
+  else ()
+    find_program(PATCH_EXECUTABLE patch)
+    if (${PATCH_EXECUTABLE} MATCHES "PATCH_EXECUTABLE-NOTFOUND")
+      message(FATAL_ERROR "Please install the 'patch' standard command-line tool")
+    endif()
+  endif()
+endif()
+
+
+
+##
+## Check the existence of the required decompression tools
+##
+
+if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
+  find_program(ZIP_EXECUTABLE 7z 
+    PATHS 
+    "$ENV{ProgramFiles}/7-Zip"
+    "$ENV{ProgramW6432}/7-Zip"
+    )
+
+  if (${ZIP_EXECUTABLE} MATCHES "ZIP_EXECUTABLE-NOTFOUND")
+    message(FATAL_ERROR "Please install the '7-zip' software (http://www.7-zip.org/)")
+  endif()
+
+else()
+  find_program(UNZIP_EXECUTABLE unzip)
+  if (${UNZIP_EXECUTABLE} MATCHES "UNZIP_EXECUTABLE-NOTFOUND")
+    message(FATAL_ERROR "Please install the 'unzip' package")
+  endif()
+
+  find_program(TAR_EXECUTABLE tar)
+  if (${TAR_EXECUTABLE} MATCHES "TAR_EXECUTABLE-NOTFOUND")
+    message(FATAL_ERROR "Please install the 'tar' package")
+  endif()
+endif()
+
+
+macro(DownloadPackage MD5 Url TargetDirectory)
+  if (NOT IS_DIRECTORY "${TargetDirectory}")
+    GetUrlFilename(TMP_FILENAME "${Url}")
+
+    set(TMP_PATH "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${TMP_FILENAME}")
+    if (NOT EXISTS "${TMP_PATH}")
+      message("Downloading ${Url}")
+
+      # This fixes issue 6: "I think cmake shouldn't download the
+      # packages which are not in the system, it should stop and let
+      # user know."
+      # https://code.google.com/p/orthanc/issues/detail?id=6
+      if (NOT STATIC_BUILD AND NOT ALLOW_DOWNLOADS)
+	message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON")
+      endif()
+
+      file(DOWNLOAD "${Url}" "${TMP_PATH}" 
+        SHOW_PROGRESS EXPECTED_MD5 "${MD5}"
+        TIMEOUT 60 INACTIVITY_TIMEOUT 60)
+    else()
+      message("Using local copy of ${Url}")
+    endif()
+
+    GetUrlExtension(TMP_EXTENSION "${Url}")
+    #message(${TMP_EXTENSION})
+    message("Uncompressing ${TMP_FILENAME}")
+
+    if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
+      # How to silently extract files using 7-zip
+      # http://superuser.com/questions/331148/7zip-command-line-extract-silently-quietly
+
+      if (("${TMP_EXTENSION}" STREQUAL "gz") OR 
+          ("${TMP_EXTENSION}" STREQUAL "tgz") OR
+          ("${TMP_EXTENSION}" STREQUAL "xz"))
+        execute_process(
+          COMMAND ${ZIP_EXECUTABLE} e -y ${TMP_PATH}
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          OUTPUT_QUIET
+          )
+
+        if (Failure)
+          message(FATAL_ERROR "Error while running the uncompression tool")
+        endif()
+
+        if ("${TMP_EXTENSION}" STREQUAL "tgz")
+          string(REGEX REPLACE ".tgz$" ".tar" TMP_FILENAME2 "${TMP_FILENAME}")
+        elseif ("${TMP_EXTENSION}" STREQUAL "gz")
+          string(REGEX REPLACE ".gz$" "" TMP_FILENAME2 "${TMP_FILENAME}")
+        elseif ("${TMP_EXTENSION}" STREQUAL "xz")
+          string(REGEX REPLACE ".xz" "" TMP_FILENAME2 "${TMP_FILENAME}")
+        endif()
+
+        execute_process(
+          COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_FILENAME2}
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          OUTPUT_QUIET
+          )
+      elseif ("${TMP_EXTENSION}" STREQUAL "zip")
+        execute_process(
+          COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_PATH}
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          OUTPUT_QUIET
+          )
+      else()
+        message(FATAL_ERROR "Support your platform here")
+      endif()
+
+    else()
+      if ("${TMP_EXTENSION}" STREQUAL "zip")
+        execute_process(
+          COMMAND sh -c "${UNZIP_EXECUTABLE} -q ${TMP_PATH}"
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+        )
+      elseif (("${TMP_EXTENSION}" STREQUAL "gz") OR ("${TMP_EXTENSION}" STREQUAL "tgz"))
+        #message("tar xvfz ${TMP_PATH}")
+        execute_process(
+          COMMAND sh -c "${TAR_EXECUTABLE} xfz ${TMP_PATH}"
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          )
+      elseif ("${TMP_EXTENSION}" STREQUAL "bz2")
+        execute_process(
+          COMMAND sh -c "${TAR_EXECUTABLE} xfj ${TMP_PATH}"
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          )
+      elseif ("${TMP_EXTENSION}" STREQUAL "xz")
+        execute_process(
+          COMMAND sh -c "${TAR_EXECUTABLE} xf ${TMP_PATH}"
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          )
+      else()
+        message(FATAL_ERROR "Unknown package format.")
+      endif()
+    endif()
+   
+    if (Failure)
+      message(FATAL_ERROR "Error while running the uncompression tool")
+    endif()
+
+    if (NOT IS_DIRECTORY "${TargetDirectory}")
+      message(FATAL_ERROR "The package was not uncompressed at the proper location. Check the CMake instructions.")
+    endif()
+  endif()
+endmacro()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/CMake/GoogleTestConfiguration.cmake	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,68 @@
+if (USE_GOOGLE_TEST_DEBIAN_PACKAGE)
+  find_path(GOOGLE_TEST_DEBIAN_SOURCES_DIR
+    NAMES src/gtest-all.cc
+    PATHS
+    /usr/src/gtest
+    /usr/src/googletest/googletest
+    PATH_SUFFIXES src
+    )
+
+  find_path(GOOGLE_TEST_DEBIAN_INCLUDE_DIR
+    NAMES gtest.h
+    PATHS
+    /usr/include/gtest
+    )
+
+  message("Path to the Debian Google Test sources: ${GOOGLE_TEST_DEBIAN_SOURCES_DIR}")
+  message("Path to the Debian Google Test includes: ${GOOGLE_TEST_DEBIAN_INCLUDE_DIR}")
+
+  set(GOOGLE_TEST_SOURCES
+    ${GOOGLE_TEST_DEBIAN_SOURCES_DIR}/src/gtest-all.cc
+    )
+
+  include_directories(${GOOGLE_TEST_DEBIAN_SOURCES_DIR})
+
+  if (NOT EXISTS ${GOOGLE_TEST_SOURCES} OR
+      NOT EXISTS ${GOOGLE_TEST_DEBIAN_INCLUDE_DIR}/gtest.h)
+    message(FATAL_ERROR "Please install the libgtest-dev package")
+  endif()
+
+elseif (STATIC_BUILD OR NOT USE_SYSTEM_GOOGLE_TEST)
+  set(GOOGLE_TEST_SOURCES_DIR ${CMAKE_BINARY_DIR}/gtest-1.7.0)
+  set(GOOGLE_TEST_URL "http://www.orthanc-server.com/downloads/third-party/gtest-1.7.0.zip")
+  set(GOOGLE_TEST_MD5 "2d6ec8ccdf5c46b05ba54a9fd1d130d7")
+
+  DownloadPackage(${GOOGLE_TEST_MD5} ${GOOGLE_TEST_URL} "${GOOGLE_TEST_SOURCES_DIR}")
+
+  include_directories(
+    ${GOOGLE_TEST_SOURCES_DIR}/include
+    ${GOOGLE_TEST_SOURCES_DIR}
+    )
+
+  set(GOOGLE_TEST_SOURCES
+    ${GOOGLE_TEST_SOURCES_DIR}/src/gtest-all.cc
+    )
+
+  # https://code.google.com/p/googletest/issues/detail?id=412
+  if (MSVC) # VS2012 does not support tuples correctly yet
+    add_definitions(/D _VARIADIC_MAX=10)
+  endif()
+  
+  if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+    add_definitions(-DGTEST_HAS_CLONE=0)
+  endif()
+  
+  source_group(ThirdParty\\GoogleTest REGULAR_EXPRESSION ${GOOGLE_TEST_SOURCES_DIR}/.*)
+
+else()
+  include(FindGTest)
+  if (NOT GTEST_FOUND)
+    message(FATAL_ERROR "Unable to find GoogleTest")
+  endif()
+
+  include_directories(${GTEST_INCLUDE_DIRS})
+
+  # The variable GTEST_LIBRARIES contains the shared library of
+  # Google Test, create an alias for more uniformity
+  set(GOOGLE_TEST_LIBRARIES ${GTEST_LIBRARIES})
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/CMake/JsonCppConfiguration.cmake	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,82 @@
+set(JSONCPP_CXX11 OFF)
+
+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://www.orthanc-server.com/downloads/third-party/jsoncpp-0.10.6.tar.gz")
+    set(JSONCPP_MD5 "13d1991d79697df8cadbc25c93e37c83")
+    add_definitions(-DORTHANC_LEGACY_JSONCPP=1)
+  else()
+    set(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-1.8.4)
+    set(JSONCPP_URL "http://www.orthanc-server.com/downloads/third-party/jsoncpp-1.8.4.tar.gz")
+    set(JSONCPP_MD5 "fa47a3ab6b381869b6a5f20811198662")
+    add_definitions(-DORTHANC_LEGACY_JSONCPP=0)
+    set(JSONCPP_CXX11 ON)
+  endif()
+
+  DownloadPackage(${JSONCPP_MD5} ${JSONCPP_URL} "${JSONCPP_SOURCES_DIR}")
+
+  set(JSONCPP_SOURCES
+    ${JSONCPP_SOURCES_DIR}/src/lib_json/json_reader.cpp
+    ${JSONCPP_SOURCES_DIR}/src/lib_json/json_value.cpp
+    ${JSONCPP_SOURCES_DIR}/src/lib_json/json_writer.cpp
+    )
+
+  include_directories(
+    ${JSONCPP_SOURCES_DIR}/include
+    )
+
+  source_group(ThirdParty\\JsonCpp REGULAR_EXPRESSION ${JSONCPP_SOURCES_DIR}/.*)
+
+else()
+  find_path(JSONCPP_INCLUDE_DIR json/reader.h
+    /usr/include/jsoncpp
+    /usr/local/include/jsoncpp
+    )
+
+  message("JsonCpp include dir: ${JSONCPP_INCLUDE_DIR}")
+  include_directories(${JSONCPP_INCLUDE_DIR})
+  link_libraries(jsoncpp)
+
+  CHECK_INCLUDE_FILE_CXX(${JSONCPP_INCLUDE_DIR}/json/reader.h HAVE_JSONCPP_H)
+  if (NOT HAVE_JSONCPP_H)
+    message(FATAL_ERROR "Please install the libjsoncpp-dev package")
+  endif()
+
+  # Switch to the C++11 standard if the version of JsonCpp is 1.y.z
+  if (EXISTS ${JSONCPP_INCLUDE_DIR}/json/version.h)
+    file(STRINGS
+      "${JSONCPP_INCLUDE_DIR}/json/version.h" 
+      JSONCPP_VERSION_MAJOR1 REGEX
+      ".*define JSONCPP_VERSION_MAJOR.*")
+
+    if (NOT JSONCPP_VERSION_MAJOR1)
+      message(FATAL_ERROR "Unable to extract the major version of JsonCpp")
+    endif()
+    
+    string(REGEX REPLACE
+      ".*JSONCPP_VERSION_MAJOR.*([0-9]+)$" "\\1" 
+      JSONCPP_VERSION_MAJOR ${JSONCPP_VERSION_MAJOR1})
+    message("JsonCpp major version: ${JSONCPP_VERSION_MAJOR}")
+
+    if (JSONCPP_VERSION_MAJOR GREATER 0)
+      set(JSONCPP_CXX11 ON)
+    endif()
+  else()
+    message("Unable to detect the major version of JsonCpp, assuming < 1.0.0")
+  endif()
+endif()
+
+
+if (JSONCPP_CXX11)
+  # Osimis has encountered problems when this macro is left at its
+  # default value (1000), so we increase this limit
+  # 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")
+    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wno-deprecated-declarations")
+  endif()
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/CMake/LibCurlConfiguration.cmake	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,323 @@
+if (STATIC_BUILD OR NOT USE_SYSTEM_CURL)
+  SET(CURL_SOURCES_DIR ${CMAKE_BINARY_DIR}/curl-7.57.0)
+  SET(CURL_URL "http://www.orthanc-server.com/downloads/third-party/curl-7.57.0.tar.gz")
+  SET(CURL_MD5 "c7aab73aaf5e883ca1d7518f93649dc2")
+
+  if (IS_DIRECTORY "${CURL_SOURCES_DIR}")
+    set(FirstRun OFF)
+  else()
+    set(FirstRun ON)
+  endif()
+  
+  DownloadPackage(${CURL_MD5} ${CURL_URL} "${CURL_SOURCES_DIR}")
+
+  if (FirstRun)
+    execute_process(
+      COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
+      ${ORTHANC_ROOT}/Resources/Patches/curl-7.57.0-cmake.patch
+      WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+      RESULT_VARIABLE Failure
+      )
+    
+    if (Failure)
+      message(FATAL_ERROR "Error while patching a file")
+    endif()
+  endif()
+  
+  include_directories(
+    ${CURL_SOURCES_DIR}/include
+    )
+
+  AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib CURL_SOURCES)
+  AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib/vauth CURL_SOURCES)
+  AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib/vtls CURL_SOURCES)
+  source_group(ThirdParty\\LibCurl REGULAR_EXPRESSION ${CURL_SOURCES_DIR}/.*)
+
+  add_definitions(
+    -DBUILDING_LIBCURL=1
+    -DCURL_STATICLIB=1
+    -DCURL_DISABLE_LDAPS=1
+    -DCURL_DISABLE_LDAP=1
+    -DCURL_DISABLE_DICT=1
+    -DCURL_DISABLE_FILE=1
+    -DCURL_DISABLE_FTP=1
+    -DCURL_DISABLE_GOPHER=1
+    -DCURL_DISABLE_LDAP=1
+    -DCURL_DISABLE_LDAPS=1
+    -DCURL_DISABLE_POP3=1
+    #-DCURL_DISABLE_PROXY=1
+    -DCURL_DISABLE_RTSP=1
+    -DCURL_DISABLE_TELNET=1
+    -DCURL_DISABLE_TFTP=1
+    )
+
+  if (ENABLE_SSL)
+    add_definitions(
+      #-DHAVE_LIBSSL=1
+      -DUSE_OPENSSL=1
+      -DHAVE_OPENSSL_ENGINE_H=1
+      -DUSE_SSLEAY=1
+      )
+  endif()
+
+  if (NOT EXISTS "${CURL_SOURCES_DIR}/lib/curl_config.h")
+    #file(WRITE ${CURL_SOURCES_DIR}/lib/curl_config.h "")
+
+    file(WRITE ${CURL_SOURCES_DIR}/lib/vauth/vauth/vauth.h "#include \"../vauth.h\"\n")
+    file(WRITE ${CURL_SOURCES_DIR}/lib/vauth/vauth/digest.h "#include \"../digest.h\"\n")
+    file(WRITE ${CURL_SOURCES_DIR}/lib/vauth/vauth/ntlm.h "#include \"../ntlm.h\"\n")
+    file(WRITE ${CURL_SOURCES_DIR}/lib/vauth/vtls/vtls.h "#include \"../../vtls/vtls.h\"\n")
+
+    file(GLOB CURL_LIBS_HEADERS ${CURL_SOURCES_DIR}/lib/*.h)
+    foreach (header IN LISTS CURL_LIBS_HEADERS)
+      get_filename_component(filename ${header} NAME)
+      file(WRITE ${CURL_SOURCES_DIR}/lib/vauth/${filename} "#include \"../${filename}\"\n")
+      file(WRITE ${CURL_SOURCES_DIR}/lib/vtls/${filename} "#include \"../${filename}\"\n")
+    endforeach()
+  endif()
+
+  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")
+    if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
+      SET(TMP_OS "x86_64")
+    else()
+      SET(TMP_OS "x86")
+    endif()
+
+    set_property(
+      SOURCE ${CURL_SOURCES}
+      PROPERTY COMPILE_DEFINITIONS "HAVE_CONFIG_H=1;OS=\"${TMP_OS}\""
+      )
+   
+    include(${CURL_SOURCES_DIR}/CMake/Macros.cmake)
+
+    # WARNING: Do *not* reorder the "check_include_file_concat()" below!
+    check_include_file_concat("stdio.h"          HAVE_STDIO_H)
+    check_include_file_concat("inttypes.h"       HAVE_INTTYPES_H)
+    check_include_file_concat("sys/filio.h"      HAVE_SYS_FILIO_H)
+    check_include_file_concat("sys/ioctl.h"      HAVE_SYS_IOCTL_H)
+    check_include_file_concat("sys/param.h"      HAVE_SYS_PARAM_H)
+    check_include_file_concat("sys/poll.h"       HAVE_SYS_POLL_H)
+    check_include_file_concat("sys/resource.h"   HAVE_SYS_RESOURCE_H)
+    check_include_file_concat("sys/select.h"     HAVE_SYS_SELECT_H)
+    check_include_file_concat("sys/socket.h"     HAVE_SYS_SOCKET_H)
+    check_include_file_concat("sys/sockio.h"     HAVE_SYS_SOCKIO_H)
+    check_include_file_concat("sys/stat.h"       HAVE_SYS_STAT_H)
+    check_include_file_concat("sys/time.h"       HAVE_SYS_TIME_H)
+    check_include_file_concat("sys/types.h"      HAVE_SYS_TYPES_H)
+    check_include_file_concat("sys/uio.h"        HAVE_SYS_UIO_H)
+    check_include_file_concat("sys/un.h"         HAVE_SYS_UN_H)
+    check_include_file_concat("sys/utime.h"      HAVE_SYS_UTIME_H)
+    check_include_file_concat("sys/xattr.h"      HAVE_SYS_XATTR_H)
+    check_include_file_concat("alloca.h"         HAVE_ALLOCA_H)
+    check_include_file_concat("arpa/inet.h"      HAVE_ARPA_INET_H)
+    check_include_file_concat("arpa/tftp.h"      HAVE_ARPA_TFTP_H)
+    check_include_file_concat("assert.h"         HAVE_ASSERT_H)
+    check_include_file_concat("crypto.h"         HAVE_CRYPTO_H)
+    check_include_file_concat("des.h"            HAVE_DES_H)
+    check_include_file_concat("err.h"            HAVE_ERR_H)
+    check_include_file_concat("errno.h"          HAVE_ERRNO_H)
+    check_include_file_concat("fcntl.h"          HAVE_FCNTL_H)
+    check_include_file_concat("idn2.h"           HAVE_IDN2_H)
+    check_include_file_concat("ifaddrs.h"        HAVE_IFADDRS_H)
+    check_include_file_concat("io.h"             HAVE_IO_H)
+    check_include_file_concat("krb.h"            HAVE_KRB_H)
+    check_include_file_concat("libgen.h"         HAVE_LIBGEN_H)
+    check_include_file_concat("limits.h"         HAVE_LIMITS_H)
+    check_include_file_concat("locale.h"         HAVE_LOCALE_H)
+    check_include_file_concat("net/if.h"         HAVE_NET_IF_H)
+    check_include_file_concat("netdb.h"          HAVE_NETDB_H)
+    check_include_file_concat("netinet/in.h"     HAVE_NETINET_IN_H)
+    check_include_file_concat("netinet/tcp.h"    HAVE_NETINET_TCP_H)
+
+    check_include_file_concat("pem.h"            HAVE_PEM_H)
+    check_include_file_concat("poll.h"           HAVE_POLL_H)
+    check_include_file_concat("pwd.h"            HAVE_PWD_H)
+    check_include_file_concat("rsa.h"            HAVE_RSA_H)
+    check_include_file_concat("setjmp.h"         HAVE_SETJMP_H)
+    check_include_file_concat("sgtty.h"          HAVE_SGTTY_H)
+    check_include_file_concat("signal.h"         HAVE_SIGNAL_H)
+    check_include_file_concat("ssl.h"            HAVE_SSL_H)
+    check_include_file_concat("stdbool.h"        HAVE_STDBOOL_H)
+    check_include_file_concat("stdint.h"         HAVE_STDINT_H)
+    check_include_file_concat("stdio.h"          HAVE_STDIO_H)
+    check_include_file_concat("stdlib.h"         HAVE_STDLIB_H)
+    check_include_file_concat("string.h"         HAVE_STRING_H)
+    check_include_file_concat("strings.h"        HAVE_STRINGS_H)
+    check_include_file_concat("stropts.h"        HAVE_STROPTS_H)
+    check_include_file_concat("termio.h"         HAVE_TERMIO_H)
+    check_include_file_concat("termios.h"        HAVE_TERMIOS_H)
+    check_include_file_concat("time.h"           HAVE_TIME_H)
+    check_include_file_concat("unistd.h"         HAVE_UNISTD_H)
+    check_include_file_concat("utime.h"          HAVE_UTIME_H)
+    check_include_file_concat("x509.h"           HAVE_X509_H)
+
+    check_include_file_concat("process.h"        HAVE_PROCESS_H)
+    check_include_file_concat("stddef.h"         HAVE_STDDEF_H)
+    check_include_file_concat("dlfcn.h"          HAVE_DLFCN_H)
+    check_include_file_concat("malloc.h"         HAVE_MALLOC_H)
+    check_include_file_concat("memory.h"         HAVE_MEMORY_H)
+    check_include_file_concat("netinet/if_ether.h" HAVE_NETINET_IF_ETHER_H)
+    check_include_file_concat("stdint.h"        HAVE_STDINT_H)
+    check_include_file_concat("sockio.h"        HAVE_SOCKIO_H)
+    check_include_file_concat("sys/utsname.h"   HAVE_SYS_UTSNAME_H)
+
+    check_type_size("size_t"  SIZEOF_SIZE_T)
+    check_type_size("ssize_t"  SIZEOF_SSIZE_T)
+    check_type_size("long long"  SIZEOF_LONG_LONG)
+    check_type_size("long"  SIZEOF_LONG)
+    check_type_size("short"  SIZEOF_SHORT)
+    check_type_size("int"  SIZEOF_INT)
+    check_type_size("__int64"  SIZEOF___INT64)
+    check_type_size("long double"  SIZEOF_LONG_DOUBLE)
+    check_type_size("time_t"  SIZEOF_TIME_T)
+    check_type_size("off_t"  SIZEOF_OFF_T)
+    check_type_size("socklen_t" CURL_SIZEOF_CURL_SOCKLEN_T)
+
+    check_symbol_exists(basename      "${CURL_INCLUDES}" HAVE_BASENAME)
+    check_symbol_exists(socket        "${CURL_INCLUDES}" HAVE_SOCKET)
+    # poll on macOS is unreliable, it first did not exist, then was broken until
+    # fixed in 10.9 only to break again in 10.12.
+    if(NOT APPLE)
+      check_symbol_exists(poll        "${CURL_INCLUDES}" HAVE_POLL)
+    endif()
+    check_symbol_exists(select        "${CURL_INCLUDES}" HAVE_SELECT)
+    check_symbol_exists(strdup        "${CURL_INCLUDES}" HAVE_STRDUP)
+    check_symbol_exists(strstr        "${CURL_INCLUDES}" HAVE_STRSTR)
+    check_symbol_exists(strtok_r      "${CURL_INCLUDES}" HAVE_STRTOK_R)
+    check_symbol_exists(strftime      "${CURL_INCLUDES}" HAVE_STRFTIME)
+    check_symbol_exists(uname         "${CURL_INCLUDES}" HAVE_UNAME)
+    check_symbol_exists(strcasecmp    "${CURL_INCLUDES}" HAVE_STRCASECMP)
+    check_symbol_exists(stricmp       "${CURL_INCLUDES}" HAVE_STRICMP)
+    check_symbol_exists(strcmpi       "${CURL_INCLUDES}" HAVE_STRCMPI)
+    check_symbol_exists(strncmpi      "${CURL_INCLUDES}" HAVE_STRNCMPI)
+    check_symbol_exists(alarm         "${CURL_INCLUDES}" HAVE_ALARM)
+    if(NOT HAVE_STRNCMPI)
+      set(HAVE_STRCMPI)
+    endif(NOT HAVE_STRNCMPI)
+
+    check_symbol_exists(gethostbyaddr "${CURL_INCLUDES}" HAVE_GETHOSTBYADDR)
+    check_symbol_exists(gethostbyaddr_r "${CURL_INCLUDES}" HAVE_GETHOSTBYADDR_R)
+    check_symbol_exists(gettimeofday  "${CURL_INCLUDES}" HAVE_GETTIMEOFDAY)
+    check_symbol_exists(inet_addr     "${CURL_INCLUDES}" HAVE_INET_ADDR)
+    check_symbol_exists(inet_ntoa     "${CURL_INCLUDES}" HAVE_INET_NTOA)
+    check_symbol_exists(inet_ntoa_r   "${CURL_INCLUDES}" HAVE_INET_NTOA_R)
+    check_symbol_exists(tcsetattr     "${CURL_INCLUDES}" HAVE_TCSETATTR)
+    check_symbol_exists(tcgetattr     "${CURL_INCLUDES}" HAVE_TCGETATTR)
+    check_symbol_exists(perror        "${CURL_INCLUDES}" HAVE_PERROR)
+    check_symbol_exists(closesocket   "${CURL_INCLUDES}" HAVE_CLOSESOCKET)
+    check_symbol_exists(setvbuf       "${CURL_INCLUDES}" HAVE_SETVBUF)
+    check_symbol_exists(sigsetjmp     "${CURL_INCLUDES}" HAVE_SIGSETJMP)
+    check_symbol_exists(getpass_r     "${CURL_INCLUDES}" HAVE_GETPASS_R)
+    check_symbol_exists(strlcat       "${CURL_INCLUDES}" HAVE_STRLCAT)
+    check_symbol_exists(getpwuid      "${CURL_INCLUDES}" HAVE_GETPWUID)
+    check_symbol_exists(geteuid       "${CURL_INCLUDES}" HAVE_GETEUID)
+    check_symbol_exists(utime         "${CURL_INCLUDES}" HAVE_UTIME)
+    check_symbol_exists(gmtime_r      "${CURL_INCLUDES}" HAVE_GMTIME_R)
+    check_symbol_exists(localtime_r   "${CURL_INCLUDES}" HAVE_LOCALTIME_R)
+
+    check_symbol_exists(gethostbyname   "${CURL_INCLUDES}" HAVE_GETHOSTBYNAME)
+    check_symbol_exists(gethostbyname_r "${CURL_INCLUDES}" HAVE_GETHOSTBYNAME_R)
+
+    check_symbol_exists(signal        "${CURL_INCLUDES}" HAVE_SIGNAL_FUNC)
+    check_symbol_exists(SIGALRM       "${CURL_INCLUDES}" HAVE_SIGNAL_MACRO)
+    if(HAVE_SIGNAL_FUNC AND HAVE_SIGNAL_MACRO)
+      set(HAVE_SIGNAL 1)
+    endif(HAVE_SIGNAL_FUNC AND HAVE_SIGNAL_MACRO)
+    check_symbol_exists(uname          "${CURL_INCLUDES}" HAVE_UNAME)
+    check_symbol_exists(strtoll        "${CURL_INCLUDES}" HAVE_STRTOLL)
+    check_symbol_exists(_strtoi64      "${CURL_INCLUDES}" HAVE__STRTOI64)
+    check_symbol_exists(strerror_r     "${CURL_INCLUDES}" HAVE_STRERROR_R)
+    check_symbol_exists(siginterrupt   "${CURL_INCLUDES}" HAVE_SIGINTERRUPT)
+    check_symbol_exists(perror         "${CURL_INCLUDES}" HAVE_PERROR)
+    check_symbol_exists(fork           "${CURL_INCLUDES}" HAVE_FORK)
+    check_symbol_exists(getaddrinfo    "${CURL_INCLUDES}" HAVE_GETADDRINFO)
+    check_symbol_exists(freeaddrinfo   "${CURL_INCLUDES}" HAVE_FREEADDRINFO)
+    check_symbol_exists(freeifaddrs    "${CURL_INCLUDES}" HAVE_FREEIFADDRS)
+    check_symbol_exists(pipe           "${CURL_INCLUDES}" HAVE_PIPE)
+    check_symbol_exists(ftruncate      "${CURL_INCLUDES}" HAVE_FTRUNCATE)
+    check_symbol_exists(getprotobyname "${CURL_INCLUDES}" HAVE_GETPROTOBYNAME)
+    check_symbol_exists(getrlimit      "${CURL_INCLUDES}" HAVE_GETRLIMIT)
+    check_symbol_exists(setlocale      "${CURL_INCLUDES}" HAVE_SETLOCALE)
+    check_symbol_exists(setmode        "${CURL_INCLUDES}" HAVE_SETMODE)
+    check_symbol_exists(setrlimit      "${CURL_INCLUDES}" HAVE_SETRLIMIT)
+    check_symbol_exists(fcntl          "${CURL_INCLUDES}" HAVE_FCNTL)
+    check_symbol_exists(ioctl          "${CURL_INCLUDES}" HAVE_IOCTL)
+    check_symbol_exists(setsockopt     "${CURL_INCLUDES}" HAVE_SETSOCKOPT)
+
+    if(HAVE_SIZEOF_LONG_LONG)
+      set(HAVE_LONGLONG 1)
+      set(HAVE_LL 1)
+    endif(HAVE_SIZEOF_LONG_LONG)
+
+    check_function_exists(mach_absolute_time HAVE_MACH_ABSOLUTE_TIME)
+    check_function_exists(gethostname HAVE_GETHOSTNAME)
+
+    check_include_file_concat("pthread.h" HAVE_PTHREAD_H)
+    check_symbol_exists(recv "sys/socket.h" HAVE_RECV)
+    check_symbol_exists(send "sys/socket.h" HAVE_SEND)
+
+    check_struct_has_member("struct sockaddr_un" sun_path "sys/un.h" USE_UNIX_SOCKETS)
+
+    set(CMAKE_REQUIRED_INCLUDES "${CURL_SOURCES_DIR}/include")
+    set(CMAKE_EXTRA_INCLUDE_FILES "curl/system.h")
+    check_type_size("curl_off_t"  SIZEOF_CURL_OFF_T)
+
+    add_definitions(-DHAVE_GLIBC_STRERROR_R=1)
+
+    include(${CURL_SOURCES_DIR}/CMake/OtherTests.cmake)
+
+    foreach(CURL_TEST
+        HAVE_FCNTL_O_NONBLOCK
+        HAVE_IOCTLSOCKET
+        HAVE_IOCTLSOCKET_CAMEL
+        HAVE_IOCTLSOCKET_CAMEL_FIONBIO
+        HAVE_IOCTLSOCKET_FIONBIO
+        HAVE_IOCTL_FIONBIO
+        HAVE_IOCTL_SIOCGIFADDR
+        HAVE_SETSOCKOPT_SO_NONBLOCK
+        HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
+        TIME_WITH_SYS_TIME
+        HAVE_O_NONBLOCK
+        HAVE_GETHOSTBYADDR_R_5
+        HAVE_GETHOSTBYADDR_R_7
+        HAVE_GETHOSTBYADDR_R_8
+        HAVE_GETHOSTBYADDR_R_5_REENTRANT
+        HAVE_GETHOSTBYADDR_R_7_REENTRANT
+        HAVE_GETHOSTBYADDR_R_8_REENTRANT
+        HAVE_GETHOSTBYNAME_R_3
+        HAVE_GETHOSTBYNAME_R_5
+        HAVE_GETHOSTBYNAME_R_6
+        HAVE_GETHOSTBYNAME_R_3_REENTRANT
+        HAVE_GETHOSTBYNAME_R_5_REENTRANT
+        HAVE_GETHOSTBYNAME_R_6_REENTRANT
+        HAVE_SOCKLEN_T
+        HAVE_IN_ADDR_T
+        HAVE_BOOL_T
+        STDC_HEADERS
+        RETSIGTYPE_TEST
+        HAVE_INET_NTOA_R_DECL
+        HAVE_INET_NTOA_R_DECL_REENTRANT
+        HAVE_GETADDRINFO
+        HAVE_FILE_OFFSET_BITS
+        )
+      curl_internal_test(${CURL_TEST})
+    endforeach(CURL_TEST)
+
+    configure_file(
+      ${CURL_SOURCES_DIR}/lib/curl_config.h.cmake
+      ${CURL_SOURCES_DIR}/lib/curl_config.h
+      )
+  endif()
+else()
+  include(FindCURL)
+  include_directories(${CURL_INCLUDE_DIRS})
+  link_libraries(${CURL_LIBRARIES})
+
+  if (NOT ${CURL_FOUND})
+    message(FATAL_ERROR "Unable to find LibCurl")
+  endif()
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/CMake/LibJpegConfiguration.cmake	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,95 @@
+if (STATIC_BUILD OR NOT USE_SYSTEM_LIBJPEG)
+  set(LIBJPEG_SOURCES_DIR ${CMAKE_BINARY_DIR}/jpeg-9a)
+  DownloadPackage(
+    "3353992aecaee1805ef4109aadd433e7"
+    "http://www.orthanc-server.com/downloads/third-party/jpegsrc.v9a.tar.gz"
+    "${LIBJPEG_SOURCES_DIR}")
+
+  include_directories(
+    ${LIBJPEG_SOURCES_DIR}
+    )
+
+  list(APPEND LIBJPEG_SOURCES 
+    ${LIBJPEG_SOURCES_DIR}/jaricom.c
+    ${LIBJPEG_SOURCES_DIR}/jcapimin.c
+    ${LIBJPEG_SOURCES_DIR}/jcapistd.c
+    ${LIBJPEG_SOURCES_DIR}/jcarith.c
+    ${LIBJPEG_SOURCES_DIR}/jccoefct.c
+    ${LIBJPEG_SOURCES_DIR}/jccolor.c
+    ${LIBJPEG_SOURCES_DIR}/jcdctmgr.c
+    ${LIBJPEG_SOURCES_DIR}/jchuff.c
+    ${LIBJPEG_SOURCES_DIR}/jcinit.c
+    ${LIBJPEG_SOURCES_DIR}/jcmarker.c
+    ${LIBJPEG_SOURCES_DIR}/jcmaster.c
+    ${LIBJPEG_SOURCES_DIR}/jcomapi.c
+    ${LIBJPEG_SOURCES_DIR}/jcparam.c
+    ${LIBJPEG_SOURCES_DIR}/jcprepct.c
+    ${LIBJPEG_SOURCES_DIR}/jcsample.c
+    ${LIBJPEG_SOURCES_DIR}/jctrans.c
+    ${LIBJPEG_SOURCES_DIR}/jdapimin.c
+    ${LIBJPEG_SOURCES_DIR}/jdapistd.c
+    ${LIBJPEG_SOURCES_DIR}/jdarith.c
+    ${LIBJPEG_SOURCES_DIR}/jdatadst.c
+    ${LIBJPEG_SOURCES_DIR}/jdatasrc.c
+    ${LIBJPEG_SOURCES_DIR}/jdcoefct.c
+    ${LIBJPEG_SOURCES_DIR}/jdcolor.c
+    ${LIBJPEG_SOURCES_DIR}/jddctmgr.c
+    ${LIBJPEG_SOURCES_DIR}/jdhuff.c
+    ${LIBJPEG_SOURCES_DIR}/jdinput.c
+    ${LIBJPEG_SOURCES_DIR}/jcmainct.c
+    ${LIBJPEG_SOURCES_DIR}/jdmainct.c
+    ${LIBJPEG_SOURCES_DIR}/jdmarker.c
+    ${LIBJPEG_SOURCES_DIR}/jdmaster.c
+    ${LIBJPEG_SOURCES_DIR}/jdmerge.c
+    ${LIBJPEG_SOURCES_DIR}/jdpostct.c
+    ${LIBJPEG_SOURCES_DIR}/jdsample.c
+    ${LIBJPEG_SOURCES_DIR}/jdtrans.c
+    ${LIBJPEG_SOURCES_DIR}/jerror.c
+    ${LIBJPEG_SOURCES_DIR}/jfdctflt.c
+    ${LIBJPEG_SOURCES_DIR}/jfdctfst.c
+    ${LIBJPEG_SOURCES_DIR}/jfdctint.c
+    ${LIBJPEG_SOURCES_DIR}/jidctflt.c
+    ${LIBJPEG_SOURCES_DIR}/jidctfst.c
+    ${LIBJPEG_SOURCES_DIR}/jidctint.c
+    #${LIBJPEG_SOURCES_DIR}/jmemansi.c
+    #${LIBJPEG_SOURCES_DIR}/jmemdos.c
+    #${LIBJPEG_SOURCES_DIR}/jmemmac.c
+    ${LIBJPEG_SOURCES_DIR}/jmemmgr.c
+    #${LIBJPEG_SOURCES_DIR}/jmemname.c
+    ${LIBJPEG_SOURCES_DIR}/jmemnobs.c
+    ${LIBJPEG_SOURCES_DIR}/jquant1.c
+    ${LIBJPEG_SOURCES_DIR}/jquant2.c
+    ${LIBJPEG_SOURCES_DIR}/jutils.c
+
+    # ${LIBJPEG_SOURCES_DIR}/rdbmp.c
+    # ${LIBJPEG_SOURCES_DIR}/rdcolmap.c
+    # ${LIBJPEG_SOURCES_DIR}/rdgif.c
+    # ${LIBJPEG_SOURCES_DIR}/rdppm.c
+    # ${LIBJPEG_SOURCES_DIR}/rdrle.c
+    # ${LIBJPEG_SOURCES_DIR}/rdswitch.c
+    # ${LIBJPEG_SOURCES_DIR}/rdtarga.c
+    # ${LIBJPEG_SOURCES_DIR}/transupp.c
+    # ${LIBJPEG_SOURCES_DIR}/wrbmp.c
+    # ${LIBJPEG_SOURCES_DIR}/wrgif.c
+    # ${LIBJPEG_SOURCES_DIR}/wrppm.c
+    # ${LIBJPEG_SOURCES_DIR}/wrrle.c
+    # ${LIBJPEG_SOURCES_DIR}/wrtarga.c
+    )
+
+  configure_file(
+    ${LIBJPEG_SOURCES_DIR}/jconfig.txt
+    ${LIBJPEG_SOURCES_DIR}/jconfig.h COPYONLY
+    )
+
+  source_group(ThirdParty\\libjpeg REGULAR_EXPRESSION ${LIBJPEG_SOURCES_DIR}/.*)
+
+else()
+  include(FindJPEG)
+
+  if (NOT ${JPEG_FOUND})
+    message(FATAL_ERROR "Unable to find libjpeg")
+  endif()
+
+  include_directories(${JPEG_INCLUDE_DIR})
+  link_libraries(${JPEG_LIBRARIES})
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/CMake/LibPngConfiguration.cmake	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,61 @@
+if (STATIC_BUILD OR NOT USE_SYSTEM_LIBPNG)
+  SET(LIBPNG_SOURCES_DIR ${CMAKE_BINARY_DIR}/libpng-1.5.12)
+  SET(LIBPNG_URL "http://www.orthanc-server.com/downloads/third-party/libpng-1.5.12.tar.gz")
+  SET(LIBPNG_MD5 "8ea7f60347a306c5faf70b977fa80e28")
+
+  DownloadPackage(${LIBPNG_MD5} ${LIBPNG_URL} "${LIBPNG_SOURCES_DIR}")
+
+  include_directories(
+    ${LIBPNG_SOURCES_DIR}
+    )
+
+  configure_file(
+    ${LIBPNG_SOURCES_DIR}/scripts/pnglibconf.h.prebuilt
+    ${LIBPNG_SOURCES_DIR}/pnglibconf.h
+    )
+
+  set(LIBPNG_SOURCES
+    #${LIBPNG_SOURCES_DIR}/example.c
+    ${LIBPNG_SOURCES_DIR}/png.c
+    ${LIBPNG_SOURCES_DIR}/pngerror.c
+    ${LIBPNG_SOURCES_DIR}/pngget.c
+    ${LIBPNG_SOURCES_DIR}/pngmem.c
+    ${LIBPNG_SOURCES_DIR}/pngpread.c
+    ${LIBPNG_SOURCES_DIR}/pngread.c
+    ${LIBPNG_SOURCES_DIR}/pngrio.c
+    ${LIBPNG_SOURCES_DIR}/pngrtran.c
+    ${LIBPNG_SOURCES_DIR}/pngrutil.c
+    ${LIBPNG_SOURCES_DIR}/pngset.c
+    #${LIBPNG_SOURCES_DIR}/pngtest.c
+    ${LIBPNG_SOURCES_DIR}/pngtrans.c
+    ${LIBPNG_SOURCES_DIR}/pngwio.c
+    ${LIBPNG_SOURCES_DIR}/pngwrite.c
+    ${LIBPNG_SOURCES_DIR}/pngwtran.c
+    ${LIBPNG_SOURCES_DIR}/pngwutil.c
+    )
+
+  #set_property(
+  #  SOURCE ${LIBPNG_SOURCES}
+  #  PROPERTY COMPILE_FLAGS -UHAVE_CONFIG_H)
+
+  add_definitions(
+    -DPNG_NO_CONSOLE_IO=1
+    -DPNG_NO_STDIO=1
+    # The following declaration avoids "__declspec(dllexport)" in
+    # libpng to prevent publicly exposing its symbols by the DLLs
+    -DPNG_IMPEXP=
+    )
+
+  source_group(ThirdParty\\libpng REGULAR_EXPRESSION ${LIBPNG_SOURCES_DIR}/.*)
+
+else()
+  include(FindPNG)
+
+  if (NOT ${PNG_FOUND})
+    message(FATAL_ERROR "Unable to find libpng")
+  endif()
+
+  include_directories(${PNG_INCLUDE_DIRS})
+  link_libraries(${PNG_LIBRARIES})
+  add_definitions(${PNG_DEFINITIONS})
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/CMake/OpenSslConfiguration.cmake	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,219 @@
+if (STATIC_BUILD OR NOT USE_SYSTEM_OPENSSL)
+  # WARNING - We had to repack the upstream ".tar.gz" file to a ZIP
+  # file, as the upstream distribution ships symbolic links that are
+  # not always properly handled when uncompressing on Windows.
+
+  SET(OPENSSL_SOURCES_DIR ${CMAKE_BINARY_DIR}/openssl-1.0.2d)
+  SET(OPENSSL_URL "http://www.orthanc-server.com/downloads/third-party/openssl-1.0.2d.zip")
+  SET(OPENSSL_MD5 "4b2ac15fc6db17f3dadc54482d3eee85")
+
+  if (IS_DIRECTORY "${OPENSSL_SOURCES_DIR}")
+    set(FirstRun OFF)
+  else()
+    set(FirstRun ON)
+  endif()
+
+  DownloadPackage(${OPENSSL_MD5} ${OPENSSL_URL} "${OPENSSL_SOURCES_DIR}")
+
+  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
+    -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/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_PKCS11)
+    list(APPEND OPENSSL_SOURCES_SUBDIRS
+      # EC, ECDH and ECDSA are necessary for PKCS11
+      ${OPENSSL_SOURCES_DIR}/crypto/ec
+      ${OPENSSL_SOURCES_DIR}/crypto/ecdh
+      ${OPENSSL_SOURCES_DIR}/crypto/ecdsa
+      )
+  else()
+    add_definitions(
+      -DOPENSSL_NO_EC
+      -DOPENSSL_NO_ECDH
+      -DOPENSSL_NO_ECDSA
+      )
+  endif()
+
+  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/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}/crypto/ec/ecp_nistz256_table.c
+
+    ${OPENSSL_SOURCES_DIR}/ssl/heartbeat_test.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")
+  endif()
+
+  source_group(ThirdParty\\OpenSSL REGULAR_EXPRESSION ${OPENSSL_SOURCES_DIR}/.*)
+
+else()
+  include(FindOpenSSL)
+
+  if (NOT ${OPENSSL_FOUND})
+    message(FATAL_ERROR "Unable to find OpenSSL")
+  endif()
+
+  include_directories(${OPENSSL_INCLUDE_DIR})
+  link_libraries(${OPENSSL_LIBRARIES})
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/CMake/OrthancFrameworkConfiguration.cmake	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,532 @@
+##
+## This is a CMake configuration file that configures the core
+## libraries of Orthanc. This file can be used by external projects so
+## as to gain access to the Orthanc APIs (the most prominent examples
+## are currently "Stone of Orthanc" and "Orthanc for whole-slide
+## imaging plugin").
+##
+
+
+#####################################################################
+## Configuration of the components
+#####################################################################
+
+# Path to the root folder of the Orthanc distribution
+set(ORTHANC_ROOT ${CMAKE_CURRENT_LIST_DIR}/../..)
+
+# Some basic inclusions
+include(CMakePushCheckState)
+include(CheckFunctionExists)
+include(CheckIncludeFile)
+include(CheckIncludeFileCXX)
+include(CheckIncludeFiles)
+include(CheckLibraryExists)
+include(CheckStructHasMember)
+include(CheckSymbolExists)
+include(CheckTypeSize)
+include(FindPythonInterp)
+  
+include(${CMAKE_CURRENT_LIST_DIR}/AutoGeneratedCode.cmake)
+include(${CMAKE_CURRENT_LIST_DIR}/DownloadPackage.cmake)
+include(${CMAKE_CURRENT_LIST_DIR}/Compiler.cmake)
+
+
+#####################################################################
+## Disable unneeded macros
+#####################################################################
+
+if (NOT ENABLE_SQLITE)
+  unset(USE_SYSTEM_SQLITE CACHE)
+  add_definitions(-DORTHANC_ENABLE_SQLITE=0)
+endif()
+
+if (NOT ENABLE_CRYPTO_OPTIONS)
+  unset(ENABLE_SSL CACHE)
+  unset(ENABLE_PKCS11 CACHE)
+  unset(USE_SYSTEM_OPENSSL CACHE)
+  unset(USE_SYSTEM_LIBP11 CACHE)
+  add_definitions(
+    -DORTHANC_ENABLE_SSL=0
+    -DORTHANC_ENABLE_PKCS11=0
+    )
+endif()
+
+if (NOT ENABLE_WEB_CLIENT)
+  unset(USE_SYSTEM_CURL CACHE)
+  add_definitions(-DORTHANC_ENABLE_CURL=0)
+endif()
+
+if (NOT ENABLE_WEB_SERVER)
+  unset(ENABLE_CIVETWEB CACHE)
+  unset(USE_SYSTEM_CIVETWEB CACHE)
+  unset(USE_SYSTEM_MONGOOSE CACHE)
+  add_definitions(
+    -DORTHANC_ENABLE_CIVETWEB=0
+    -DORTHANC_ENABLE_MONGOOSE=0
+    )
+endif()
+
+if (NOT ENABLE_JPEG)
+  unset(USE_SYSTEM_LIBJPEG CACHE)
+  add_definitions(-DORTHANC_ENABLE_JPEG=0)
+endif()
+
+if (NOT ENABLE_PNG)
+  unset(USE_SYSTEM_LIBPNG CACHE)
+  add_definitions(-DORTHANC_ENABLE_PNG=0)
+endif()
+
+if (NOT ENABLE_LUA)
+  unset(USE_SYSTEM_LUA CACHE)
+  add_definitions(-DORTHANC_ENABLE_LUA=0)
+endif()
+
+if (NOT ENABLE_PUGIXML)
+  unset(USE_SYSTEM_PUGIXML CACHE)
+  add_definitions(-DORTHANC_ENABLE_PUGIXML=0)
+endif()
+
+if (NOT ENABLE_LOCALE)
+  unset(USE_SYSTEM_LIBICONV CACHE)
+  add_definitions(-DORTHANC_ENABLE_LOCALE=0)
+endif()
+
+if (NOT ENABLE_GOOGLE_TEST)
+  unset(USE_SYSTEM_GOOGLE_TEST CACHE)
+  unset(USE_GOOGLE_TEST_DEBIAN_PACKAGE CACHE)
+endif()
+
+if (NOT ENABLE_DCMTK)
+  add_definitions(
+    -DORTHANC_ENABLE_DCMTK=0
+    -DORTHANC_ENABLE_DCMTK_NETWORKING=0
+    )
+  unset(DCMTK_DICTIONARY_DIR CACHE)
+  unset(USE_DCMTK_360 CACHE)
+  unset(USE_DCMTK_362_PRIVATE_DIC CACHE)
+  unset(USE_SYSTEM_DCMTK CACHE)
+  unset(ENABLE_DCMTK_JPEG CACHE)
+  unset(ENABLE_DCMTK_JPEG_LOSSLESS CACHE)
+endif()
+
+
+#####################################################################
+## List of source files
+#####################################################################
+
+set(ORTHANC_CORE_SOURCES_INTERNAL
+  ${ORTHANC_ROOT}/Core/Cache/MemoryCache.cpp
+  ${ORTHANC_ROOT}/Core/ChunkedBuffer.cpp
+  ${ORTHANC_ROOT}/Core/Compression/DeflateBaseCompressor.cpp
+  ${ORTHANC_ROOT}/Core/Compression/GzipCompressor.cpp
+  ${ORTHANC_ROOT}/Core/Compression/HierarchicalZipWriter.cpp
+  ${ORTHANC_ROOT}/Core/Compression/ZipWriter.cpp
+  ${ORTHANC_ROOT}/Core/Compression/ZlibCompressor.cpp
+  ${ORTHANC_ROOT}/Core/DicomFormat/DicomArray.cpp
+  ${ORTHANC_ROOT}/Core/DicomFormat/DicomImageInformation.cpp
+  ${ORTHANC_ROOT}/Core/DicomFormat/DicomInstanceHasher.cpp
+  ${ORTHANC_ROOT}/Core/DicomFormat/DicomIntegerPixelAccessor.cpp
+  ${ORTHANC_ROOT}/Core/DicomFormat/DicomMap.cpp
+  ${ORTHANC_ROOT}/Core/DicomFormat/DicomTag.cpp
+  ${ORTHANC_ROOT}/Core/DicomFormat/DicomValue.cpp
+  ${ORTHANC_ROOT}/Core/Enumerations.cpp
+  ${ORTHANC_ROOT}/Core/Images/Font.cpp
+  ${ORTHANC_ROOT}/Core/Images/FontRegistry.cpp
+  ${ORTHANC_ROOT}/Core/Images/IImageWriter.cpp
+  ${ORTHANC_ROOT}/Core/Images/Image.cpp
+  ${ORTHANC_ROOT}/Core/Images/ImageAccessor.cpp
+  ${ORTHANC_ROOT}/Core/Images/ImageBuffer.cpp
+  ${ORTHANC_ROOT}/Core/Images/ImageProcessing.cpp
+  ${ORTHANC_ROOT}/Core/Logging.cpp
+  ${ORTHANC_ROOT}/Core/Toolbox.cpp
+  ${ORTHANC_ROOT}/Core/WebServiceParameters.cpp
+  )
+
+
+#####################################################################
+## Configuration of optional third-party dependencies
+#####################################################################
+
+
+##
+## Embedded database: SQLite
+##
+
+if (ENABLE_SQLITE)
+  include(${CMAKE_CURRENT_LIST_DIR}/SQLiteConfiguration.cmake)
+  add_definitions(-DORTHANC_ENABLE_SQLITE=1)
+
+  list(APPEND ORTHANC_CORE_SOURCES_INTERNAL
+    ${ORTHANC_ROOT}/Core/SQLite/Connection.cpp
+    ${ORTHANC_ROOT}/Core/SQLite/FunctionContext.cpp
+    ${ORTHANC_ROOT}/Core/SQLite/Statement.cpp
+    ${ORTHANC_ROOT}/Core/SQLite/StatementId.cpp
+    ${ORTHANC_ROOT}/Core/SQLite/StatementReference.cpp
+    ${ORTHANC_ROOT}/Core/SQLite/Transaction.cpp
+    )
+endif()
+
+
+##
+## Cryptography: OpenSSL and libp11
+## Must be above "ENABLE_WEB_CLIENT" and "ENABLE_WEB_SERVER"
+##
+
+if (ENABLE_CRYPTO_OPTIONS)
+  if (ENABLE_SSL)
+    include(${CMAKE_CURRENT_LIST_DIR}/OpenSslConfiguration.cmake)
+    add_definitions(-DORTHANC_ENABLE_SSL=1)
+  else()
+    unset(USE_SYSTEM_OPENSSL CACHE)
+    add_definitions(-DORTHANC_ENABLE_SSL=0)
+  endif()
+
+  if (ENABLE_PKCS11)
+    if (ENABLE_SSL)
+      include(${CMAKE_CURRENT_LIST_DIR}/LibP11Configuration.cmake)
+
+      add_definitions(-DORTHANC_ENABLE_PKCS11=1)
+      list(APPEND ORTHANC_CORE_SOURCES_INTERNAL
+        ${ORTHANC_ROOT}/Core/Pkcs11.cpp
+        )
+    else()
+      message(FATAL_ERROR "OpenSSL is required to enable PKCS#11 support")
+    endif()
+  else()
+    add_definitions(-DORTHANC_ENABLE_PKCS11=0)  
+  endif()
+endif()
+
+
+##
+## HTTP client: libcurl
+##
+
+if (ENABLE_WEB_CLIENT)
+  include(${CMAKE_CURRENT_LIST_DIR}/LibCurlConfiguration.cmake)
+  add_definitions(-DORTHANC_ENABLE_CURL=1)
+
+  list(APPEND ORTHANC_CORE_SOURCES_INTERNAL
+    ${ORTHANC_ROOT}/Core/HttpClient.cpp
+    )
+endif()
+
+
+##
+## HTTP server: Mongoose 3.8 or Civetweb
+##
+
+if (ENABLE_WEB_SERVER)
+  if (ENABLE_CIVETWEB)
+    include(${CMAKE_CURRENT_LIST_DIR}/CivetwebConfiguration.cmake)
+    add_definitions(
+      -DORTHANC_ENABLE_CIVETWEB=1
+      -DORTHANC_ENABLE_MONGOOSE=0
+      )
+  else()
+    include(${CMAKE_CURRENT_LIST_DIR}/MongooseConfiguration.cmake)
+    add_definitions(
+      -DORTHANC_ENABLE_CIVETWEB=0
+      -DORTHANC_ENABLE_MONGOOSE=1
+      )
+  endif()
+
+  list(APPEND ORTHANC_CORE_SOURCES_INTERNAL
+    ${ORTHANC_ROOT}/Core/HttpServer/BufferHttpSender.cpp
+    ${ORTHANC_ROOT}/Core/HttpServer/FilesystemHttpHandler.cpp
+    ${ORTHANC_ROOT}/Core/HttpServer/FilesystemHttpSender.cpp
+    ${ORTHANC_ROOT}/Core/HttpServer/HttpContentNegociation.cpp
+    ${ORTHANC_ROOT}/Core/HttpServer/HttpFileSender.cpp
+    ${ORTHANC_ROOT}/Core/HttpServer/HttpOutput.cpp
+    ${ORTHANC_ROOT}/Core/HttpServer/HttpStreamTranscoder.cpp
+    ${ORTHANC_ROOT}/Core/HttpServer/HttpToolbox.cpp
+    ${ORTHANC_ROOT}/Core/HttpServer/MongooseServer.cpp
+    ${ORTHANC_ROOT}/Core/HttpServer/StringHttpOutput.cpp
+    ${ORTHANC_ROOT}/Core/RestApi/RestApi.cpp
+    ${ORTHANC_ROOT}/Core/RestApi/RestApiCall.cpp
+    ${ORTHANC_ROOT}/Core/RestApi/RestApiGetCall.cpp
+    ${ORTHANC_ROOT}/Core/RestApi/RestApiHierarchy.cpp
+    ${ORTHANC_ROOT}/Core/RestApi/RestApiOutput.cpp
+    ${ORTHANC_ROOT}/Core/RestApi/RestApiPath.cpp
+    )
+endif()
+
+
+##
+## JPEG support: libjpeg
+##
+
+if (ENABLE_JPEG)
+  include(${CMAKE_CURRENT_LIST_DIR}/LibJpegConfiguration.cmake)
+  add_definitions(-DORTHANC_ENABLE_JPEG=1)
+
+  list(APPEND ORTHANC_CORE_SOURCES_INTERNAL
+    ${ORTHANC_ROOT}/Core/Images/JpegErrorManager.cpp
+    ${ORTHANC_ROOT}/Core/Images/JpegReader.cpp
+    ${ORTHANC_ROOT}/Core/Images/JpegWriter.cpp
+    )
+endif()
+
+
+##
+## PNG support: libpng (in conjunction with zlib)
+##
+
+if (ENABLE_PNG)
+  include(${CMAKE_CURRENT_LIST_DIR}/LibPngConfiguration.cmake)
+  add_definitions(-DORTHANC_ENABLE_PNG=1)
+
+  list(APPEND ORTHANC_CORE_SOURCES_INTERNAL
+    ${ORTHANC_ROOT}/Core/Images/PngReader.cpp
+    ${ORTHANC_ROOT}/Core/Images/PngWriter.cpp
+    )
+endif()
+
+
+##
+## Lua support
+##
+
+if (ENABLE_LUA)
+  include(${CMAKE_CURRENT_LIST_DIR}/LuaConfiguration.cmake)
+  add_definitions(-DORTHANC_ENABLE_LUA=1)
+
+  list(APPEND ORTHANC_CORE_SOURCES_INTERNAL
+    ${ORTHANC_ROOT}/Core/Lua/LuaContext.cpp
+    ${ORTHANC_ROOT}/Core/Lua/LuaFunctionCall.cpp
+    )
+endif()
+
+
+##
+## XML support: pugixml
+##
+
+if (ENABLE_PUGIXML)
+  include(${CMAKE_CURRENT_LIST_DIR}/PugixmlConfiguration.cmake)
+  add_definitions(-DORTHANC_ENABLE_PUGIXML=1)
+endif()
+
+
+##
+## Locale support: libiconv
+##
+
+if (ENABLE_LOCALE)
+  include(${CMAKE_CURRENT_LIST_DIR}/LibIconvConfiguration.cmake)
+  add_definitions(-DORTHANC_ENABLE_LOCALE=1)
+endif()
+
+
+##
+## Google Test for unit testing
+##
+
+if (ENABLE_GOOGLE_TEST)
+  include(${CMAKE_CURRENT_LIST_DIR}/GoogleTestConfiguration.cmake)
+endif()
+
+
+
+#####################################################################
+## Inclusion of mandatory third-party dependencies
+#####################################################################
+
+include(${CMAKE_CURRENT_LIST_DIR}/JsonCppConfiguration.cmake)
+include(${CMAKE_CURRENT_LIST_DIR}/ZlibConfiguration.cmake)
+
+if (NOT ORTHANC_SANDBOXED)
+  include(${CMAKE_CURRENT_LIST_DIR}/UuidConfiguration.cmake)
+endif()
+
+# We put Boost as the last dependency, as it is the heaviest to
+# configure, which allows to quickly spot problems when configuring
+# static builds in other dependencies
+include(${CMAKE_CURRENT_LIST_DIR}/BoostConfiguration.cmake)
+
+
+#####################################################################
+## Optional configuration of DCMTK
+#####################################################################
+
+if (ENABLE_DCMTK)
+  if (NOT ENABLE_LOCALE)
+    message(FATAL_ERROR "Support for locales must be enabled if enabling DICOM support")
+  endif()
+
+  include(${CMAKE_CURRENT_LIST_DIR}/DcmtkConfiguration.cmake)
+
+  add_definitions(-DORTHANC_ENABLE_DCMTK=1)
+
+  if (ENABLE_DCMTK_JPEG)
+    add_definitions(-DORTHANC_ENABLE_DCMTK_JPEG=1)
+  else()
+    add_definitions(-DORTHANC_ENABLE_DCMTK_JPEG=0)
+  endif()
+
+  if (ENABLE_DCMTK_JPEG_LOSSLESS)
+    add_definitions(-DORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS=1)
+  else()
+    add_definitions(-DORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS=0)
+  endif()
+
+  set(ORTHANC_DICOM_SOURCES_INTERNAL
+    ${ORTHANC_ROOT}/Core/DicomParsing/DicomDirWriter.cpp
+    ${ORTHANC_ROOT}/Core/DicomParsing/DicomModification.cpp
+    ${ORTHANC_ROOT}/Core/DicomParsing/FromDcmtkBridge.cpp
+    ${ORTHANC_ROOT}/Core/DicomParsing/ParsedDicomFile.cpp
+    ${ORTHANC_ROOT}/Core/DicomParsing/ToDcmtkBridge.cpp
+
+    ${ORTHANC_ROOT}/Core/DicomParsing/Internals/DicomFrameIndex.cpp
+    ${ORTHANC_ROOT}/Core/DicomParsing/Internals/DicomImageDecoder.cpp
+    )
+
+  if (ENABLE_DCMTK_NETWORKING)
+    add_definitions(-DORTHANC_ENABLE_DCMTK_NETWORKING=1)
+    list(APPEND ORTHANC_DICOM_SOURCES_INTERNAL
+      ${ORTHANC_ROOT}/Core/DicomNetworking/DicomFindAnswers.cpp
+      ${ORTHANC_ROOT}/Core/DicomNetworking/DicomServer.cpp
+      ${ORTHANC_ROOT}/Core/DicomNetworking/DicomUserConnection.cpp
+      ${ORTHANC_ROOT}/Core/DicomNetworking/RemoteModalityParameters.cpp
+      ${ORTHANC_ROOT}/Core/DicomNetworking/ReusableDicomUserConnection.cpp
+
+      ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/CommandDispatcher.cpp
+      ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/FindScp.cpp
+      ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/MoveScp.cpp
+      ${ORTHANC_ROOT}/Core/DicomNetworking/Internals/StoreScp.cpp
+      )
+  else()
+    add_definitions(-DORTHANC_ENABLE_DCMTK_NETWORKING=0)
+  endif()
+
+  if (STANDALONE_BUILD AND NOT HAS_EMBEDDED_RESOURCES)
+    EmbedResources(
+      ${DCMTK_DICTIONARIES}
+      )
+    list(APPEND ORTHANC_DICOM_SOURCES_DEPENDENCIES
+      ${AUTOGENERATED_SOURCES}
+      )
+  endif()
+endif()
+
+
+#####################################################################
+## Configuration of the C/C++ macros
+#####################################################################
+
+add_definitions(
+  -DORTHANC_API_VERSION="${ORTHANC_API_VERSION}"
+  -DORTHANC_DATABASE_VERSION=${ORTHANC_DATABASE_VERSION}
+  -DORTHANC_DEFAULT_DICOM_ENCODING=Encoding_Latin1
+  -DORTHANC_ENABLE_BASE64=1
+  -DORTHANC_ENABLE_MD5=1
+  -DORTHANC_MAXIMUM_TAG_LENGTH=256
+  -DORTHANC_VERSION="${ORTHANC_VERSION}"
+  )
+
+
+if (ORTHANC_SANDBOXED)
+  add_definitions(
+    -DORTHANC_SANDBOXED=1
+    -DORTHANC_ENABLE_LOGGING_PLUGIN=0
+    )
+
+  if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
+    add_definitions(
+      -DORTHANC_ENABLE_LOGGING=1
+      -DORTHANC_ENABLE_LOGGING_STDIO=1
+      )
+  else()
+    add_definitions(
+      -DORTHANC_ENABLE_LOGGING=0
+      )
+  endif()
+  
+else()
+  add_definitions(
+    -DORTHANC_SANDBOXED=0
+    -DORTHANC_ENABLE_LOGGING=1
+    -DORTHANC_ENABLE_LOGGING_PLUGIN=0
+    -DORTHANC_ENABLE_LOGGING_STDIO=0
+    )
+  
+  list(APPEND ORTHANC_CORE_SOURCES_INTERNAL
+    ${ORTHANC_ROOT}/Core/Cache/SharedArchive.cpp
+    ${ORTHANC_ROOT}/Core/FileStorage/FilesystemStorage.cpp
+    ${ORTHANC_ROOT}/Core/FileStorage/StorageAccessor.cpp
+    ${ORTHANC_ROOT}/Core/MultiThreading/BagOfTasksProcessor.cpp
+    ${ORTHANC_ROOT}/Core/MultiThreading/Mutex.cpp
+    ${ORTHANC_ROOT}/Core/MultiThreading/ReaderWriterLock.cpp
+    ${ORTHANC_ROOT}/Core/MultiThreading/RunnableWorkersPool.cpp
+    ${ORTHANC_ROOT}/Core/MultiThreading/Semaphore.cpp
+    ${ORTHANC_ROOT}/Core/MultiThreading/SharedMessageQueue.cpp
+    ${ORTHANC_ROOT}/Core/SharedLibrary.cpp
+    ${ORTHANC_ROOT}/Core/SystemToolbox.cpp
+    ${ORTHANC_ROOT}/Core/TemporaryFile.cpp
+    )
+endif()
+
+
+if (HAS_EMBEDDED_RESOURCES)
+  add_definitions(-DORTHANC_HAS_EMBEDDED_RESOURCES=1)
+
+  list(APPEND ORTHANC_CORE_SOURCES_INTERNAL
+    ${ORTHANC_ROOT}/Core/HttpServer/EmbeddedResourceHttpHandler.cpp
+    )
+else()
+  add_definitions(-DORTHANC_HAS_EMBEDDED_RESOURCES=0)
+endif()
+
+
+#####################################################################
+## Gathering of all the source code
+#####################################################################
+
+# The "xxx_INTERNAL" variables list the source code that belongs to
+# the Orthanc project. It can be used to configure precompiled headers
+# if using Microsoft Visual Studio.
+
+# The "xxx_DEPENDENCIES" variables list the source code coming from
+# third-party dependencies.
+
+
+set(ORTHANC_CORE_SOURCES_DEPENDENCIES
+  ${BOOST_SOURCES}
+  ${CIVETWEB_SOURCES}
+  ${CURL_SOURCES}
+  ${JSONCPP_SOURCES}
+  ${LIBICONV_SOURCES}
+  ${LIBJPEG_SOURCES}
+  ${LIBP11_SOURCES}
+  ${LIBPNG_SOURCES}
+  ${LUA_SOURCES}
+  ${MONGOOSE_SOURCES}
+  ${OPENSSL_SOURCES}
+  ${PUGIXML_SOURCES}
+  ${SQLITE_SOURCES}
+  ${UUID_SOURCES}
+  ${ZLIB_SOURCES}
+
+  ${ORTHANC_ROOT}/Resources/ThirdParty/md5/md5.c
+  ${ORTHANC_ROOT}/Resources/ThirdParty/base64/base64.cpp
+
+  # This is the minizip distribution to create ZIP files using zlib
+  ${ORTHANC_ROOT}/Resources/ThirdParty/minizip/ioapi.c
+  ${ORTHANC_ROOT}/Resources/ThirdParty/minizip/zip.c
+  )  
+
+set(ORTHANC_CORE_SOURCES
+  ${ORTHANC_CORE_SOURCES_INTERNAL}
+  ${ORTHANC_CORE_SOURCES_DEPENDENCIES}
+  )
+
+if (ENABLE_DCMTK)
+  list(APPEND ORTHANC_DICOM_SOURCES_DEPENDENCIES
+    ${DCMTK_SOURCES}
+    )
+  
+  set(ORTHANC_DICOM_SOURCES
+    ${ORTHANC_DICOM_SOURCES_INTERNAL}
+    ${ORTHANC_DICOM_SOURCES_DEPENDENCIES}
+    )
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/CMake/OrthancFrameworkParameters.cmake	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,105 @@
+#####################################################################
+## Versioning information
+#####################################################################
+
+# Version of the build, should always be "mainline" except in release branches
+set(ORTHANC_VERSION "mainline")
+
+# Version of the database schema. History:
+#   * Orthanc 0.1.0 -> Orthanc 0.3.0 = no versioning
+#   * Orthanc 0.3.1                  = version 2
+#   * Orthanc 0.4.0 -> Orthanc 0.7.2 = version 3
+#   * Orthanc 0.7.3 -> Orthanc 0.8.4 = version 4
+#   * Orthanc 0.8.5 -> Orthanc 0.9.4 = version 5
+#   * Orthanc 0.9.5 -> mainline      = version 6
+set(ORTHANC_DATABASE_VERSION 6)
+
+# 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 "1.0")
+
+
+#####################################################################
+## CMake parameters tunable by the user
+#####################################################################
+
+# Support of static compilation
+set(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages")
+set(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)")
+set(STANDALONE_BUILD ON CACHE BOOL "Standalone build (all the resources are embedded, necessary for releases)")
+
+# Generic parameters of the build
+set(ENABLE_CIVETWEB OFF CACHE BOOL "Use Civetweb instead of Mongoose (experimental)")
+set(ENABLE_PKCS11 OFF CACHE BOOL "Enable PKCS#11 for HTTPS client authentication using hardware security modules and smart cards")
+set(ENABLE_PROFILING OFF CACHE BOOL "Whether to enable the generation of profiling information with gprof")
+set(ENABLE_SSL ON CACHE BOOL "Include support for SSL")
+set(ENABLE_LUA_MODULES OFF CACHE BOOL "Enable support for loading external Lua modules (only meaningful if using static version of the Lua engine)")
+
+# Parameters to fine-tune linking against system libraries
+set(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost")
+set(USE_SYSTEM_CIVETWEB ON CACHE BOOL "Use the system version of Civetweb (experimental)")
+set(USE_SYSTEM_CURL ON CACHE BOOL "Use the system version of LibCurl")
+set(USE_SYSTEM_GOOGLE_TEST ON CACHE BOOL "Use the system version of Google Test")
+set(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp")
+set(USE_SYSTEM_LIBICONV ON CACHE BOOL "Use the system version of libiconv")
+set(USE_SYSTEM_LIBJPEG ON CACHE BOOL "Use the system version of libjpeg")
+set(USE_SYSTEM_LIBP11 OFF CACHE BOOL "Use the system version of libp11 (PKCS#11 wrapper library)")
+set(USE_SYSTEM_LIBPNG ON CACHE BOOL "Use the system version of libpng")
+set(USE_SYSTEM_LUA ON CACHE BOOL "Use the system version of Lua")
+set(USE_SYSTEM_MONGOOSE ON CACHE BOOL "Use the system version of Mongoose")
+set(USE_SYSTEM_OPENSSL ON CACHE BOOL "Use the system version of OpenSSL")
+set(USE_SYSTEM_PUGIXML ON CACHE BOOL "Use the system version of Pugixml")
+set(USE_SYSTEM_SQLITE ON CACHE BOOL "Use the system version of SQLite")
+set(USE_SYSTEM_UUID ON CACHE BOOL "Use the system version of the uuid library from e2fsprogs")
+set(USE_SYSTEM_ZLIB ON CACHE BOOL "Use the system version of ZLib")
+
+# 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(USE_DCMTK_360 OFF CACHE BOOL "Use older DCMTK version 3.6.0 in static builds (instead of default 3.6.2)")
+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_JPEG ON CACHE BOOL "Enable JPEG-LS (Lossless) decompression")
+set(ENABLE_DCMTK_JPEG_LOSSLESS ON CACHE BOOL "Enable JPEG-LS (Lossless) decompression")
+
+# Advanced and distribution-specific parameters
+set(USE_GOOGLE_TEST_DEBIAN_PACKAGE OFF CACHE BOOL "Use the sources of Google Test shipped with libgtest-dev (Debian only)")
+set(SYSTEM_MONGOOSE_USE_CALLBACKS ON CACHE BOOL "The system version of Mongoose uses callbacks (version >= 3.7)")
+set(USE_BOOST_ICONV ON CACHE BOOL "Use iconv instead of wconv (Windows only)")
+set(USE_PUGIXML ON CACHE BOOL "Use the Pugixml parser (turn off only for debug)")
+set(USE_LEGACY_JSONCPP OFF CACHE BOOL "Use the old branch 0.x.y of JsonCpp, that does not require a C++11 compiler (for old versions of Visual Studio)")
+
+mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE)
+mark_as_advanced(SYSTEM_MONGOOSE_USE_CALLBACKS)
+mark_as_advanced(USE_BOOST_ICONV)
+mark_as_advanced(USE_PUGIXML)
+mark_as_advanced(USE_LEGACY_JSONCPP)
+
+
+#####################################################################
+## Internal CMake parameters to enable the optional subcomponents of
+## the Orthanc framework
+#####################################################################
+
+# These options must be set to "ON" if compiling Orthanc, but might be
+# set to "OFF" by third-party projects if their associated features
+# are not required
+
+set(ENABLE_CRYPTO_OPTIONS OFF CACHE INTERNAL "Show options related to cryptography")
+set(ENABLE_JPEG OFF CACHE INTERNAL "Enable support of JPEG")
+set(ENABLE_GOOGLE_TEST OFF CACHE INTERNAL "Enable support of Google Test")
+set(ENABLE_LOCALE OFF CACHE INTERNAL "Enable support for locales (notably in Boost)")
+set(ENABLE_LUA OFF CACHE INTERNAL "Enable support of Lua scripting")
+set(ENABLE_PNG OFF CACHE INTERNAL "Enable support of PNG")
+set(ENABLE_PUGIXML OFF CACHE INTERNAL "Enable support of XML through Pugixml")
+set(ENABLE_SQLITE OFF CACHE INTERNAL "Enable support of SQLite databases")
+set(ENABLE_WEB_CLIENT OFF CACHE INTERNAL "Enable Web client")
+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(HAS_EMBEDDED_RESOURCES OFF CACHE INTERNAL
+  "Whether resources are auto-generated using EmbedResources.py")
+
+set(ORTHANC_SANDBOXED OFF CACHE INTERNAL
+  "Whether Orthanc runs inside a sandboxed environment (such as Google NaCl or WebAssembly)")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/CMake/UuidConfiguration.cmake	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,104 @@
+if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+
+  if (STATIC_BUILD OR NOT USE_SYSTEM_UUID)
+    SET(E2FSPROGS_SOURCES_DIR ${CMAKE_BINARY_DIR}/e2fsprogs-1.43.8)
+    SET(E2FSPROGS_URL "http://www.orthanc-server.com/downloads/third-party/e2fsprogs-1.43.8.tar.gz")
+    SET(E2FSPROGS_MD5 "670b7a74a8ead5333acf21b9afc92b3c")
+
+    DownloadPackage(${E2FSPROGS_MD5} ${E2FSPROGS_URL} "${E2FSPROGS_SOURCES_DIR}")
+
+    include_directories(
+      ${E2FSPROGS_SOURCES_DIR}/lib
+      )
+
+    set(UUID_SOURCES
+      #${E2FSPROGS_SOURCES_DIR}/lib/uuid/tst_uuid.c
+      #${E2FSPROGS_SOURCES_DIR}/lib/uuid/uuid_time.c
+      ${E2FSPROGS_SOURCES_DIR}/lib/uuid/clear.c
+      ${E2FSPROGS_SOURCES_DIR}/lib/uuid/compare.c
+      ${E2FSPROGS_SOURCES_DIR}/lib/uuid/copy.c
+      ${E2FSPROGS_SOURCES_DIR}/lib/uuid/gen_uuid.c
+      ${E2FSPROGS_SOURCES_DIR}/lib/uuid/isnull.c
+      ${E2FSPROGS_SOURCES_DIR}/lib/uuid/pack.c
+      ${E2FSPROGS_SOURCES_DIR}/lib/uuid/parse.c
+      ${E2FSPROGS_SOURCES_DIR}/lib/uuid/unpack.c
+      ${E2FSPROGS_SOURCES_DIR}/lib/uuid/unparse.c
+      )
+
+    check_include_file("net/if.h"       HAVE_NET_IF_H)
+    check_include_file("net/if_dl.h"    HAVE_NET_IF_DL_H)
+    check_include_file("netinet/in.h"   HAVE_NETINET_IN_H)
+    check_include_file("stdlib.h"       HAVE_STDLIB_H)
+    check_include_file("sys/file.h"     HAVE_SYS_FILE_H)
+    check_include_file("sys/ioctl.h"    HAVE_SYS_IOCTL_H)
+    check_include_file("sys/resource.h" HAVE_SYS_RESOURCE_H)
+    check_include_file("sys/socket.h"   HAVE_SYS_SOCKET_H)
+    check_include_file("sys/sockio.h"   HAVE_SYS_SOCKIO_H)
+    check_include_file("sys/syscall.h"  HAVE_SYS_SYSCALL_H)
+    check_include_file("sys/time.h"     HAVE_SYS_TIME_H)
+    check_include_file("sys/un.h"       HAVE_SYS_UN_H)
+    check_include_file("unistd.h"       HAVE_UNISTD_H)
+
+    If (NOT HAVE_NET_IF_H)  # This is the case of OpenBSD
+      unset(HAVE_NET_IF_H CACHE)
+      check_include_files("sys/socket.h;net/if.h" HAVE_NET_IF_H)
+    endif()
+
+    if (NOT HAVE_NETINET_TCP_H)  # This is the case of OpenBSD
+      unset(HAVE_NETINET_TCP_H CACHE)
+      check_include_files("sys/socket.h;netinet/tcp.h" HAVE_NETINET_TCP_H)
+    endif()
+
+    file(WRITE ${E2FSPROGS_SOURCES_DIR}/lib/uuid/config.h.cmake "
+#cmakedefine HAVE_NET_IF_H \@HAVE_NET_IF_H\@
+#cmakedefine HAVE_NET_IF_DL_H \@HAVE_NET_IF_DL_H\@
+#cmakedefine HAVE_NETINET_IN_H \@HAVE_NETINET_IN_H\@
+#cmakedefine HAVE_STDLIB_H \@HAVE_STDLIB_H \@
+#cmakedefine HAVE_SYS_FILE_H \@HAVE_SYS_FILE_H\@
+#cmakedefine HAVE_SYS_IOCTL_H \@HAVE_SYS_IOCTL_H\@
+#cmakedefine HAVE_SYS_RESOURCE_H \@HAVE_SYS_RESOURCE_H\@
+#cmakedefine HAVE_SYS_SOCKET_H \@HAVE_SYS_SOCKET_H\@
+#cmakedefine HAVE_SYS_SOCKIO_H \@HAVE_SYS_SOCKIO_H\@
+#cmakedefine HAVE_SYS_SYSCALL_H \@HAVE_SYS_SYSCALL_H\@
+#cmakedefine HAVE_SYS_TIME_H \@HAVE_SYS_TIME_H\@
+#cmakedefine HAVE_SYS_UN_H \@HAVE_SYS_UN_H\@
+#cmakedefine HAVE_UNISTD_H \@HAVE_UNISTD_H\@
+")
+    
+    configure_file(
+      ${E2FSPROGS_SOURCES_DIR}/lib/uuid/config.h.cmake
+      ${E2FSPROGS_SOURCES_DIR}/lib/uuid/config.h
+      )
+    
+    
+    configure_file(
+      ${E2FSPROGS_SOURCES_DIR}/lib/uuid/uuid.h.in
+      ${E2FSPROGS_SOURCES_DIR}/lib/uuid/uuid.h
+      )
+
+    file(WRITE
+      ${E2FSPROGS_SOURCES_DIR}/lib/uuid/uuid_types.h
+      "#include <stdint.h>\n")
+
+    #configure_file(
+    #  ${E2FSPROGS_SOURCES_DIR}/lib/uuid/uuid_types.h.in
+    #  ${E2FSPROGS_SOURCES_DIR}/lib/uuid/uuid_types.h
+    #  )
+    
+    source_group(ThirdParty\\uuid REGULAR_EXPRESSION ${E2FSPROGS_SOURCES_DIR}/.*)
+
+  else()
+    CHECK_INCLUDE_FILE(uuid/uuid.h HAVE_UUID_H)
+    if (NOT HAVE_UUID_H)
+      message(FATAL_ERROR "Please install uuid-dev, e2fsprogs (OpenBSD) or e2fsprogs-libuuid (FreeBSD)")
+    endif()
+
+    check_library_exists(uuid uuid_generate_random "" HAVE_UUID_LIB)
+    if (NOT HAVE_UUID_LIB)
+      message(FATAL_ERROR "Unable to find the uuid library")
+    endif()
+    
+    link_libraries(uuid)
+  endif()
+
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/CMake/ZlibConfiguration.cmake	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,45 @@
+if (STATIC_BUILD OR NOT USE_SYSTEM_ZLIB)
+  SET(ZLIB_SOURCES_DIR ${CMAKE_BINARY_DIR}/zlib-1.2.7)
+  SET(ZLIB_URL "http://www.orthanc-server.com/downloads/third-party/zlib-1.2.7.tar.gz")
+  SET(ZLIB_MD5 "60df6a37c56e7c1366cca812414f7b85")
+
+  DownloadPackage(${ZLIB_MD5} ${ZLIB_URL} "${ZLIB_SOURCES_DIR}")
+
+  include_directories(
+    ${ZLIB_SOURCES_DIR}
+    )
+
+  list(APPEND ZLIB_SOURCES 
+    ${ZLIB_SOURCES_DIR}/adler32.c
+    ${ZLIB_SOURCES_DIR}/compress.c
+    ${ZLIB_SOURCES_DIR}/crc32.c 
+    ${ZLIB_SOURCES_DIR}/deflate.c 
+    ${ZLIB_SOURCES_DIR}/gzclose.c 
+    ${ZLIB_SOURCES_DIR}/gzlib.c 
+    ${ZLIB_SOURCES_DIR}/gzread.c 
+    ${ZLIB_SOURCES_DIR}/gzwrite.c 
+    ${ZLIB_SOURCES_DIR}/infback.c 
+    ${ZLIB_SOURCES_DIR}/inffast.c 
+    ${ZLIB_SOURCES_DIR}/inflate.c 
+    ${ZLIB_SOURCES_DIR}/inftrees.c 
+    ${ZLIB_SOURCES_DIR}/trees.c 
+    ${ZLIB_SOURCES_DIR}/uncompr.c 
+    ${ZLIB_SOURCES_DIR}/zutil.c
+    )
+
+  source_group(ThirdParty\\zlib REGULAR_EXPRESSION ${ZLIB_SOURCES_DIR}/.*)
+
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
+    # "ioapi.c" from zlib (minizip) expects the "IOAPI_NO_64" macro to be set to "true"
+    # https://ohse.de/uwe/articles/lfs.html
+    add_definitions(
+      -DIOAPI_NO_64=1
+      )
+  endif()
+
+else()
+  include(FindZLIB)
+  include_directories(${ZLIB_INCLUDE_DIRS})
+  link_libraries(${ZLIB_LIBRARIES})
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/EmbedResources.py	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,431 @@
+#!/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-2018 Osimis S.A., 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 sys
+import os
+import os.path
+import pprint
+import re
+
+UPCASE_CHECK = True
+USE_SYSTEM_EXCEPTION = False
+EXCEPTION_CLASS = 'OrthancException'
+OUT_OF_RANGE_EXCEPTION = 'OrthancException(ErrorCode_ParameterOutOfRange)'
+INEXISTENT_PATH_EXCEPTION = 'OrthancException(ErrorCode_InexistentItem)'
+NAMESPACE = 'Orthanc'
+
+ARGS = []
+for i in range(len(sys.argv)):
+    if not sys.argv[i].startswith('--'):
+        ARGS.append(sys.argv[i])
+    elif sys.argv[i].lower() == '--no-upcase-check':
+        UPCASE_CHECK = False
+    elif sys.argv[i].lower() == '--system-exception':
+        USE_SYSTEM_EXCEPTION = True
+        EXCEPTION_CLASS = '::std::runtime_error'
+        OUT_OF_RANGE_EXCEPTION = '%s("Parameter out of range")' % EXCEPTION_CLASS
+        INEXISTENT_PATH_EXCEPTION = '%s("Unknown path in a directory resource")' % EXCEPTION_CLASS
+    elif sys.argv[i].startswith('--namespace='):
+        NAMESPACE = sys.argv[i][sys.argv[i].find('=') + 1 : ]
+
+if len(ARGS) < 2 or len(ARGS) % 2 != 0:
+    print ('Usage:')
+    print ('python %s [--no-upcase-check] [--system-exception] [--namespace=<Namespace>] <TargetBaseFilename> [ <Name> <Source> ]*' % sys.argv[0])
+    exit(-1)
+
+TARGET_BASE_FILENAME = ARGS[1]
+SOURCES = ARGS[2:]
+
+try:
+    # Make sure the destination directory exists
+    os.makedirs(os.path.normpath(os.path.join(TARGET_BASE_FILENAME, '..')))
+except:
+    pass
+
+
+#####################################################################
+## Read each resource file
+#####################################################################
+
+def CheckNoUpcase(s):
+    global UPCASE_CHECK
+    if (UPCASE_CHECK and
+        re.search('[A-Z]', s) != None):
+        raise Exception("Path in a directory with an upcase letter: %s" % s)
+
+resources = {}
+
+counter = 0
+i = 0
+while i < len(SOURCES):
+    resourceName = SOURCES[i].upper()
+    pathName = SOURCES[i + 1]
+
+    if not os.path.exists(pathName):
+        raise Exception("Non existing path: %s" % pathName)
+
+    if resourceName in resources:
+        raise Exception("Twice the same resource: " + resourceName)
+    
+    if os.path.isdir(pathName):
+        # The resource is a directory: Recursively explore its files
+        content = {}
+        for root, dirs, files in os.walk(pathName):
+            base = os.path.relpath(root, pathName)
+
+            # Fix issue #24 (Build fails on OSX when directory has .DS_Store files):
+            # Ignore folders whose name starts with a dot (".")
+            if base.find('/.') != -1:
+                print('Ignoring folder: %s' % root)
+                continue
+
+            for f in files:
+                if f.find('~') == -1:  # Ignore Emacs backup files
+                    if base == '.':
+                        r = f
+                    else:
+                        r = os.path.join(base, f)
+
+                    CheckNoUpcase(r)
+                    r = '/' + r.replace('\\', '/')
+                    if r in content:
+                        raise Exception("Twice the same filename (check case): " + r)
+
+                    content[r] = {
+                        'Filename' : os.path.join(root, f),
+                        'Index' : counter
+                        }
+                    counter += 1
+
+        resources[resourceName] = {
+            'Type' : 'Directory',
+            'Files' : content
+            }
+
+    elif os.path.isfile(pathName):
+        resources[resourceName] = {
+            'Type' : 'File',
+            'Index' : counter,
+            'Filename' : pathName
+            }
+        counter += 1
+
+    else:
+        raise Exception("Not a regular file, nor a directory: " + pathName)
+
+    i += 2
+
+#pprint.pprint(resources)
+
+
+#####################################################################
+## Write .h header
+#####################################################################
+
+header = open(TARGET_BASE_FILENAME + '.h', 'w')
+
+header.write("""
+#pragma once
+
+#include <string>
+#include <list>
+
+#if defined(_MSC_VER)
+#  pragma warning(disable: 4065)  // "Switch statement contains 'default' but no 'case' labels"
+#endif
+
+namespace %s
+{
+  namespace EmbeddedResources
+  {
+    enum FileResourceId
+    {
+""" % NAMESPACE)
+
+isFirst = True
+for name in resources:
+    if resources[name]['Type'] == 'File':
+        if isFirst:
+            isFirst = False
+        else:    
+            header.write(',\n')
+        header.write('      %s' % name)
+
+header.write("""
+    };
+
+    enum DirectoryResourceId
+    {
+""")
+
+isFirst = True
+for name in resources:
+    if resources[name]['Type'] == 'Directory':
+        if isFirst:
+            isFirst = False
+        else:    
+            header.write(',\n')
+        header.write('      %s' % name)
+
+header.write("""
+    };
+
+    const void* GetFileResourceBuffer(FileResourceId id);
+    size_t GetFileResourceSize(FileResourceId id);
+    void GetFileResource(std::string& result, FileResourceId id);
+
+    const void* GetDirectoryResourceBuffer(DirectoryResourceId id, const char* path);
+    size_t GetDirectoryResourceSize(DirectoryResourceId id, const char* path);
+    void GetDirectoryResource(std::string& result, DirectoryResourceId id, const char* path);
+
+    void ListResources(std::list<std::string>& result, DirectoryResourceId id);
+  }
+}
+""")
+header.close()
+
+
+
+#####################################################################
+## Write the resource content in the .cpp source
+#####################################################################
+
+PYTHON_MAJOR_VERSION = sys.version_info[0]
+
+def WriteResource(cpp, item):
+    cpp.write('    static const uint8_t resource%dBuffer[] = {' % item['Index'])
+
+    f = open(item['Filename'], "rb")
+    content = f.read()
+    f.close()
+
+    # http://stackoverflow.com/a/1035360
+    pos = 0
+    for b in content:
+        if PYTHON_MAJOR_VERSION == 2:
+            c = ord(b[0])
+        else:
+            c = b
+
+        if pos > 0:
+            cpp.write(', ')
+
+        if (pos % 16) == 0:
+            cpp.write('\n    ')
+
+        if c < 0:
+            raise Exception("Internal error")
+
+        cpp.write("0x%02x" % c)
+        pos += 1
+
+    # Zero-size array are disallowed, so we put one single void character in it.
+    if pos == 0:
+        cpp.write('  0')
+
+    cpp.write('  };\n')
+    cpp.write('    static const size_t resource%dSize = %d;\n' % (item['Index'], pos))
+
+
+cpp = open(TARGET_BASE_FILENAME + '.cpp', 'w')
+
+cpp.write('#include "%s.h"\n' % os.path.basename(TARGET_BASE_FILENAME))
+
+if USE_SYSTEM_EXCEPTION:
+    cpp.write('#include <stdexcept>')
+else:
+    cpp.write('#include "%s/Core/OrthancException.h"' % os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
+
+cpp.write("""
+#include <stdint.h>
+#include <string.h>
+
+namespace %s
+{
+  namespace EmbeddedResources
+  {
+""" % NAMESPACE)
+
+
+for name in resources:
+    if resources[name]['Type'] == 'File':
+        WriteResource(cpp, resources[name])
+    else:
+        for f in resources[name]['Files']:
+            WriteResource(cpp, resources[name]['Files'][f])
+
+
+
+#####################################################################
+## Write the accessors to the file resources in .cpp
+#####################################################################
+
+cpp.write("""
+    const void* GetFileResourceBuffer(FileResourceId id)
+    {
+      switch (id)
+      {
+""")
+for name in resources:
+    if resources[name]['Type'] == 'File':
+        cpp.write('      case %s:\n' % name)
+        cpp.write('        return resource%dBuffer;\n' % resources[name]['Index'])
+
+cpp.write("""
+      default:
+        throw %s;
+      }
+    }
+
+    size_t GetFileResourceSize(FileResourceId id)
+    {
+      switch (id)
+      {
+""" % OUT_OF_RANGE_EXCEPTION)
+
+for name in resources:
+    if resources[name]['Type'] == 'File':
+        cpp.write('      case %s:\n' % name)
+        cpp.write('        return resource%dSize;\n' % resources[name]['Index'])
+
+cpp.write("""
+      default:
+        throw %s;
+      }
+    }
+""" % OUT_OF_RANGE_EXCEPTION)
+
+
+
+#####################################################################
+## Write the accessors to the directory resources in .cpp
+#####################################################################
+
+cpp.write("""
+    const void* GetDirectoryResourceBuffer(DirectoryResourceId id, const char* path)
+    {
+      switch (id)
+      {
+""")
+
+for name in resources:
+    if resources[name]['Type'] == 'Directory':
+        cpp.write('      case %s:\n' % name)
+        isFirst = True
+        for path in resources[name]['Files']:
+            cpp.write('        if (!strcmp(path, "%s"))\n' % path)
+            cpp.write('          return resource%dBuffer;\n' % resources[name]['Files'][path]['Index'])
+        cpp.write('        throw %s;\n\n' % INEXISTENT_PATH_EXCEPTION)
+
+cpp.write("""      default:
+        throw %s;
+      }
+    }
+
+    size_t GetDirectoryResourceSize(DirectoryResourceId id, const char* path)
+    {
+      switch (id)
+      {
+""" % OUT_OF_RANGE_EXCEPTION)
+
+for name in resources:
+    if resources[name]['Type'] == 'Directory':
+        cpp.write('      case %s:\n' % name)
+        isFirst = True
+        for path in resources[name]['Files']:
+            cpp.write('        if (!strcmp(path, "%s"))\n' % path)
+            cpp.write('          return resource%dSize;\n' % resources[name]['Files'][path]['Index'])
+        cpp.write('        throw %s;\n\n' % INEXISTENT_PATH_EXCEPTION)
+
+cpp.write("""      default:
+        throw %s;
+      }
+    }
+""" % OUT_OF_RANGE_EXCEPTION)
+
+
+
+
+#####################################################################
+## List the resources in a directory
+#####################################################################
+
+cpp.write("""
+    void ListResources(std::list<std::string>& result, DirectoryResourceId id)
+    {
+      result.clear();
+
+      switch (id)
+      {
+""")
+
+for name in resources:
+    if resources[name]['Type'] == 'Directory':
+        cpp.write('      case %s:\n' % name)
+        for path in sorted(resources[name]['Files']):
+            cpp.write('        result.push_back("%s");\n' % path)
+        cpp.write('        break;\n\n')
+
+cpp.write("""      default:
+        throw %s;
+      }
+    }
+""" % OUT_OF_RANGE_EXCEPTION)
+
+
+
+
+#####################################################################
+## Write the convenience wrappers in .cpp
+#####################################################################
+
+cpp.write("""
+    void GetFileResource(std::string& result, FileResourceId id)
+    {
+      size_t size = GetFileResourceSize(id);
+      result.resize(size);
+      if (size > 0)
+        memcpy(&result[0], GetFileResourceBuffer(id), size);
+    }
+
+    void GetDirectoryResource(std::string& result, DirectoryResourceId id, const char* path)
+    {
+      size_t size = GetDirectoryResourceSize(id, path);
+      result.resize(size);
+      if (size > 0)
+        memcpy(&result[0], GetDirectoryResourceBuffer(id, path), size);
+    }
+  }
+}
+""")
+cpp.close()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/LinuxStandardBaseToolchain.cmake	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,66 @@
+# LSB_CC=gcc-4.8 LSB_CXX=g++-4.8 cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=../Resources/LinuxStandardBaseToolchain.cmake
+
+INCLUDE(CMakeForceCompiler)
+
+SET(LSB_PATH $ENV{LSB_PATH})
+SET(LSB_CC $ENV{LSB_CC})
+SET(LSB_CXX $ENV{LSB_CXX})
+SET(LSB_TARGET_VERSION "4.0")
+
+IF ("${LSB_PATH}" STREQUAL "")
+  SET(LSB_PATH "/opt/lsb")
+ENDIF()
+
+IF (EXISTS ${LSB_PATH}/lib64)
+  SET(LSB_TARGET_PROCESSOR "x86_64")
+  SET(LSB_LIBPATH ${LSB_PATH}/lib64-${LSB_TARGET_VERSION})
+ELSEIF (EXISTS ${LSB_PATH}/lib)
+  SET(LSB_TARGET_PROCESSOR "x86")
+  SET(LSB_LIBPATH ${LSB_PATH}/lib-${LSB_TARGET_VERSION})
+ELSE()
+  MESSAGE(FATAL_ERROR "Unable to detect the target processor architecture. Check the LSB_PATH environment variable.")
+ENDIF()
+
+SET(LSB_CPPPATH ${LSB_PATH}/include)
+SET(PKG_CONFIG_PATH ${LSB_LIBPATH}/pkgconfig/)
+
+# the name of the target operating system
+SET(CMAKE_SYSTEM_NAME Linux)
+SET(CMAKE_SYSTEM_VERSION LinuxStandardBase)
+SET(CMAKE_SYSTEM_PROCESSOR ${LSB_TARGET_PROCESSOR})
+
+# 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)
+
+# here is the target environment located
+SET(CMAKE_FIND_ROOT_PATH ${LSB_PATH})
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER)
+SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER)
+
+SET(CMAKE_CROSSCOMPILING OFF)
+
+
+SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -I${LSB_PATH}/include" CACHE INTERNAL "" FORCE)
+SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -nostdinc++ -I${LSB_PATH}/include -I${LSB_PATH}/include/c++ -I${LSB_PATH}/include/c++/backward" CACHE INTERNAL "" FORCE)
+SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -L${LSB_LIBPATH} --lsb-besteffort" CACHE INTERNAL "" FORCE)
+SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -L${LSB_LIBPATH} --lsb-besteffort" CACHE INTERNAL "" FORCE)
+
+if (NOT "${LSB_CXX}" STREQUAL "")
+  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-cxx=${LSB_CXX}")
+  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-cxx=${LSB_CXX}")
+  SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --lsb-cxx=${LSB_CXX}")
+endif()
+
+if (NOT "${LSB_CC}" STREQUAL "")
+  SET(CMAKE_C_FLAGS "${CMAKE_CC_FLAGS} --lsb-cc=${LSB_CC}")
+  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-cc=${LSB_CC}")
+  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-cc=${LSB_CC}")
+  SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --lsb-cc=${LSB_CC}")
+endif()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/MinGW-W64-Toolchain32.cmake	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,17 @@
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Windows)
+
+# which compilers to use for C and C++
+set(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
+set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
+set(CMAKE_RC_COMPILER i686-w64-mingw32-windres)
+
+# here is the target environment located
+set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32)
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/MinGW-W64-Toolchain64.cmake	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,17 @@
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Windows)
+
+# which compilers to use for C and C++
+set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
+set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
+set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
+
+# here is the target environment located
+set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32)
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/MinGWToolchain.cmake	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,17 @@
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Windows)
+
+# which compilers to use for C and C++
+set(CMAKE_C_COMPILER i586-mingw32msvc-gcc)
+set(CMAKE_CXX_COMPILER i586-mingw32msvc-g++)
+set(CMAKE_RC_COMPILER i586-mingw32msvc-windres)
+
+# here is the target environment located
+set(CMAKE_FIND_ROOT_PATH /usr/i586-mingw32msvc)
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/Patches/boost-1.65.1-linux-standard-base.patch	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,12 @@
+diff -urEb boost_1_65_1.orig/boost/move/adl_move_swap.hpp boost_1_65_1/boost/move/adl_move_swap.hpp
+--- boost_1_65_1.orig/boost/move/adl_move_swap.hpp	2017-11-08 17:43:20.000000000 +0100
++++ boost_1_65_1/boost/move/adl_move_swap.hpp	2018-01-02 15:34:48.829052917 +0100
+@@ -28,6 +28,8 @@
+ //Try to avoid including <algorithm>, as it's quite big
+ #if defined(_MSC_VER) && defined(BOOST_DINKUMWARE_STDLIB)
+    #include <utility>   //Dinkum libraries define std::swap in utility which is lighter than algorithm
++#elif defined(__LSB_VERSION__)
++#  include <utility>
+ #elif defined(BOOST_GNU_STDLIB)
+    //For non-GCC compilers, where GNUC version is not very reliable, or old GCC versions
+    //use the good old stl_algobase header, which is quite lightweight
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/ThirdParty/VisualStudio/stdint.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,259 @@
+// ISO C9x  compliant stdint.h for Microsoft Visual Studio
+// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 
+// 
+//  Copyright (c) 2006-2013 Alexander Chemeris
+// 
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+// 
+//   1. Redistributions of source code must retain the above copyright notice,
+//      this list of conditions and the following disclaimer.
+// 
+//   2. Redistributions in binary form must reproduce the above copyright
+//      notice, this list of conditions and the following disclaimer in the
+//      documentation and/or other materials provided with the distribution.
+// 
+//   3. Neither the name of the product nor the names of its contributors may
+//      be used to endorse or promote products derived from this software
+//      without specific prior written permission.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// 
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef _MSC_VER // [
+#error "Use this header only with Microsoft Visual C++ compilers!"
+#endif // _MSC_VER ]
+
+#ifndef _MSC_STDINT_H_ // [
+#define _MSC_STDINT_H_
+
+#if _MSC_VER > 1000
+#pragma once
+#endif
+
+#if _MSC_VER >= 1600 // [
+#include <stdint.h>
+#else // ] _MSC_VER >= 1600 [
+
+#include <limits.h>
+
+// For Visual Studio 6 in C++ mode and for many Visual Studio versions when
+// compiling for ARM we should wrap <wchar.h> include with 'extern "C++" {}'
+// or compiler give many errors like this:
+//   error C2733: second C linkage of overloaded function 'wmemchr' not allowed
+#ifdef __cplusplus
+extern "C" {
+#endif
+#  include <wchar.h>
+#ifdef __cplusplus
+}
+#endif
+
+// Define _W64 macros to mark types changing their size, like intptr_t.
+#ifndef _W64
+#  if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300
+#     define _W64 __w64
+#  else
+#     define _W64
+#  endif
+#endif
+
+
+// 7.18.1 Integer types
+
+// 7.18.1.1 Exact-width integer types
+
+// Visual Studio 6 and Embedded Visual C++ 4 doesn't
+// realize that, e.g. char has the same size as __int8
+// so we give up on __intX for them.
+#if (_MSC_VER < 1300)
+   typedef signed char       int8_t;
+   typedef signed short      int16_t;
+   typedef signed int        int32_t;
+   typedef unsigned char     uint8_t;
+   typedef unsigned short    uint16_t;
+   typedef unsigned int      uint32_t;
+#else
+   typedef signed __int8     int8_t;
+   typedef signed __int16    int16_t;
+   typedef signed __int32    int32_t;
+   typedef unsigned __int8   uint8_t;
+   typedef unsigned __int16  uint16_t;
+   typedef unsigned __int32  uint32_t;
+#endif
+typedef signed __int64       int64_t;
+typedef unsigned __int64     uint64_t;
+
+
+// 7.18.1.2 Minimum-width integer types
+typedef int8_t    int_least8_t;
+typedef int16_t   int_least16_t;
+typedef int32_t   int_least32_t;
+typedef int64_t   int_least64_t;
+typedef uint8_t   uint_least8_t;
+typedef uint16_t  uint_least16_t;
+typedef uint32_t  uint_least32_t;
+typedef uint64_t  uint_least64_t;
+
+// 7.18.1.3 Fastest minimum-width integer types
+typedef int8_t    int_fast8_t;
+typedef int16_t   int_fast16_t;
+typedef int32_t   int_fast32_t;
+typedef int64_t   int_fast64_t;
+typedef uint8_t   uint_fast8_t;
+typedef uint16_t  uint_fast16_t;
+typedef uint32_t  uint_fast32_t;
+typedef uint64_t  uint_fast64_t;
+
+// 7.18.1.4 Integer types capable of holding object pointers
+#ifdef _WIN64 // [
+   typedef signed __int64    intptr_t;
+   typedef unsigned __int64  uintptr_t;
+#else // _WIN64 ][
+   typedef _W64 signed int   intptr_t;
+   typedef _W64 unsigned int uintptr_t;
+#endif // _WIN64 ]
+
+// 7.18.1.5 Greatest-width integer types
+typedef int64_t   intmax_t;
+typedef uint64_t  uintmax_t;
+
+
+// 7.18.2 Limits of specified-width integer types
+
+#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [   See footnote 220 at page 257 and footnote 221 at page 259
+
+// 7.18.2.1 Limits of exact-width integer types
+#define INT8_MIN     ((int8_t)_I8_MIN)
+#define INT8_MAX     _I8_MAX
+#define INT16_MIN    ((int16_t)_I16_MIN)
+#define INT16_MAX    _I16_MAX
+#define INT32_MIN    ((int32_t)_I32_MIN)
+#define INT32_MAX    _I32_MAX
+#define INT64_MIN    ((int64_t)_I64_MIN)
+#define INT64_MAX    _I64_MAX
+#define UINT8_MAX    _UI8_MAX
+#define UINT16_MAX   _UI16_MAX
+#define UINT32_MAX   _UI32_MAX
+#define UINT64_MAX   _UI64_MAX
+
+// 7.18.2.2 Limits of minimum-width integer types
+#define INT_LEAST8_MIN    INT8_MIN
+#define INT_LEAST8_MAX    INT8_MAX
+#define INT_LEAST16_MIN   INT16_MIN
+#define INT_LEAST16_MAX   INT16_MAX
+#define INT_LEAST32_MIN   INT32_MIN
+#define INT_LEAST32_MAX   INT32_MAX
+#define INT_LEAST64_MIN   INT64_MIN
+#define INT_LEAST64_MAX   INT64_MAX
+#define UINT_LEAST8_MAX   UINT8_MAX
+#define UINT_LEAST16_MAX  UINT16_MAX
+#define UINT_LEAST32_MAX  UINT32_MAX
+#define UINT_LEAST64_MAX  UINT64_MAX
+
+// 7.18.2.3 Limits of fastest minimum-width integer types
+#define INT_FAST8_MIN    INT8_MIN
+#define INT_FAST8_MAX    INT8_MAX
+#define INT_FAST16_MIN   INT16_MIN
+#define INT_FAST16_MAX   INT16_MAX
+#define INT_FAST32_MIN   INT32_MIN
+#define INT_FAST32_MAX   INT32_MAX
+#define INT_FAST64_MIN   INT64_MIN
+#define INT_FAST64_MAX   INT64_MAX
+#define UINT_FAST8_MAX   UINT8_MAX
+#define UINT_FAST16_MAX  UINT16_MAX
+#define UINT_FAST32_MAX  UINT32_MAX
+#define UINT_FAST64_MAX  UINT64_MAX
+
+// 7.18.2.4 Limits of integer types capable of holding object pointers
+#ifdef _WIN64 // [
+#  define INTPTR_MIN   INT64_MIN
+#  define INTPTR_MAX   INT64_MAX
+#  define UINTPTR_MAX  UINT64_MAX
+#else // _WIN64 ][
+#  define INTPTR_MIN   INT32_MIN
+#  define INTPTR_MAX   INT32_MAX
+#  define UINTPTR_MAX  UINT32_MAX
+#endif // _WIN64 ]
+
+// 7.18.2.5 Limits of greatest-width integer types
+#define INTMAX_MIN   INT64_MIN
+#define INTMAX_MAX   INT64_MAX
+#define UINTMAX_MAX  UINT64_MAX
+
+// 7.18.3 Limits of other integer types
+
+#ifdef _WIN64 // [
+#  define PTRDIFF_MIN  _I64_MIN
+#  define PTRDIFF_MAX  _I64_MAX
+#else  // _WIN64 ][
+#  define PTRDIFF_MIN  _I32_MIN
+#  define PTRDIFF_MAX  _I32_MAX
+#endif  // _WIN64 ]
+
+#define SIG_ATOMIC_MIN  INT_MIN
+#define SIG_ATOMIC_MAX  INT_MAX
+
+#ifndef SIZE_MAX // [
+#  ifdef _WIN64 // [
+#     define SIZE_MAX  _UI64_MAX
+#  else // _WIN64 ][
+#     define SIZE_MAX  _UI32_MAX
+#  endif // _WIN64 ]
+#endif // SIZE_MAX ]
+
+// WCHAR_MIN and WCHAR_MAX are also defined in <wchar.h>
+#ifndef WCHAR_MIN // [
+#  define WCHAR_MIN  0
+#endif  // WCHAR_MIN ]
+#ifndef WCHAR_MAX // [
+#  define WCHAR_MAX  _UI16_MAX
+#endif  // WCHAR_MAX ]
+
+#define WINT_MIN  0
+#define WINT_MAX  _UI16_MAX
+
+#endif // __STDC_LIMIT_MACROS ]
+
+
+// 7.18.4 Limits of other integer types
+
+#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [   See footnote 224 at page 260
+
+// 7.18.4.1 Macros for minimum-width integer constants
+
+#define INT8_C(val)  val##i8
+#define INT16_C(val) val##i16
+#define INT32_C(val) val##i32
+#define INT64_C(val) val##i64
+
+#define UINT8_C(val)  val##ui8
+#define UINT16_C(val) val##ui16
+#define UINT32_C(val) val##ui32
+#define UINT64_C(val) val##ui64
+
+// 7.18.4.2 Macros for greatest-width integer constants
+// These #ifndef's are needed to prevent collisions with <boost/cstdint.hpp>.
+// Check out Issue 9 for the details.
+#ifndef INTMAX_C //   [
+#  define INTMAX_C   INT64_C
+#endif // INTMAX_C    ]
+#ifndef UINTMAX_C //  [
+#  define UINTMAX_C  UINT64_C
+#endif // UINTMAX_C   ]
+
+#endif // __STDC_CONSTANT_MACROS ]
+
+#endif // _MSC_VER >= 1600 ]
+
+#endif // _MSC_STDINT_H_ ]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/ThirdParty/base64/base64.cpp	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,128 @@
+/* 
+   base64.cpp and base64.h
+
+   Copyright (C) 2004-2008 René Nyffenegger
+
+   This source code is provided 'as-is', without any express or implied
+   warranty. In no event will the author be held liable for any damages
+   arising from the use of this software.
+
+   Permission is granted to anyone to use this software for any purpose,
+   including commercial applications, and to alter it and redistribute it
+   freely, subject to the following restrictions:
+
+   1. The origin of this source code must not be misrepresented; you must not
+      claim that you wrote the original source code. If you use this source code
+      in a product, an acknowledgment in the product documentation would be
+      appreciated but is not required.
+
+   2. Altered source versions must be plainly marked as such, and must not be
+      misrepresented as being the original source code.
+
+   3. This notice may not be removed or altered from any source distribution.
+
+   René Nyffenegger rene.nyffenegger@adp-gmbh.ch
+
+*/
+
+#include "base64.h"
+#include <string.h>
+
+static const std::string base64_chars = 
+             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+             "abcdefghijklmnopqrstuvwxyz"
+             "0123456789+/";
+
+
+static inline bool is_base64(unsigned char c) {
+  return (isalnum(c) || (c == '+') || (c == '/'));
+}
+
+std::string base64_encode(const std::string& stringToEncode) 
+{
+  const unsigned char* bytes_to_encode = reinterpret_cast<const unsigned char*>
+    (stringToEncode.size() > 0 ? &stringToEncode[0] : NULL);
+  unsigned int in_len = stringToEncode.size();
+  
+  std::string ret;
+  int i = 0;
+  int j = 0;
+  unsigned char char_array_3[3];
+  unsigned char char_array_4[4];
+
+  while (in_len--) {
+    char_array_3[i++] = *(bytes_to_encode++);
+    if (i == 3) {
+      char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+      char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
+      char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
+      char_array_4[3] = char_array_3[2] & 0x3f;
+
+      for(i = 0; (i <4) ; i++)
+        ret += base64_chars[char_array_4[i]];
+      i = 0;
+    }
+  }
+
+  if (i)
+  {
+    for(j = i; j < 3; j++)
+      char_array_3[j] = '\0';
+
+    char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+    char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
+    char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
+    char_array_4[3] = char_array_3[2] & 0x3f;
+
+    for (j = 0; (j < i + 1); j++)
+      ret += base64_chars[char_array_4[j]];
+
+    while((i++ < 3))
+      ret += '=';
+
+  }
+
+  return ret;
+}
+
+
+std::string base64_decode(const std::string& encoded_string) {
+  int in_len = encoded_string.size();
+  int i = 0;
+  int j = 0;
+  int in_ = 0;
+  unsigned char char_array_4[4], char_array_3[3];
+  std::string ret;
+
+  while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
+    char_array_4[i++] = encoded_string[in_]; in_++;
+    if (i ==4) {
+      for (i = 0; i <4; i++)
+        char_array_4[i] = base64_chars.find(char_array_4[i]);
+
+      char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
+      char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
+      char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
+
+      for (i = 0; (i < 3); i++)
+        ret += char_array_3[i];
+      i = 0;
+    }
+  }
+
+  if (i) {
+    for (j = i; j <4; j++)
+      char_array_4[j] = 0;
+
+    for (j = 0; j <4; j++)
+      char_array_4[j] = base64_chars.find(char_array_4[j]);
+
+    char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
+    char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
+    char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
+
+    for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
+  }
+
+  return ret;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/ThirdParty/base64/base64.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,4 @@
+#include <string>
+
+std::string base64_encode(const std::string& stringToEncode);
+std::string base64_decode(const std::string& s);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/ThirdParty/md5/md5.c	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,381 @@
+/*
+  Copyright (C) 1999, 2000, 2002 Aladdin Enterprises.  All rights reserved.
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  L. Peter Deutsch
+  ghost@aladdin.com
+
+ */
+/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */
+/*
+  Independent implementation of MD5 (RFC 1321).
+
+  This code implements the MD5 Algorithm defined in RFC 1321, whose
+  text is available at
+	http://www.ietf.org/rfc/rfc1321.txt
+  The code is derived from the text of the RFC, including the test suite
+  (section A.5) but excluding the rest of Appendix A.  It does not include
+  any code or documentation that is identified in the RFC as being
+  copyrighted.
+
+  The original and principal author of md5.c is L. Peter Deutsch
+  <ghost@aladdin.com>.  Other authors are noted in the change history
+  that follows (in reverse chronological order):
+
+  2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order
+	either statically or dynamically; added missing #include <string.h>
+	in library.
+  2002-03-11 lpd Corrected argument list for main(), and added int return
+	type, in test program and T value program.
+  2002-02-21 lpd Added missing #include <stdio.h> in test program.
+  2000-07-03 lpd Patched to eliminate warnings about "constant is
+	unsigned in ANSI C, signed in traditional"; made test program
+	self-checking.
+  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
+  1999-05-03 lpd Original version.
+ */
+
+#include "md5.h"
+#include <string.h>
+
+#undef BYTE_ORDER	/* 1 = big-endian, -1 = little-endian, 0 = unknown */
+#ifdef ARCH_IS_BIG_ENDIAN
+#  define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
+#else
+#  define BYTE_ORDER 0
+#endif
+
+#define T_MASK ((md5_word_t)~0)
+#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
+#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
+#define T3    0x242070db
+#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
+#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
+#define T6    0x4787c62a
+#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
+#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
+#define T9    0x698098d8
+#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
+#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
+#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
+#define T13    0x6b901122
+#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
+#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
+#define T16    0x49b40821
+#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
+#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
+#define T19    0x265e5a51
+#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
+#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
+#define T22    0x02441453
+#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
+#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
+#define T25    0x21e1cde6
+#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
+#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
+#define T28    0x455a14ed
+#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
+#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
+#define T31    0x676f02d9
+#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
+#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
+#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
+#define T35    0x6d9d6122
+#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
+#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
+#define T38    0x4bdecfa9
+#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
+#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
+#define T41    0x289b7ec6
+#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
+#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
+#define T44    0x04881d05
+#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
+#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
+#define T47    0x1fa27cf8
+#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
+#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
+#define T50    0x432aff97
+#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
+#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
+#define T53    0x655b59c3
+#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
+#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
+#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
+#define T57    0x6fa87e4f
+#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
+#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
+#define T60    0x4e0811a1
+#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
+#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
+#define T63    0x2ad7d2bb
+#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)
+
+
+static void
+md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
+{
+    md5_word_t
+	a = pms->abcd[0], b = pms->abcd[1],
+	c = pms->abcd[2], d = pms->abcd[3];
+    md5_word_t t;
+#if BYTE_ORDER > 0
+    /* Define storage only for big-endian CPUs. */
+    md5_word_t X[16];
+#else
+    /* Define storage for little-endian or both types of CPUs. */
+    md5_word_t xbuf[16];
+    const md5_word_t *X;
+#endif
+
+    {
+#if BYTE_ORDER == 0
+	/*
+	 * Determine dynamically whether this is a big-endian or
+	 * little-endian machine, since we can use a more efficient
+	 * algorithm on the latter.
+	 */
+	static const int w = 1;
+
+	if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
+#endif
+#if BYTE_ORDER <= 0		/* little-endian */
+	{
+	    /*
+	     * On little-endian machines, we can process properly aligned
+	     * data without copying it.
+	     */
+	    if (!((data - (const md5_byte_t *)0) & 3)) {
+		/* data are properly aligned */
+		X = (const md5_word_t *)data;
+	    } else {
+		/* not aligned */
+		memcpy(xbuf, data, 64);
+		X = xbuf;
+	    }
+	}
+#endif
+#if BYTE_ORDER == 0
+	else			/* dynamic big-endian */
+#endif
+#if BYTE_ORDER >= 0		/* big-endian */
+	{
+	    /*
+	     * On big-endian machines, we must arrange the bytes in the
+	     * right order.
+	     */
+	    const md5_byte_t *xp = data;
+	    int i;
+
+#  if BYTE_ORDER == 0
+	    X = xbuf;		/* (dynamic only) */
+#  else
+#    define xbuf X		/* (static only) */
+#  endif
+	    for (i = 0; i < 16; ++i, xp += 4)
+		xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
+	}
+#endif
+    }
+
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
+
+    /* Round 1. */
+    /* Let [abcd k s i] denote the operation
+       a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
+#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + F(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+    /* Do the following 16 operations. */
+    SET(a, b, c, d,  0,  7,  T1);
+    SET(d, a, b, c,  1, 12,  T2);
+    SET(c, d, a, b,  2, 17,  T3);
+    SET(b, c, d, a,  3, 22,  T4);
+    SET(a, b, c, d,  4,  7,  T5);
+    SET(d, a, b, c,  5, 12,  T6);
+    SET(c, d, a, b,  6, 17,  T7);
+    SET(b, c, d, a,  7, 22,  T8);
+    SET(a, b, c, d,  8,  7,  T9);
+    SET(d, a, b, c,  9, 12, T10);
+    SET(c, d, a, b, 10, 17, T11);
+    SET(b, c, d, a, 11, 22, T12);
+    SET(a, b, c, d, 12,  7, T13);
+    SET(d, a, b, c, 13, 12, T14);
+    SET(c, d, a, b, 14, 17, T15);
+    SET(b, c, d, a, 15, 22, T16);
+#undef SET
+
+     /* Round 2. */
+     /* Let [abcd k s i] denote the operation
+          a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
+#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + G(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  1,  5, T17);
+    SET(d, a, b, c,  6,  9, T18);
+    SET(c, d, a, b, 11, 14, T19);
+    SET(b, c, d, a,  0, 20, T20);
+    SET(a, b, c, d,  5,  5, T21);
+    SET(d, a, b, c, 10,  9, T22);
+    SET(c, d, a, b, 15, 14, T23);
+    SET(b, c, d, a,  4, 20, T24);
+    SET(a, b, c, d,  9,  5, T25);
+    SET(d, a, b, c, 14,  9, T26);
+    SET(c, d, a, b,  3, 14, T27);
+    SET(b, c, d, a,  8, 20, T28);
+    SET(a, b, c, d, 13,  5, T29);
+    SET(d, a, b, c,  2,  9, T30);
+    SET(c, d, a, b,  7, 14, T31);
+    SET(b, c, d, a, 12, 20, T32);
+#undef SET
+
+     /* Round 3. */
+     /* Let [abcd k s t] denote the operation
+          a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + H(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  5,  4, T33);
+    SET(d, a, b, c,  8, 11, T34);
+    SET(c, d, a, b, 11, 16, T35);
+    SET(b, c, d, a, 14, 23, T36);
+    SET(a, b, c, d,  1,  4, T37);
+    SET(d, a, b, c,  4, 11, T38);
+    SET(c, d, a, b,  7, 16, T39);
+    SET(b, c, d, a, 10, 23, T40);
+    SET(a, b, c, d, 13,  4, T41);
+    SET(d, a, b, c,  0, 11, T42);
+    SET(c, d, a, b,  3, 16, T43);
+    SET(b, c, d, a,  6, 23, T44);
+    SET(a, b, c, d,  9,  4, T45);
+    SET(d, a, b, c, 12, 11, T46);
+    SET(c, d, a, b, 15, 16, T47);
+    SET(b, c, d, a,  2, 23, T48);
+#undef SET
+
+     /* Round 4. */
+     /* Let [abcd k s t] denote the operation
+          a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
+#define I(x, y, z) ((y) ^ ((x) | ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + I(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  0,  6, T49);
+    SET(d, a, b, c,  7, 10, T50);
+    SET(c, d, a, b, 14, 15, T51);
+    SET(b, c, d, a,  5, 21, T52);
+    SET(a, b, c, d, 12,  6, T53);
+    SET(d, a, b, c,  3, 10, T54);
+    SET(c, d, a, b, 10, 15, T55);
+    SET(b, c, d, a,  1, 21, T56);
+    SET(a, b, c, d,  8,  6, T57);
+    SET(d, a, b, c, 15, 10, T58);
+    SET(c, d, a, b,  6, 15, T59);
+    SET(b, c, d, a, 13, 21, T60);
+    SET(a, b, c, d,  4,  6, T61);
+    SET(d, a, b, c, 11, 10, T62);
+    SET(c, d, a, b,  2, 15, T63);
+    SET(b, c, d, a,  9, 21, T64);
+#undef SET
+
+     /* Then perform the following additions. (That is increment each
+        of the four registers by the value it had before this block
+        was started.) */
+    pms->abcd[0] += a;
+    pms->abcd[1] += b;
+    pms->abcd[2] += c;
+    pms->abcd[3] += d;
+}
+
+void
+md5_init(md5_state_t *pms)
+{
+    pms->count[0] = pms->count[1] = 0;
+    pms->abcd[0] = 0x67452301;
+    pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
+    pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
+    pms->abcd[3] = 0x10325476;
+}
+
+void
+md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
+{
+    const md5_byte_t *p = data;
+    int left = nbytes;
+    int offset = (pms->count[0] >> 3) & 63;
+    md5_word_t nbits = (md5_word_t)(nbytes << 3);
+
+    if (nbytes <= 0)
+	return;
+
+    /* Update the message length. */
+    pms->count[1] += nbytes >> 29;
+    pms->count[0] += nbits;
+    if (pms->count[0] < nbits)
+	pms->count[1]++;
+
+    /* Process an initial partial block. */
+    if (offset) {
+	int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
+
+	memcpy(pms->buf + offset, p, copy);
+	if (offset + copy < 64)
+	    return;
+	p += copy;
+	left -= copy;
+	md5_process(pms, pms->buf);
+    }
+
+    /* Process full blocks. */
+    for (; left >= 64; p += 64, left -= 64)
+	md5_process(pms, p);
+
+    /* Process a final partial block. */
+    if (left)
+	memcpy(pms->buf, p, left);
+}
+
+void
+md5_finish(md5_state_t *pms, md5_byte_t digest[16])
+{
+    static const md5_byte_t pad[64] = {
+	0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+    };
+    md5_byte_t data[8];
+    int i;
+
+    /* Save the length before padding. */
+    for (i = 0; i < 8; ++i)
+	data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
+    /* Pad to 56 bytes mod 64. */
+    md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
+    /* Append the length. */
+    md5_append(pms, data, 8);
+    for (i = 0; i < 16; ++i)
+	digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/ThirdParty/md5/md5.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,91 @@
+/*
+  Copyright (C) 1999, 2002 Aladdin Enterprises.  All rights reserved.
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  L. Peter Deutsch
+  ghost@aladdin.com
+
+ */
+/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */
+/*
+  Independent implementation of MD5 (RFC 1321).
+
+  This code implements the MD5 Algorithm defined in RFC 1321, whose
+  text is available at
+	http://www.ietf.org/rfc/rfc1321.txt
+  The code is derived from the text of the RFC, including the test suite
+  (section A.5) but excluding the rest of Appendix A.  It does not include
+  any code or documentation that is identified in the RFC as being
+  copyrighted.
+
+  The original and principal author of md5.h is L. Peter Deutsch
+  <ghost@aladdin.com>.  Other authors are noted in the change history
+  that follows (in reverse chronological order):
+
+  2002-04-13 lpd Removed support for non-ANSI compilers; removed
+	references to Ghostscript; clarified derivation from RFC 1321;
+	now handles byte order either statically or dynamically.
+  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
+	added conditionalization for C++ compilation from Martin
+	Purschke <purschke@bnl.gov>.
+  1999-05-03 lpd Original version.
+ */
+
+#ifndef md5_INCLUDED
+#  define md5_INCLUDED
+
+/*
+ * This package supports both compile-time and run-time determination of CPU
+ * byte order.  If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
+ * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
+ * defined as non-zero, the code will be compiled to run only on big-endian
+ * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
+ * run on either big- or little-endian CPUs, but will run slightly less
+ * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
+ */
+
+typedef unsigned char md5_byte_t; /* 8-bit byte */
+typedef unsigned int md5_word_t; /* 32-bit word */
+
+/* Define the state of the MD5 Algorithm. */
+typedef struct md5_state_s {
+    md5_word_t count[2];	/* message length in bits, lsw first */
+    md5_word_t abcd[4];		/* digest buffer */
+    md5_byte_t buf[64];		/* accumulate block */
+} md5_state_t;
+
+#ifdef __cplusplus
+extern "C" 
+{
+#endif
+
+/* Initialize the algorithm. */
+void md5_init(md5_state_t *pms);
+
+/* Append a string to the message. */
+void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
+
+/* Finish the message and return the digest. */
+void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
+
+#ifdef __cplusplus
+}  /* end extern "C" */
+#endif
+
+#endif /* md5_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/ThirdParty/minizip/NOTES	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,1 @@
+These files come from the "contrib/minizip" directory in zlib 1.2.7.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/ThirdParty/minizip/crypt.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,131 @@
+/* crypt.h -- base code for crypt/uncrypt ZIPfile
+
+
+   Version 1.01e, February 12th, 2005
+
+   Copyright (C) 1998-2005 Gilles Vollant
+
+   This code is a modified version of crypting code in Infozip distribution
+
+   The encryption/decryption parts of this source code (as opposed to the
+   non-echoing password parts) were originally written in Europe.  The
+   whole source package can be freely distributed, including from the USA.
+   (Prior to January 2000, re-export from the US was a violation of US law.)
+
+   This encryption code is a direct transcription of the algorithm from
+   Roger Schlafly, described by Phil Katz in the file appnote.txt.  This
+   file (appnote.txt) is distributed with the PKZIP program (even in the
+   version without encryption capabilities).
+
+   If you don't need crypting in your application, just define symbols
+   NOCRYPT and NOUNCRYPT.
+
+   This code support the "Traditional PKWARE Encryption".
+
+   The new AES encryption added on Zip format by Winzip (see the page
+   http://www.winzip.com/aes_info.htm ) and PKWare PKZip 5.x Strong
+   Encryption is not supported.
+*/
+
+#define CRC32(c, b) ((*(pcrc_32_tab+(((int)(c) ^ (b)) & 0xff))) ^ ((c) >> 8))
+
+/***********************************************************************
+ * Return the next byte in the pseudo-random sequence
+ */
+static int decrypt_byte(unsigned long* pkeys, const unsigned long* pcrc_32_tab)
+{
+    unsigned temp;  /* POTENTIAL BUG:  temp*(temp^1) may overflow in an
+                     * unpredictable manner on 16-bit systems; not a problem
+                     * with any known compiler so far, though */
+
+    temp = ((unsigned)(*(pkeys+2)) & 0xffff) | 2;
+    return (int)(((temp * (temp ^ 1)) >> 8) & 0xff);
+}
+
+/***********************************************************************
+ * Update the encryption keys with the next byte of plain text
+ */
+static int update_keys(unsigned long* pkeys,const unsigned long* pcrc_32_tab,int c)
+{
+    (*(pkeys+0)) = CRC32((*(pkeys+0)), c);
+    (*(pkeys+1)) += (*(pkeys+0)) & 0xff;
+    (*(pkeys+1)) = (*(pkeys+1)) * 134775813L + 1;
+    {
+      register int keyshift = (int)((*(pkeys+1)) >> 24);
+      (*(pkeys+2)) = CRC32((*(pkeys+2)), keyshift);
+    }
+    return c;
+}
+
+
+/***********************************************************************
+ * Initialize the encryption keys and the random header according to
+ * the given password.
+ */
+static void init_keys(const char* passwd,unsigned long* pkeys,const unsigned long* pcrc_32_tab)
+{
+    *(pkeys+0) = 305419896L;
+    *(pkeys+1) = 591751049L;
+    *(pkeys+2) = 878082192L;
+    while (*passwd != '\0') {
+        update_keys(pkeys,pcrc_32_tab,(int)*passwd);
+        passwd++;
+    }
+}
+
+#define zdecode(pkeys,pcrc_32_tab,c) \
+    (update_keys(pkeys,pcrc_32_tab,c ^= decrypt_byte(pkeys,pcrc_32_tab)))
+
+#define zencode(pkeys,pcrc_32_tab,c,t) \
+    (t=decrypt_byte(pkeys,pcrc_32_tab), update_keys(pkeys,pcrc_32_tab,c), t^(c))
+
+#ifdef INCLUDECRYPTINGCODE_IFCRYPTALLOWED
+
+#define RAND_HEAD_LEN  12
+   /* "last resort" source for second part of crypt seed pattern */
+#  ifndef ZCR_SEED2
+#    define ZCR_SEED2 3141592654UL     /* use PI as default pattern */
+#  endif
+
+static int crypthead(const char* passwd,      /* password string */
+                     unsigned char* buf,      /* where to write header */
+                     int bufSize,
+                     unsigned long* pkeys,
+                     const unsigned long* pcrc_32_tab,
+                     unsigned long crcForCrypting)
+{
+    int n;                       /* index in random header */
+    int t;                       /* temporary */
+    int c;                       /* random byte */
+    unsigned char header[RAND_HEAD_LEN-2]; /* random header */
+    static unsigned calls = 0;   /* ensure different random header each time */
+
+    if (bufSize<RAND_HEAD_LEN)
+      return 0;
+
+    /* First generate RAND_HEAD_LEN-2 random bytes. We encrypt the
+     * output of rand() to get less predictability, since rand() is
+     * often poorly implemented.
+     */
+    if (++calls == 1)
+    {
+        srand((unsigned)(time(NULL) ^ ZCR_SEED2));
+    }
+    init_keys(passwd, pkeys, pcrc_32_tab);
+    for (n = 0; n < RAND_HEAD_LEN-2; n++)
+    {
+        c = (rand() >> 7) & 0xff;
+        header[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, c, t);
+    }
+    /* Encrypt random header (last two bytes is high word of crc) */
+    init_keys(passwd, pkeys, pcrc_32_tab);
+    for (n = 0; n < RAND_HEAD_LEN-2; n++)
+    {
+        buf[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, header[n], t);
+    }
+    buf[n++] = (unsigned char)zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 16) & 0xff, t);
+    buf[n++] = (unsigned char)zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 24) & 0xff, t);
+    return n;
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/ThirdParty/minizip/ioapi.c	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,247 @@
+/* ioapi.h -- IO base function header for compress/uncompress .zip
+   part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Modifications for Zip64 support
+         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
+
+         For more info read MiniZip_info.txt
+
+*/
+
+#if defined(_WIN32) && (!(defined(_CRT_SECURE_NO_WARNINGS)))
+        #define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#if defined(__APPLE__) || defined(IOAPI_NO_64)
+// In darwin and perhaps other BSD variants off_t is a 64 bit value, hence no need for specific 64 bit functions
+#define FOPEN_FUNC(filename, mode) fopen(filename, mode)
+#define FTELLO_FUNC(stream) ftello(stream)
+#define FSEEKO_FUNC(stream, offset, origin) fseeko(stream, offset, origin)
+#else
+#define FOPEN_FUNC(filename, mode) fopen64(filename, mode)
+#define FTELLO_FUNC(stream) ftello64(stream)
+#define FSEEKO_FUNC(stream, offset, origin) fseeko64(stream, offset, origin)
+#endif
+
+
+#include "ioapi.h"
+
+voidpf call_zopen64 (const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode)
+{
+    if (pfilefunc->zfile_func64.zopen64_file != NULL)
+        return (*(pfilefunc->zfile_func64.zopen64_file)) (pfilefunc->zfile_func64.opaque,filename,mode);
+    else
+    {
+        return (*(pfilefunc->zopen32_file))(pfilefunc->zfile_func64.opaque,(const char*)filename,mode);
+    }
+}
+
+long call_zseek64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin)
+{
+    if (pfilefunc->zfile_func64.zseek64_file != NULL)
+        return (*(pfilefunc->zfile_func64.zseek64_file)) (pfilefunc->zfile_func64.opaque,filestream,offset,origin);
+    else
+    {
+        uLong offsetTruncated = (uLong)offset;
+        if (offsetTruncated != offset)
+            return -1;
+        else
+            return (*(pfilefunc->zseek32_file))(pfilefunc->zfile_func64.opaque,filestream,offsetTruncated,origin);
+    }
+}
+
+ZPOS64_T call_ztell64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream)
+{
+    if (pfilefunc->zfile_func64.zseek64_file != NULL)
+        return (*(pfilefunc->zfile_func64.ztell64_file)) (pfilefunc->zfile_func64.opaque,filestream);
+    else
+    {
+        uLong tell_uLong = (*(pfilefunc->ztell32_file))(pfilefunc->zfile_func64.opaque,filestream);
+        if ((tell_uLong) == MAXU32)
+            return (ZPOS64_T)-1;
+        else
+            return tell_uLong;
+    }
+}
+
+void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32)
+{
+    p_filefunc64_32->zfile_func64.zopen64_file = NULL;
+    p_filefunc64_32->zopen32_file = p_filefunc32->zopen_file;
+    p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file;
+    p_filefunc64_32->zfile_func64.zread_file = p_filefunc32->zread_file;
+    p_filefunc64_32->zfile_func64.zwrite_file = p_filefunc32->zwrite_file;
+    p_filefunc64_32->zfile_func64.ztell64_file = NULL;
+    p_filefunc64_32->zfile_func64.zseek64_file = NULL;
+    p_filefunc64_32->zfile_func64.zclose_file = p_filefunc32->zclose_file;
+    p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file;
+    p_filefunc64_32->zfile_func64.opaque = p_filefunc32->opaque;
+    p_filefunc64_32->zseek32_file = p_filefunc32->zseek_file;
+    p_filefunc64_32->ztell32_file = p_filefunc32->ztell_file;
+}
+
+
+
+static voidpf  ZCALLBACK fopen_file_func OF((voidpf opaque, const char* filename, int mode));
+static uLong   ZCALLBACK fread_file_func OF((voidpf opaque, voidpf stream, void* buf, uLong size));
+static uLong   ZCALLBACK fwrite_file_func OF((voidpf opaque, voidpf stream, const void* buf,uLong size));
+static ZPOS64_T ZCALLBACK ftell64_file_func OF((voidpf opaque, voidpf stream));
+static long    ZCALLBACK fseek64_file_func OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin));
+static int     ZCALLBACK fclose_file_func OF((voidpf opaque, voidpf stream));
+static int     ZCALLBACK ferror_file_func OF((voidpf opaque, voidpf stream));
+
+static voidpf ZCALLBACK fopen_file_func (voidpf opaque, const char* filename, int mode)
+{
+    FILE* file = NULL;
+    const char* mode_fopen = NULL;
+    if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ)
+        mode_fopen = "rb";
+    else
+    if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
+        mode_fopen = "r+b";
+    else
+    if (mode & ZLIB_FILEFUNC_MODE_CREATE)
+        mode_fopen = "wb";
+
+    if ((filename!=NULL) && (mode_fopen != NULL))
+        file = fopen(filename, mode_fopen);
+    return file;
+}
+
+static voidpf ZCALLBACK fopen64_file_func (voidpf opaque, const void* filename, int mode)
+{
+    FILE* file = NULL;
+    const char* mode_fopen = NULL;
+    if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ)
+        mode_fopen = "rb";
+    else
+    if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
+        mode_fopen = "r+b";
+    else
+    if (mode & ZLIB_FILEFUNC_MODE_CREATE)
+        mode_fopen = "wb";
+
+    if ((filename!=NULL) && (mode_fopen != NULL))
+        file = FOPEN_FUNC((const char*)filename, mode_fopen);
+    return file;
+}
+
+
+static uLong ZCALLBACK fread_file_func (voidpf opaque, voidpf stream, void* buf, uLong size)
+{
+    uLong ret;
+    ret = (uLong)fread(buf, 1, (size_t)size, (FILE *)stream);
+    return ret;
+}
+
+static uLong ZCALLBACK fwrite_file_func (voidpf opaque, voidpf stream, const void* buf, uLong size)
+{
+    uLong ret;
+    ret = (uLong)fwrite(buf, 1, (size_t)size, (FILE *)stream);
+    return ret;
+}
+
+static long ZCALLBACK ftell_file_func (voidpf opaque, voidpf stream)
+{
+    long ret;
+    ret = ftell((FILE *)stream);
+    return ret;
+}
+
+
+static ZPOS64_T ZCALLBACK ftell64_file_func (voidpf opaque, voidpf stream)
+{
+    ZPOS64_T ret;
+    ret = FTELLO_FUNC((FILE *)stream);
+    return ret;
+}
+
+static long ZCALLBACK fseek_file_func (voidpf  opaque, voidpf stream, uLong offset, int origin)
+{
+    int fseek_origin=0;
+    long ret;
+    switch (origin)
+    {
+    case ZLIB_FILEFUNC_SEEK_CUR :
+        fseek_origin = SEEK_CUR;
+        break;
+    case ZLIB_FILEFUNC_SEEK_END :
+        fseek_origin = SEEK_END;
+        break;
+    case ZLIB_FILEFUNC_SEEK_SET :
+        fseek_origin = SEEK_SET;
+        break;
+    default: return -1;
+    }
+    ret = 0;
+    if (fseek((FILE *)stream, offset, fseek_origin) != 0)
+        ret = -1;
+    return ret;
+}
+
+static long ZCALLBACK fseek64_file_func (voidpf  opaque, voidpf stream, ZPOS64_T offset, int origin)
+{
+    int fseek_origin=0;
+    long ret;
+    switch (origin)
+    {
+    case ZLIB_FILEFUNC_SEEK_CUR :
+        fseek_origin = SEEK_CUR;
+        break;
+    case ZLIB_FILEFUNC_SEEK_END :
+        fseek_origin = SEEK_END;
+        break;
+    case ZLIB_FILEFUNC_SEEK_SET :
+        fseek_origin = SEEK_SET;
+        break;
+    default: return -1;
+    }
+    ret = 0;
+
+    if(FSEEKO_FUNC((FILE *)stream, offset, fseek_origin) != 0)
+                        ret = -1;
+
+    return ret;
+}
+
+
+static int ZCALLBACK fclose_file_func (voidpf opaque, voidpf stream)
+{
+    int ret;
+    ret = fclose((FILE *)stream);
+    return ret;
+}
+
+static int ZCALLBACK ferror_file_func (voidpf opaque, voidpf stream)
+{
+    int ret;
+    ret = ferror((FILE *)stream);
+    return ret;
+}
+
+void fill_fopen_filefunc (pzlib_filefunc_def)
+  zlib_filefunc_def* pzlib_filefunc_def;
+{
+    pzlib_filefunc_def->zopen_file = fopen_file_func;
+    pzlib_filefunc_def->zread_file = fread_file_func;
+    pzlib_filefunc_def->zwrite_file = fwrite_file_func;
+    pzlib_filefunc_def->ztell_file = ftell_file_func;
+    pzlib_filefunc_def->zseek_file = fseek_file_func;
+    pzlib_filefunc_def->zclose_file = fclose_file_func;
+    pzlib_filefunc_def->zerror_file = ferror_file_func;
+    pzlib_filefunc_def->opaque = NULL;
+}
+
+void fill_fopen64_filefunc (zlib_filefunc64_def*  pzlib_filefunc_def)
+{
+    pzlib_filefunc_def->zopen64_file = fopen64_file_func;
+    pzlib_filefunc_def->zread_file = fread_file_func;
+    pzlib_filefunc_def->zwrite_file = fwrite_file_func;
+    pzlib_filefunc_def->ztell64_file = ftell64_file_func;
+    pzlib_filefunc_def->zseek64_file = fseek64_file_func;
+    pzlib_filefunc_def->zclose_file = fclose_file_func;
+    pzlib_filefunc_def->zerror_file = ferror_file_func;
+    pzlib_filefunc_def->opaque = NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/ThirdParty/minizip/ioapi.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,208 @@
+/* ioapi.h -- IO base function header for compress/uncompress .zip
+   part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Modifications for Zip64 support
+         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
+
+         For more info read MiniZip_info.txt
+
+         Changes
+
+    Oct-2009 - Defined ZPOS64_T to fpos_t on windows and u_int64_t on linux. (might need to find a better why for this)
+    Oct-2009 - Change to fseeko64, ftello64 and fopen64 so large files would work on linux.
+               More if/def section may be needed to support other platforms
+    Oct-2009 - Defined fxxxx64 calls to normal fopen/ftell/fseek so they would compile on windows.
+                          (but you should use iowin32.c for windows instead)
+
+*/
+
+#ifndef _ZLIBIOAPI64_H
+#define _ZLIBIOAPI64_H
+
+#if (!defined(_WIN32)) && (!defined(WIN32)) && (!defined(__APPLE__))
+
+  // Linux needs this to support file operation on files larger then 4+GB
+  // But might need better if/def to select just the platforms that needs them.
+
+        #ifndef __USE_FILE_OFFSET64
+                #define __USE_FILE_OFFSET64
+        #endif
+        #ifndef __USE_LARGEFILE64
+                #define __USE_LARGEFILE64
+        #endif
+        #ifndef _LARGEFILE64_SOURCE
+                #define _LARGEFILE64_SOURCE
+        #endif
+        #ifndef _FILE_OFFSET_BIT
+                #define _FILE_OFFSET_BIT 64
+        #endif
+
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "zlib.h"
+
+#if defined(USE_FILE32API)
+#define fopen64 fopen
+#define ftello64 ftell
+#define fseeko64 fseek
+#else
+#ifdef __FreeBSD__
+#define fopen64 fopen
+#define ftello64 ftello
+#define fseeko64 fseeko
+#endif
+#ifdef _MSC_VER
+ #define fopen64 fopen
+ #if (_MSC_VER >= 1400) && (!(defined(NO_MSCVER_FILE64_FUNC)))
+  #define ftello64 _ftelli64
+  #define fseeko64 _fseeki64
+ #else // old MSC
+  #define ftello64 ftell
+  #define fseeko64 fseek
+ #endif
+#endif
+#endif
+
+/*
+#ifndef ZPOS64_T
+  #ifdef _WIN32
+                #define ZPOS64_T fpos_t
+  #else
+    #include <stdint.h>
+    #define ZPOS64_T uint64_t
+  #endif
+#endif
+*/
+
+#ifdef HAVE_MINIZIP64_CONF_H
+#include "mz64conf.h"
+#endif
+
+/* a type choosen by DEFINE */
+#ifdef HAVE_64BIT_INT_CUSTOM
+typedef  64BIT_INT_CUSTOM_TYPE ZPOS64_T;
+#else
+#ifdef HAS_STDINT_H
+#include "stdint.h"
+typedef uint64_t ZPOS64_T;
+#else
+
+/* Maximum unsigned 32-bit value used as placeholder for zip64 */
+#define MAXU32 0xffffffff
+
+#if defined(_MSC_VER) || defined(__BORLANDC__)
+typedef unsigned __int64 ZPOS64_T;
+#else
+typedef unsigned long long int ZPOS64_T;
+#endif
+#endif
+#endif
+
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#define ZLIB_FILEFUNC_SEEK_CUR (1)
+#define ZLIB_FILEFUNC_SEEK_END (2)
+#define ZLIB_FILEFUNC_SEEK_SET (0)
+
+#define ZLIB_FILEFUNC_MODE_READ      (1)
+#define ZLIB_FILEFUNC_MODE_WRITE     (2)
+#define ZLIB_FILEFUNC_MODE_READWRITEFILTER (3)
+
+#define ZLIB_FILEFUNC_MODE_EXISTING (4)
+#define ZLIB_FILEFUNC_MODE_CREATE   (8)
+
+
+#ifndef ZCALLBACK
+ #if (defined(WIN32) || defined(_WIN32) || defined (WINDOWS) || defined (_WINDOWS)) && defined(CALLBACK) && defined (USEWINDOWS_CALLBACK)
+   #define ZCALLBACK CALLBACK
+ #else
+   #define ZCALLBACK
+ #endif
+#endif
+
+
+
+
+typedef voidpf   (ZCALLBACK *open_file_func)      OF((voidpf opaque, const char* filename, int mode));
+typedef uLong    (ZCALLBACK *read_file_func)      OF((voidpf opaque, voidpf stream, void* buf, uLong size));
+typedef uLong    (ZCALLBACK *write_file_func)     OF((voidpf opaque, voidpf stream, const void* buf, uLong size));
+typedef int      (ZCALLBACK *close_file_func)     OF((voidpf opaque, voidpf stream));
+typedef int      (ZCALLBACK *testerror_file_func) OF((voidpf opaque, voidpf stream));
+
+typedef long     (ZCALLBACK *tell_file_func)      OF((voidpf opaque, voidpf stream));
+typedef long     (ZCALLBACK *seek_file_func)      OF((voidpf opaque, voidpf stream, uLong offset, int origin));
+
+
+/* here is the "old" 32 bits structure structure */
+typedef struct zlib_filefunc_def_s
+{
+    open_file_func      zopen_file;
+    read_file_func      zread_file;
+    write_file_func     zwrite_file;
+    tell_file_func      ztell_file;
+    seek_file_func      zseek_file;
+    close_file_func     zclose_file;
+    testerror_file_func zerror_file;
+    voidpf              opaque;
+} zlib_filefunc_def;
+
+typedef ZPOS64_T (ZCALLBACK *tell64_file_func)    OF((voidpf opaque, voidpf stream));
+typedef long     (ZCALLBACK *seek64_file_func)    OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin));
+typedef voidpf   (ZCALLBACK *open64_file_func)    OF((voidpf opaque, const void* filename, int mode));
+
+typedef struct zlib_filefunc64_def_s
+{
+    open64_file_func    zopen64_file;
+    read_file_func      zread_file;
+    write_file_func     zwrite_file;
+    tell64_file_func    ztell64_file;
+    seek64_file_func    zseek64_file;
+    close_file_func     zclose_file;
+    testerror_file_func zerror_file;
+    voidpf              opaque;
+} zlib_filefunc64_def;
+
+void fill_fopen64_filefunc OF((zlib_filefunc64_def* pzlib_filefunc_def));
+void fill_fopen_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def));
+
+/* now internal definition, only for zip.c and unzip.h */
+typedef struct zlib_filefunc64_32_def_s
+{
+    zlib_filefunc64_def zfile_func64;
+    open_file_func      zopen32_file;
+    tell_file_func      ztell32_file;
+    seek_file_func      zseek32_file;
+} zlib_filefunc64_32_def;
+
+
+#define ZREAD64(filefunc,filestream,buf,size)     ((*((filefunc).zfile_func64.zread_file))   ((filefunc).zfile_func64.opaque,filestream,buf,size))
+#define ZWRITE64(filefunc,filestream,buf,size)    ((*((filefunc).zfile_func64.zwrite_file))  ((filefunc).zfile_func64.opaque,filestream,buf,size))
+//#define ZTELL64(filefunc,filestream)            ((*((filefunc).ztell64_file)) ((filefunc).opaque,filestream))
+//#define ZSEEK64(filefunc,filestream,pos,mode)   ((*((filefunc).zseek64_file)) ((filefunc).opaque,filestream,pos,mode))
+#define ZCLOSE64(filefunc,filestream)             ((*((filefunc).zfile_func64.zclose_file))  ((filefunc).zfile_func64.opaque,filestream))
+#define ZERROR64(filefunc,filestream)             ((*((filefunc).zfile_func64.zerror_file))  ((filefunc).zfile_func64.opaque,filestream))
+
+voidpf call_zopen64 OF((const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode));
+long    call_zseek64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin));
+ZPOS64_T call_ztell64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream));
+
+void    fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32);
+
+#define ZOPEN64(filefunc,filename,mode)         (call_zopen64((&(filefunc)),(filename),(mode)))
+#define ZTELL64(filefunc,filestream)            (call_ztell64((&(filefunc)),(filestream)))
+#define ZSEEK64(filefunc,filestream,pos,mode)   (call_zseek64((&(filefunc)),(filestream),(pos),(mode)))
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/ThirdParty/minizip/zip.c	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,2007 @@
+/* zip.c -- IO on .zip files using zlib
+   Version 1.1, February 14h, 2010
+   part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Modifications for Zip64 support
+         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
+
+         For more info read MiniZip_info.txt
+
+         Changes
+   Oct-2009 - Mathias Svensson - Remove old C style function prototypes
+   Oct-2009 - Mathias Svensson - Added Zip64 Support when creating new file archives
+   Oct-2009 - Mathias Svensson - Did some code cleanup and refactoring to get better overview of some functions.
+   Oct-2009 - Mathias Svensson - Added zipRemoveExtraInfoBlock to strip extra field data from its ZIP64 data
+                                 It is used when recreting zip archive with RAW when deleting items from a zip.
+                                 ZIP64 data is automaticly added to items that needs it, and existing ZIP64 data need to be removed.
+   Oct-2009 - Mathias Svensson - Added support for BZIP2 as compression mode (bzip2 lib is required)
+   Jan-2010 - back to unzip and minizip 1.0 name scheme, with compatibility layer
+
+*/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include "zlib.h"
+#include "zip.h"
+
+#ifdef STDC
+#  include <stddef.h>
+#  include <string.h>
+#  include <stdlib.h>
+#endif
+#ifdef NO_ERRNO_H
+    extern int errno;
+#else
+#   include <errno.h>
+#endif
+
+
+#ifndef local
+#  define local static
+#endif
+/* compile with -Dlocal if your debugger can't find static symbols */
+
+#ifndef VERSIONMADEBY
+# define VERSIONMADEBY   (0x0) /* platform depedent */
+#endif
+
+#ifndef Z_BUFSIZE
+#define Z_BUFSIZE (64*1024) //(16384)
+#endif
+
+#ifndef Z_MAXFILENAMEINZIP
+#define Z_MAXFILENAMEINZIP (256)
+#endif
+
+#ifndef ALLOC
+# define ALLOC(size) (malloc(size))
+#endif
+#ifndef TRYFREE
+# define TRYFREE(p) {if (p) free(p);}
+#endif
+
+/*
+#define SIZECENTRALDIRITEM (0x2e)
+#define SIZEZIPLOCALHEADER (0x1e)
+*/
+
+/* I've found an old Unix (a SunOS 4.1.3_U1) without all SEEK_* defined.... */
+
+
+// NOT sure that this work on ALL platform
+#define MAKEULONG64(a, b) ((ZPOS64_T)(((unsigned long)(a)) | ((ZPOS64_T)((unsigned long)(b))) << 32))
+
+#ifndef SEEK_CUR
+#define SEEK_CUR    1
+#endif
+
+#ifndef SEEK_END
+#define SEEK_END    2
+#endif
+
+#ifndef SEEK_SET
+#define SEEK_SET    0
+#endif
+
+#ifndef DEF_MEM_LEVEL
+#if MAX_MEM_LEVEL >= 8
+#  define DEF_MEM_LEVEL 8
+#else
+#  define DEF_MEM_LEVEL  MAX_MEM_LEVEL
+#endif
+#endif
+const char zip_copyright[] =" zip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll";
+
+
+#define SIZEDATA_INDATABLOCK (4096-(4*4))
+
+#define LOCALHEADERMAGIC    (0x04034b50)
+#define CENTRALHEADERMAGIC  (0x02014b50)
+#define ENDHEADERMAGIC      (0x06054b50)
+#define ZIP64ENDHEADERMAGIC      (0x6064b50)
+#define ZIP64ENDLOCHEADERMAGIC   (0x7064b50)
+
+#define FLAG_LOCALHEADER_OFFSET (0x06)
+#define CRC_LOCALHEADER_OFFSET  (0x0e)
+
+#define SIZECENTRALHEADER (0x2e) /* 46 */
+
+typedef struct linkedlist_datablock_internal_s
+{
+  struct linkedlist_datablock_internal_s* next_datablock;
+  uLong  avail_in_this_block;
+  uLong  filled_in_this_block;
+  uLong  unused; /* for future use and alignement */
+  unsigned char data[SIZEDATA_INDATABLOCK];
+} linkedlist_datablock_internal;
+
+typedef struct linkedlist_data_s
+{
+    linkedlist_datablock_internal* first_block;
+    linkedlist_datablock_internal* last_block;
+} linkedlist_data;
+
+
+typedef struct
+{
+    z_stream stream;            /* zLib stream structure for inflate */
+#ifdef HAVE_BZIP2
+    bz_stream bstream;          /* bzLib stream structure for bziped */
+#endif
+
+    int  stream_initialised;    /* 1 is stream is initialised */
+    uInt pos_in_buffered_data;  /* last written byte in buffered_data */
+
+    ZPOS64_T pos_local_header;     /* offset of the local header of the file
+                                     currenty writing */
+    char* central_header;       /* central header data for the current file */
+    uLong size_centralExtra;
+    uLong size_centralheader;   /* size of the central header for cur file */
+    uLong size_centralExtraFree; /* Extra bytes allocated to the centralheader but that are not used */
+    uLong flag;                 /* flag of the file currently writing */
+
+    int  method;                /* compression method of file currenty wr.*/
+    int  raw;                   /* 1 for directly writing raw data */
+    Byte buffered_data[Z_BUFSIZE];/* buffer contain compressed data to be writ*/
+    uLong dosDate;
+    uLong crc32;
+    int  encrypt;
+    int  zip64;               /* Add ZIP64 extened information in the extra field */
+    ZPOS64_T pos_zip64extrainfo;
+    ZPOS64_T totalCompressedData;
+    ZPOS64_T totalUncompressedData;
+#ifndef NOCRYPT
+    unsigned long keys[3];     /* keys defining the pseudo-random sequence */
+    const unsigned long* pcrc_32_tab;
+    int crypt_header_size;
+#endif
+} curfile64_info;
+
+typedef struct
+{
+    zlib_filefunc64_32_def z_filefunc;
+    voidpf filestream;        /* io structore of the zipfile */
+    linkedlist_data central_dir;/* datablock with central dir in construction*/
+    int  in_opened_file_inzip;  /* 1 if a file in the zip is currently writ.*/
+    curfile64_info ci;            /* info on the file curretly writing */
+
+    ZPOS64_T begin_pos;            /* position of the beginning of the zipfile */
+    ZPOS64_T add_position_when_writting_offset;
+    ZPOS64_T number_entry;
+
+#ifndef NO_ADDFILEINEXISTINGZIP
+    char *globalcomment;
+#endif
+
+} zip64_internal;
+
+
+#ifndef NOCRYPT
+#define INCLUDECRYPTINGCODE_IFCRYPTALLOWED
+#include "crypt.h"
+#endif
+
+local linkedlist_datablock_internal* allocate_new_datablock()
+{
+    linkedlist_datablock_internal* ldi;
+    ldi = (linkedlist_datablock_internal*)
+                 ALLOC(sizeof(linkedlist_datablock_internal));
+    if (ldi!=NULL)
+    {
+        ldi->next_datablock = NULL ;
+        ldi->filled_in_this_block = 0 ;
+        ldi->avail_in_this_block = SIZEDATA_INDATABLOCK ;
+    }
+    return ldi;
+}
+
+local void free_datablock(linkedlist_datablock_internal* ldi)
+{
+    while (ldi!=NULL)
+    {
+        linkedlist_datablock_internal* ldinext = ldi->next_datablock;
+        TRYFREE(ldi);
+        ldi = ldinext;
+    }
+}
+
+local void init_linkedlist(linkedlist_data* ll)
+{
+    ll->first_block = ll->last_block = NULL;
+}
+
+local void free_linkedlist(linkedlist_data* ll)
+{
+    free_datablock(ll->first_block);
+    ll->first_block = ll->last_block = NULL;
+}
+
+
+local int add_data_in_datablock(linkedlist_data* ll, const void* buf, uLong len)
+{
+    linkedlist_datablock_internal* ldi;
+    const unsigned char* from_copy;
+
+    if (ll==NULL)
+        return ZIP_INTERNALERROR;
+
+    if (ll->last_block == NULL)
+    {
+        ll->first_block = ll->last_block = allocate_new_datablock();
+        if (ll->first_block == NULL)
+            return ZIP_INTERNALERROR;
+    }
+
+    ldi = ll->last_block;
+    from_copy = (unsigned char*)buf;
+
+    while (len>0)
+    {
+        uInt copy_this;
+        uInt i;
+        unsigned char* to_copy;
+
+        if (ldi->avail_in_this_block==0)
+        {
+            ldi->next_datablock = allocate_new_datablock();
+            if (ldi->next_datablock == NULL)
+                return ZIP_INTERNALERROR;
+            ldi = ldi->next_datablock ;
+            ll->last_block = ldi;
+        }
+
+        if (ldi->avail_in_this_block < len)
+            copy_this = (uInt)ldi->avail_in_this_block;
+        else
+            copy_this = (uInt)len;
+
+        to_copy = &(ldi->data[ldi->filled_in_this_block]);
+
+        for (i=0;i<copy_this;i++)
+            *(to_copy+i)=*(from_copy+i);
+
+        ldi->filled_in_this_block += copy_this;
+        ldi->avail_in_this_block -= copy_this;
+        from_copy += copy_this ;
+        len -= copy_this;
+    }
+    return ZIP_OK;
+}
+
+
+
+/****************************************************************************/
+
+#ifndef NO_ADDFILEINEXISTINGZIP
+/* ===========================================================================
+   Inputs a long in LSB order to the given file
+   nbByte == 1, 2 ,4 or 8 (byte, short or long, ZPOS64_T)
+*/
+
+local int zip64local_putValue OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T x, int nbByte));
+local int zip64local_putValue (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T x, int nbByte)
+{
+    unsigned char buf[8];
+    int n;
+    for (n = 0; n < nbByte; n++)
+    {
+        buf[n] = (unsigned char)(x & 0xff);
+        x >>= 8;
+    }
+    if (x != 0)
+      {     /* data overflow - hack for ZIP64 (X Roche) */
+      for (n = 0; n < nbByte; n++)
+        {
+          buf[n] = 0xff;
+        }
+      }
+
+    if (ZWRITE64(*pzlib_filefunc_def,filestream,buf,nbByte)!=(uLong)nbByte)
+        return ZIP_ERRNO;
+    else
+        return ZIP_OK;
+}
+
+local void zip64local_putValue_inmemory OF((void* dest, ZPOS64_T x, int nbByte));
+local void zip64local_putValue_inmemory (void* dest, ZPOS64_T x, int nbByte)
+{
+    unsigned char* buf=(unsigned char*)dest;
+    int n;
+    for (n = 0; n < nbByte; n++) {
+        buf[n] = (unsigned char)(x & 0xff);
+        x >>= 8;
+    }
+
+    if (x != 0)
+    {     /* data overflow - hack for ZIP64 */
+       for (n = 0; n < nbByte; n++)
+       {
+          buf[n] = 0xff;
+       }
+    }
+}
+
+/****************************************************************************/
+
+
+local uLong zip64local_TmzDateToDosDate(const tm_zip* ptm)
+{
+    uLong year = (uLong)ptm->tm_year;
+    if (year>=1980)
+        year-=1980;
+    else if (year>=80)
+        year-=80;
+    return
+      (uLong) (((ptm->tm_mday) + (32 * (ptm->tm_mon+1)) + (512 * year)) << 16) |
+        ((ptm->tm_sec/2) + (32* ptm->tm_min) + (2048 * (uLong)ptm->tm_hour));
+}
+
+
+/****************************************************************************/
+
+local int zip64local_getByte OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, int *pi));
+
+local int zip64local_getByte(const zlib_filefunc64_32_def* pzlib_filefunc_def,voidpf filestream,int* pi)
+{
+    unsigned char c;
+    int err = (int)ZREAD64(*pzlib_filefunc_def,filestream,&c,1);
+    if (err==1)
+    {
+        *pi = (int)c;
+        return ZIP_OK;
+    }
+    else
+    {
+        if (ZERROR64(*pzlib_filefunc_def,filestream))
+            return ZIP_ERRNO;
+        else
+            return ZIP_EOF;
+    }
+}
+
+
+/* ===========================================================================
+   Reads a long in LSB order from the given gz_stream. Sets
+*/
+local int zip64local_getShort OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX));
+
+local int zip64local_getShort (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong* pX)
+{
+    uLong x ;
+    int i = 0;
+    int err;
+
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x = (uLong)i;
+
+    if (err==ZIP_OK)
+        err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x += ((uLong)i)<<8;
+
+    if (err==ZIP_OK)
+        *pX = x;
+    else
+        *pX = 0;
+    return err;
+}
+
+local int zip64local_getLong OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX));
+
+local int zip64local_getLong (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong* pX)
+{
+    uLong x ;
+    int i = 0;
+    int err;
+
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x = (uLong)i;
+
+    if (err==ZIP_OK)
+        err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x += ((uLong)i)<<8;
+
+    if (err==ZIP_OK)
+        err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x += ((uLong)i)<<16;
+
+    if (err==ZIP_OK)
+        err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x += ((uLong)i)<<24;
+
+    if (err==ZIP_OK)
+        *pX = x;
+    else
+        *pX = 0;
+    return err;
+}
+
+local int zip64local_getLong64 OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX));
+
+
+local int zip64local_getLong64 (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX)
+{
+  ZPOS64_T x;
+  int i = 0;
+  int err;
+
+  err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x = (ZPOS64_T)i;
+
+  if (err==ZIP_OK)
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x += ((ZPOS64_T)i)<<8;
+
+  if (err==ZIP_OK)
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x += ((ZPOS64_T)i)<<16;
+
+  if (err==ZIP_OK)
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x += ((ZPOS64_T)i)<<24;
+
+  if (err==ZIP_OK)
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x += ((ZPOS64_T)i)<<32;
+
+  if (err==ZIP_OK)
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x += ((ZPOS64_T)i)<<40;
+
+  if (err==ZIP_OK)
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x += ((ZPOS64_T)i)<<48;
+
+  if (err==ZIP_OK)
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x += ((ZPOS64_T)i)<<56;
+
+  if (err==ZIP_OK)
+    *pX = x;
+  else
+    *pX = 0;
+
+  return err;
+}
+
+#ifndef BUFREADCOMMENT
+#define BUFREADCOMMENT (0x400)
+#endif
+/*
+  Locate the Central directory of a zipfile (at the end, just before
+    the global comment)
+*/
+local ZPOS64_T zip64local_SearchCentralDir OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream));
+
+local ZPOS64_T zip64local_SearchCentralDir(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)
+{
+  unsigned char* buf;
+  ZPOS64_T uSizeFile;
+  ZPOS64_T uBackRead;
+  ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */
+  ZPOS64_T uPosFound=0;
+
+  if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0)
+    return 0;
+
+
+  uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream);
+
+  if (uMaxBack>uSizeFile)
+    uMaxBack = uSizeFile;
+
+  buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4);
+  if (buf==NULL)
+    return 0;
+
+  uBackRead = 4;
+  while (uBackRead<uMaxBack)
+  {
+    uLong uReadSize;
+    ZPOS64_T uReadPos ;
+    int i;
+    if (uBackRead+BUFREADCOMMENT>uMaxBack)
+      uBackRead = uMaxBack;
+    else
+      uBackRead+=BUFREADCOMMENT;
+    uReadPos = uSizeFile-uBackRead ;
+
+    uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ?
+      (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos);
+    if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0)
+      break;
+
+    if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize)
+      break;
+
+    for (i=(int)uReadSize-3; (i--)>0;)
+      if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) &&
+        ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06))
+      {
+        uPosFound = uReadPos+i;
+        break;
+      }
+
+      if (uPosFound!=0)
+        break;
+  }
+  TRYFREE(buf);
+  return uPosFound;
+}
+
+/*
+Locate the End of Zip64 Central directory locator and from there find the CD of a zipfile (at the end, just before
+the global comment)
+*/
+local ZPOS64_T zip64local_SearchCentralDir64 OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream));
+
+local ZPOS64_T zip64local_SearchCentralDir64(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)
+{
+  unsigned char* buf;
+  ZPOS64_T uSizeFile;
+  ZPOS64_T uBackRead;
+  ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */
+  ZPOS64_T uPosFound=0;
+  uLong uL;
+  ZPOS64_T relativeOffset;
+
+  if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0)
+    return 0;
+
+  uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream);
+
+  if (uMaxBack>uSizeFile)
+    uMaxBack = uSizeFile;
+
+  buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4);
+  if (buf==NULL)
+    return 0;
+
+  uBackRead = 4;
+  while (uBackRead<uMaxBack)
+  {
+    uLong uReadSize;
+    ZPOS64_T uReadPos;
+    int i;
+    if (uBackRead+BUFREADCOMMENT>uMaxBack)
+      uBackRead = uMaxBack;
+    else
+      uBackRead+=BUFREADCOMMENT;
+    uReadPos = uSizeFile-uBackRead ;
+
+    uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ?
+      (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos);
+    if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0)
+      break;
+
+    if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize)
+      break;
+
+    for (i=(int)uReadSize-3; (i--)>0;)
+    {
+      // Signature "0x07064b50" Zip64 end of central directory locater
+      if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && ((*(buf+i+2))==0x06) && ((*(buf+i+3))==0x07))
+      {
+        uPosFound = uReadPos+i;
+        break;
+      }
+    }
+
+      if (uPosFound!=0)
+        break;
+  }
+
+  TRYFREE(buf);
+  if (uPosFound == 0)
+    return 0;
+
+  /* Zip64 end of central directory locator */
+  if (ZSEEK64(*pzlib_filefunc_def,filestream, uPosFound,ZLIB_FILEFUNC_SEEK_SET)!=0)
+    return 0;
+
+  /* the signature, already checked */
+  if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK)
+    return 0;
+
+  /* number of the disk with the start of the zip64 end of  central directory */
+  if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK)
+    return 0;
+  if (uL != 0)
+    return 0;
+
+  /* relative offset of the zip64 end of central directory record */
+  if (zip64local_getLong64(pzlib_filefunc_def,filestream,&relativeOffset)!=ZIP_OK)
+    return 0;
+
+  /* total number of disks */
+  if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK)
+    return 0;
+  if (uL != 1)
+    return 0;
+
+  /* Goto Zip64 end of central directory record */
+  if (ZSEEK64(*pzlib_filefunc_def,filestream, relativeOffset,ZLIB_FILEFUNC_SEEK_SET)!=0)
+    return 0;
+
+  /* the signature */
+  if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK)
+    return 0;
+
+  if (uL != 0x06064b50) // signature of 'Zip64 end of central directory'
+    return 0;
+
+  return relativeOffset;
+}
+
+int LoadCentralDirectoryRecord(zip64_internal* pziinit)
+{
+  int err=ZIP_OK;
+  ZPOS64_T byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/
+
+  ZPOS64_T size_central_dir;     /* size of the central directory  */
+  ZPOS64_T offset_central_dir;   /* offset of start of central directory */
+  ZPOS64_T central_pos;
+  uLong uL;
+
+  uLong number_disk;          /* number of the current dist, used for
+                              spaning ZIP, unsupported, always 0*/
+  uLong number_disk_with_CD;  /* number the the disk with central dir, used
+                              for spaning ZIP, unsupported, always 0*/
+  ZPOS64_T number_entry;
+  ZPOS64_T number_entry_CD;      /* total number of entries in
+                                the central dir
+                                (same than number_entry on nospan) */
+  uLong VersionMadeBy;
+  uLong VersionNeeded;
+  uLong size_comment;
+
+  int hasZIP64Record = 0;
+
+  // check first if we find a ZIP64 record
+  central_pos = zip64local_SearchCentralDir64(&pziinit->z_filefunc,pziinit->filestream);
+  if(central_pos > 0)
+  {
+    hasZIP64Record = 1;
+  }
+  else if(central_pos == 0)
+  {
+    central_pos = zip64local_SearchCentralDir(&pziinit->z_filefunc,pziinit->filestream);
+  }
+
+/* disable to allow appending to empty ZIP archive
+        if (central_pos==0)
+            err=ZIP_ERRNO;
+*/
+
+  if(hasZIP64Record)
+  {
+    ZPOS64_T sizeEndOfCentralDirectory;
+    if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, central_pos, ZLIB_FILEFUNC_SEEK_SET) != 0)
+      err=ZIP_ERRNO;
+
+    /* the signature, already checked */
+    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&uL)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* size of zip64 end of central directory record */
+    if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream, &sizeEndOfCentralDirectory)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* version made by */
+    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &VersionMadeBy)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* version needed to extract */
+    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &VersionNeeded)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* number of this disk */
+    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&number_disk)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* number of the disk with the start of the central directory */
+    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&number_disk_with_CD)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* total number of entries in the central directory on this disk */
+    if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream, &number_entry)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* total number of entries in the central directory */
+    if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&number_entry_CD)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    if ((number_entry_CD!=number_entry) || (number_disk_with_CD!=0) || (number_disk!=0))
+      err=ZIP_BADZIPFILE;
+
+    /* size of the central directory */
+    if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&size_central_dir)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* offset of start of central directory with respect to the
+    starting disk number */
+    if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&offset_central_dir)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    // TODO..
+    // read the comment from the standard central header.
+    size_comment = 0;
+  }
+  else
+  {
+    // Read End of central Directory info
+    if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0)
+      err=ZIP_ERRNO;
+
+    /* the signature, already checked */
+    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&uL)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* number of this disk */
+    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream,&number_disk)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* number of the disk with the start of the central directory */
+    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream,&number_disk_with_CD)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* total number of entries in the central dir on this disk */
+    number_entry = 0;
+    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK)
+      err=ZIP_ERRNO;
+    else
+      number_entry = uL;
+
+    /* total number of entries in the central dir */
+    number_entry_CD = 0;
+    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK)
+      err=ZIP_ERRNO;
+    else
+      number_entry_CD = uL;
+
+    if ((number_entry_CD!=number_entry) || (number_disk_with_CD!=0) || (number_disk!=0))
+      err=ZIP_BADZIPFILE;
+
+    /* size of the central directory */
+    size_central_dir = 0;
+    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK)
+      err=ZIP_ERRNO;
+    else
+      size_central_dir = uL;
+
+    /* offset of start of central directory with respect to the starting disk number */
+    offset_central_dir = 0;
+    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK)
+      err=ZIP_ERRNO;
+    else
+      offset_central_dir = uL;
+
+
+    /* zipfile global comment length */
+    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &size_comment)!=ZIP_OK)
+      err=ZIP_ERRNO;
+  }
+
+  if ((central_pos<offset_central_dir+size_central_dir) &&
+    (err==ZIP_OK))
+    err=ZIP_BADZIPFILE;
+
+  if (err!=ZIP_OK)
+  {
+    ZCLOSE64(pziinit->z_filefunc, pziinit->filestream);
+    return ZIP_ERRNO;
+  }
+
+  if (size_comment>0)
+  {
+    pziinit->globalcomment = (char*)ALLOC(size_comment+1);
+    if (pziinit->globalcomment)
+    {
+      size_comment = ZREAD64(pziinit->z_filefunc, pziinit->filestream, pziinit->globalcomment,size_comment);
+      pziinit->globalcomment[size_comment]=0;
+    }
+  }
+
+  byte_before_the_zipfile = central_pos - (offset_central_dir+size_central_dir);
+  pziinit->add_position_when_writting_offset = byte_before_the_zipfile;
+
+  {
+    ZPOS64_T size_central_dir_to_read = size_central_dir;
+    size_t buf_size = SIZEDATA_INDATABLOCK;
+    void* buf_read = (void*)ALLOC(buf_size);
+    if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, offset_central_dir + byte_before_the_zipfile, ZLIB_FILEFUNC_SEEK_SET) != 0)
+      err=ZIP_ERRNO;
+
+    while ((size_central_dir_to_read>0) && (err==ZIP_OK))
+    {
+      ZPOS64_T read_this = SIZEDATA_INDATABLOCK;
+      if (read_this > size_central_dir_to_read)
+        read_this = size_central_dir_to_read;
+
+      if (ZREAD64(pziinit->z_filefunc, pziinit->filestream,buf_read,(uLong)read_this) != read_this)
+        err=ZIP_ERRNO;
+
+      if (err==ZIP_OK)
+        err = add_data_in_datablock(&pziinit->central_dir,buf_read, (uLong)read_this);
+
+      size_central_dir_to_read-=read_this;
+    }
+    TRYFREE(buf_read);
+  }
+  pziinit->begin_pos = byte_before_the_zipfile;
+  pziinit->number_entry = number_entry_CD;
+
+  if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, offset_central_dir+byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET) != 0)
+    err=ZIP_ERRNO;
+
+  return err;
+}
+
+
+#endif /* !NO_ADDFILEINEXISTINGZIP*/
+
+
+/************************************************************/
+extern zipFile ZEXPORT zipOpen3 (const void *pathname, int append, zipcharpc* globalcomment, zlib_filefunc64_32_def* pzlib_filefunc64_32_def)
+{
+    zip64_internal ziinit;
+    zip64_internal* zi;
+    int err=ZIP_OK;
+
+    ziinit.z_filefunc.zseek32_file = NULL;
+    ziinit.z_filefunc.ztell32_file = NULL;
+    if (pzlib_filefunc64_32_def==NULL)
+        fill_fopen64_filefunc(&ziinit.z_filefunc.zfile_func64);
+    else
+        ziinit.z_filefunc = *pzlib_filefunc64_32_def;
+
+    ziinit.filestream = ZOPEN64(ziinit.z_filefunc,
+                  pathname,
+                  (append == APPEND_STATUS_CREATE) ?
+                  (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_CREATE) :
+                    (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_EXISTING));
+
+    if (ziinit.filestream == NULL)
+        return NULL;
+
+    if (append == APPEND_STATUS_CREATEAFTER)
+        ZSEEK64(ziinit.z_filefunc,ziinit.filestream,0,SEEK_END);
+
+    ziinit.begin_pos = ZTELL64(ziinit.z_filefunc,ziinit.filestream);
+    ziinit.in_opened_file_inzip = 0;
+    ziinit.ci.stream_initialised = 0;
+    ziinit.number_entry = 0;
+    ziinit.add_position_when_writting_offset = 0;
+    init_linkedlist(&(ziinit.central_dir));
+
+
+
+    zi = (zip64_internal*)ALLOC(sizeof(zip64_internal));
+    if (zi==NULL)
+    {
+        ZCLOSE64(ziinit.z_filefunc,ziinit.filestream);
+        return NULL;
+    }
+
+    /* now we add file in a zipfile */
+#    ifndef NO_ADDFILEINEXISTINGZIP
+    ziinit.globalcomment = NULL;
+    if (append == APPEND_STATUS_ADDINZIP)
+    {
+      // Read and Cache Central Directory Records
+      err = LoadCentralDirectoryRecord(&ziinit);
+    }
+
+    if (globalcomment)
+    {
+      *globalcomment = ziinit.globalcomment;
+    }
+#    endif /* !NO_ADDFILEINEXISTINGZIP*/
+
+    if (err != ZIP_OK)
+    {
+#    ifndef NO_ADDFILEINEXISTINGZIP
+        TRYFREE(ziinit.globalcomment);
+#    endif /* !NO_ADDFILEINEXISTINGZIP*/
+        TRYFREE(zi);
+        return NULL;
+    }
+    else
+    {
+        *zi = ziinit;
+        return (zipFile)zi;
+    }
+}
+
+extern zipFile ZEXPORT zipOpen2 (const char *pathname, int append, zipcharpc* globalcomment, zlib_filefunc_def* pzlib_filefunc32_def)
+{
+    if (pzlib_filefunc32_def != NULL)
+    {
+        zlib_filefunc64_32_def zlib_filefunc64_32_def_fill;
+        fill_zlib_filefunc64_32_def_from_filefunc32(&zlib_filefunc64_32_def_fill,pzlib_filefunc32_def);
+        return zipOpen3(pathname, append, globalcomment, &zlib_filefunc64_32_def_fill);
+    }
+    else
+        return zipOpen3(pathname, append, globalcomment, NULL);
+}
+
+extern zipFile ZEXPORT zipOpen2_64 (const void *pathname, int append, zipcharpc* globalcomment, zlib_filefunc64_def* pzlib_filefunc_def)
+{
+    if (pzlib_filefunc_def != NULL)
+    {
+        zlib_filefunc64_32_def zlib_filefunc64_32_def_fill;
+        zlib_filefunc64_32_def_fill.zfile_func64 = *pzlib_filefunc_def;
+        zlib_filefunc64_32_def_fill.ztell32_file = NULL;
+        zlib_filefunc64_32_def_fill.zseek32_file = NULL;
+        return zipOpen3(pathname, append, globalcomment, &zlib_filefunc64_32_def_fill);
+    }
+    else
+        return zipOpen3(pathname, append, globalcomment, NULL);
+}
+
+
+
+extern zipFile ZEXPORT zipOpen (const char* pathname, int append)
+{
+    return zipOpen3((const void*)pathname,append,NULL,NULL);
+}
+
+extern zipFile ZEXPORT zipOpen64 (const void* pathname, int append)
+{
+    return zipOpen3(pathname,append,NULL,NULL);
+}
+
+int Write_LocalFileHeader(zip64_internal* zi, const char* filename, uInt size_extrafield_local, const void* extrafield_local)
+{
+  /* write the local header */
+  int err;
+  uInt size_filename = (uInt)strlen(filename);
+  uInt size_extrafield = size_extrafield_local;
+
+  err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)LOCALHEADERMAGIC, 4);
+
+  if (err==ZIP_OK)
+  {
+    if(zi->ci.zip64)
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2);/* version needed to extract */
+    else
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)20,2);/* version needed to extract */
+  }
+
+  if (err==ZIP_OK)
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.flag,2);
+
+  if (err==ZIP_OK)
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.method,2);
+
+  if (err==ZIP_OK)
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.dosDate,4);
+
+  // CRC / Compressed size / Uncompressed size will be filled in later and rewritten later
+  if (err==ZIP_OK)
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* crc 32, unknown */
+  if (err==ZIP_OK)
+  {
+    if(zi->ci.zip64)
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xFFFFFFFF,4); /* compressed size, unknown */
+    else
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* compressed size, unknown */
+  }
+  if (err==ZIP_OK)
+  {
+    if(zi->ci.zip64)
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xFFFFFFFF,4); /* uncompressed size, unknown */
+    else
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* uncompressed size, unknown */
+  }
+
+  if (err==ZIP_OK)
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_filename,2);
+
+  if(zi->ci.zip64)
+  {
+    size_extrafield += 20;
+  }
+
+  if (err==ZIP_OK)
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_extrafield,2);
+
+  if ((err==ZIP_OK) && (size_filename > 0))
+  {
+    if (ZWRITE64(zi->z_filefunc,zi->filestream,filename,size_filename)!=size_filename)
+      err = ZIP_ERRNO;
+  }
+
+  if ((err==ZIP_OK) && (size_extrafield_local > 0))
+  {
+    if (ZWRITE64(zi->z_filefunc, zi->filestream, extrafield_local, size_extrafield_local) != size_extrafield_local)
+      err = ZIP_ERRNO;
+  }
+
+
+  if ((err==ZIP_OK) && (zi->ci.zip64))
+  {
+      // write the Zip64 extended info
+      short HeaderID = 1;
+      short DataSize = 16;
+      ZPOS64_T CompressedSize = 0;
+      ZPOS64_T UncompressedSize = 0;
+
+      // Remember position of Zip64 extended info for the local file header. (needed when we update size after done with file)
+      zi->ci.pos_zip64extrainfo = ZTELL64(zi->z_filefunc,zi->filestream);
+
+      err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (short)HeaderID,2);
+      err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (short)DataSize,2);
+
+      err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (ZPOS64_T)UncompressedSize,8);
+      err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (ZPOS64_T)CompressedSize,8);
+  }
+
+  return err;
+}
+
+/*
+ NOTE.
+ When writing RAW the ZIP64 extended information in extrafield_local and extrafield_global needs to be stripped
+ before calling this function it can be done with zipRemoveExtraInfoBlock
+
+ It is not done here because then we need to realloc a new buffer since parameters are 'const' and I want to minimize
+ unnecessary allocations.
+ */
+extern int ZEXPORT zipOpenNewFileInZip4_64 (zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                         const void* extrafield_local, uInt size_extrafield_local,
+                                         const void* extrafield_global, uInt size_extrafield_global,
+                                         const char* comment, int method, int level, int raw,
+                                         int windowBits,int memLevel, int strategy,
+                                         const char* password, uLong crcForCrypting,
+                                         uLong versionMadeBy, uLong flagBase, int zip64)
+{
+    zip64_internal* zi;
+    uInt size_filename;
+    uInt size_comment;
+    uInt i;
+    int err = ZIP_OK;
+
+#    ifdef NOCRYPT
+    (crcForCrypting);
+    if (password != NULL)
+        return ZIP_PARAMERROR;
+#    endif
+
+    if (file == NULL)
+        return ZIP_PARAMERROR;
+
+#ifdef HAVE_BZIP2
+    if ((method!=0) && (method!=Z_DEFLATED) && (method!=Z_BZIP2ED))
+      return ZIP_PARAMERROR;
+#else
+    if ((method!=0) && (method!=Z_DEFLATED))
+      return ZIP_PARAMERROR;
+#endif
+
+    zi = (zip64_internal*)file;
+
+    if (zi->in_opened_file_inzip == 1)
+    {
+        err = zipCloseFileInZip (file);
+        if (err != ZIP_OK)
+            return err;
+    }
+
+    if (filename==NULL)
+        filename="-";
+
+    if (comment==NULL)
+        size_comment = 0;
+    else
+        size_comment = (uInt)strlen(comment);
+
+    size_filename = (uInt)strlen(filename);
+
+    if (zipfi == NULL)
+        zi->ci.dosDate = 0;
+    else
+    {
+        if (zipfi->dosDate != 0)
+            zi->ci.dosDate = zipfi->dosDate;
+        else
+          zi->ci.dosDate = zip64local_TmzDateToDosDate(&zipfi->tmz_date);
+    }
+
+    zi->ci.flag = flagBase;
+    if ((level==8) || (level==9))
+      zi->ci.flag |= 2;
+    if (level==2)
+      zi->ci.flag |= 4;
+    if (level==1)
+      zi->ci.flag |= 6;
+    if (password != NULL)
+      zi->ci.flag |= 1;
+
+    zi->ci.crc32 = 0;
+    zi->ci.method = method;
+    zi->ci.encrypt = 0;
+    zi->ci.stream_initialised = 0;
+    zi->ci.pos_in_buffered_data = 0;
+    zi->ci.raw = raw;
+    zi->ci.pos_local_header = ZTELL64(zi->z_filefunc,zi->filestream);
+
+    zi->ci.size_centralheader = SIZECENTRALHEADER + size_filename + size_extrafield_global + size_comment;
+    zi->ci.size_centralExtraFree = 32; // Extra space we have reserved in case we need to add ZIP64 extra info data
+
+    zi->ci.central_header = (char*)ALLOC((uInt)zi->ci.size_centralheader + zi->ci.size_centralExtraFree);
+
+    zi->ci.size_centralExtra = size_extrafield_global;
+    zip64local_putValue_inmemory(zi->ci.central_header,(uLong)CENTRALHEADERMAGIC,4);
+    /* version info */
+    zip64local_putValue_inmemory(zi->ci.central_header+4,(uLong)versionMadeBy,2);
+    zip64local_putValue_inmemory(zi->ci.central_header+6,(uLong)20,2);
+    zip64local_putValue_inmemory(zi->ci.central_header+8,(uLong)zi->ci.flag,2);
+    zip64local_putValue_inmemory(zi->ci.central_header+10,(uLong)zi->ci.method,2);
+    zip64local_putValue_inmemory(zi->ci.central_header+12,(uLong)zi->ci.dosDate,4);
+    zip64local_putValue_inmemory(zi->ci.central_header+16,(uLong)0,4); /*crc*/
+    zip64local_putValue_inmemory(zi->ci.central_header+20,(uLong)0,4); /*compr size*/
+    zip64local_putValue_inmemory(zi->ci.central_header+24,(uLong)0,4); /*uncompr size*/
+    zip64local_putValue_inmemory(zi->ci.central_header+28,(uLong)size_filename,2);
+    zip64local_putValue_inmemory(zi->ci.central_header+30,(uLong)size_extrafield_global,2);
+    zip64local_putValue_inmemory(zi->ci.central_header+32,(uLong)size_comment,2);
+    zip64local_putValue_inmemory(zi->ci.central_header+34,(uLong)0,2); /*disk nm start*/
+
+    if (zipfi==NULL)
+        zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)0,2);
+    else
+        zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)zipfi->internal_fa,2);
+
+    if (zipfi==NULL)
+        zip64local_putValue_inmemory(zi->ci.central_header+38,(uLong)0,4);
+    else
+        zip64local_putValue_inmemory(zi->ci.central_header+38,(uLong)zipfi->external_fa,4);
+
+    if(zi->ci.pos_local_header >= 0xffffffff)
+      zip64local_putValue_inmemory(zi->ci.central_header+42,(uLong)0xffffffff,4);
+    else
+      zip64local_putValue_inmemory(zi->ci.central_header+42,(uLong)zi->ci.pos_local_header - zi->add_position_when_writting_offset,4);
+
+    for (i=0;i<size_filename;i++)
+        *(zi->ci.central_header+SIZECENTRALHEADER+i) = *(filename+i);
+
+    for (i=0;i<size_extrafield_global;i++)
+        *(zi->ci.central_header+SIZECENTRALHEADER+size_filename+i) =
+              *(((const char*)extrafield_global)+i);
+
+    for (i=0;i<size_comment;i++)
+        *(zi->ci.central_header+SIZECENTRALHEADER+size_filename+
+              size_extrafield_global+i) = *(comment+i);
+    if (zi->ci.central_header == NULL)
+        return ZIP_INTERNALERROR;
+
+    zi->ci.zip64 = zip64;
+    zi->ci.totalCompressedData = 0;
+    zi->ci.totalUncompressedData = 0;
+    zi->ci.pos_zip64extrainfo = 0;
+
+    err = Write_LocalFileHeader(zi, filename, size_extrafield_local, extrafield_local);
+
+#ifdef HAVE_BZIP2
+    zi->ci.bstream.avail_in = (uInt)0;
+    zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE;
+    zi->ci.bstream.next_out = (char*)zi->ci.buffered_data;
+    zi->ci.bstream.total_in_hi32 = 0;
+    zi->ci.bstream.total_in_lo32 = 0;
+    zi->ci.bstream.total_out_hi32 = 0;
+    zi->ci.bstream.total_out_lo32 = 0;
+#endif
+
+    zi->ci.stream.avail_in = (uInt)0;
+    zi->ci.stream.avail_out = (uInt)Z_BUFSIZE;
+    zi->ci.stream.next_out = zi->ci.buffered_data;
+    zi->ci.stream.total_in = 0;
+    zi->ci.stream.total_out = 0;
+    zi->ci.stream.data_type = Z_BINARY;
+
+#ifdef HAVE_BZIP2
+    if ((err==ZIP_OK) && (zi->ci.method == Z_DEFLATED || zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw))
+#else
+    if ((err==ZIP_OK) && (zi->ci.method == Z_DEFLATED) && (!zi->ci.raw))
+#endif
+    {
+        if(zi->ci.method == Z_DEFLATED)
+        {
+          zi->ci.stream.zalloc = (alloc_func)0;
+          zi->ci.stream.zfree = (free_func)0;
+          zi->ci.stream.opaque = (voidpf)0;
+
+          if (windowBits>0)
+              windowBits = -windowBits;
+
+          err = deflateInit2(&zi->ci.stream, level, Z_DEFLATED, windowBits, memLevel, strategy);
+
+          if (err==Z_OK)
+              zi->ci.stream_initialised = Z_DEFLATED;
+        }
+        else if(zi->ci.method == Z_BZIP2ED)
+        {
+#ifdef HAVE_BZIP2
+            // Init BZip stuff here
+          zi->ci.bstream.bzalloc = 0;
+          zi->ci.bstream.bzfree = 0;
+          zi->ci.bstream.opaque = (voidpf)0;
+
+          err = BZ2_bzCompressInit(&zi->ci.bstream, level, 0,35);
+          if(err == BZ_OK)
+            zi->ci.stream_initialised = Z_BZIP2ED;
+#endif
+        }
+
+    }
+
+#    ifndef NOCRYPT
+    zi->ci.crypt_header_size = 0;
+    if ((err==Z_OK) && (password != NULL))
+    {
+        unsigned char bufHead[RAND_HEAD_LEN];
+        unsigned int sizeHead;
+        zi->ci.encrypt = 1;
+        zi->ci.pcrc_32_tab = get_crc_table();
+        /*init_keys(password,zi->ci.keys,zi->ci.pcrc_32_tab);*/
+
+        sizeHead=crypthead(password,bufHead,RAND_HEAD_LEN,zi->ci.keys,zi->ci.pcrc_32_tab,crcForCrypting);
+        zi->ci.crypt_header_size = sizeHead;
+
+        if (ZWRITE64(zi->z_filefunc,zi->filestream,bufHead,sizeHead) != sizeHead)
+                err = ZIP_ERRNO;
+    }
+#    endif
+
+    if (err==Z_OK)
+        zi->in_opened_file_inzip = 1;
+    return err;
+}
+
+extern int ZEXPORT zipOpenNewFileInZip4 (zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                         const void* extrafield_local, uInt size_extrafield_local,
+                                         const void* extrafield_global, uInt size_extrafield_global,
+                                         const char* comment, int method, int level, int raw,
+                                         int windowBits,int memLevel, int strategy,
+                                         const char* password, uLong crcForCrypting,
+                                         uLong versionMadeBy, uLong flagBase)
+{
+    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
+                                 extrafield_local, size_extrafield_local,
+                                 extrafield_global, size_extrafield_global,
+                                 comment, method, level, raw,
+                                 windowBits, memLevel, strategy,
+                                 password, crcForCrypting, versionMadeBy, flagBase, 0);
+}
+
+extern int ZEXPORT zipOpenNewFileInZip3 (zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                         const void* extrafield_local, uInt size_extrafield_local,
+                                         const void* extrafield_global, uInt size_extrafield_global,
+                                         const char* comment, int method, int level, int raw,
+                                         int windowBits,int memLevel, int strategy,
+                                         const char* password, uLong crcForCrypting)
+{
+    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
+                                 extrafield_local, size_extrafield_local,
+                                 extrafield_global, size_extrafield_global,
+                                 comment, method, level, raw,
+                                 windowBits, memLevel, strategy,
+                                 password, crcForCrypting, VERSIONMADEBY, 0, 0);
+}
+
+extern int ZEXPORT zipOpenNewFileInZip3_64(zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                         const void* extrafield_local, uInt size_extrafield_local,
+                                         const void* extrafield_global, uInt size_extrafield_global,
+                                         const char* comment, int method, int level, int raw,
+                                         int windowBits,int memLevel, int strategy,
+                                         const char* password, uLong crcForCrypting, int zip64)
+{
+    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
+                                 extrafield_local, size_extrafield_local,
+                                 extrafield_global, size_extrafield_global,
+                                 comment, method, level, raw,
+                                 windowBits, memLevel, strategy,
+                                 password, crcForCrypting, VERSIONMADEBY, 0, zip64);
+}
+
+extern int ZEXPORT zipOpenNewFileInZip2(zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                        const void* extrafield_local, uInt size_extrafield_local,
+                                        const void* extrafield_global, uInt size_extrafield_global,
+                                        const char* comment, int method, int level, int raw)
+{
+    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
+                                 extrafield_local, size_extrafield_local,
+                                 extrafield_global, size_extrafield_global,
+                                 comment, method, level, raw,
+                                 -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
+                                 NULL, 0, VERSIONMADEBY, 0, 0);
+}
+
+extern int ZEXPORT zipOpenNewFileInZip2_64(zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                        const void* extrafield_local, uInt size_extrafield_local,
+                                        const void* extrafield_global, uInt size_extrafield_global,
+                                        const char* comment, int method, int level, int raw, int zip64)
+{
+    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
+                                 extrafield_local, size_extrafield_local,
+                                 extrafield_global, size_extrafield_global,
+                                 comment, method, level, raw,
+                                 -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
+                                 NULL, 0, VERSIONMADEBY, 0, zip64);
+}
+
+extern int ZEXPORT zipOpenNewFileInZip64 (zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                        const void* extrafield_local, uInt size_extrafield_local,
+                                        const void*extrafield_global, uInt size_extrafield_global,
+                                        const char* comment, int method, int level, int zip64)
+{
+    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
+                                 extrafield_local, size_extrafield_local,
+                                 extrafield_global, size_extrafield_global,
+                                 comment, method, level, 0,
+                                 -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
+                                 NULL, 0, VERSIONMADEBY, 0, zip64);
+}
+
+extern int ZEXPORT zipOpenNewFileInZip (zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                        const void* extrafield_local, uInt size_extrafield_local,
+                                        const void*extrafield_global, uInt size_extrafield_global,
+                                        const char* comment, int method, int level)
+{
+    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
+                                 extrafield_local, size_extrafield_local,
+                                 extrafield_global, size_extrafield_global,
+                                 comment, method, level, 0,
+                                 -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
+                                 NULL, 0, VERSIONMADEBY, 0, 0);
+}
+
+local int zip64FlushWriteBuffer(zip64_internal* zi)
+{
+    int err=ZIP_OK;
+
+    if (zi->ci.encrypt != 0)
+    {
+#ifndef NOCRYPT
+        uInt i;
+        int t;
+        for (i=0;i<zi->ci.pos_in_buffered_data;i++)
+            zi->ci.buffered_data[i] = zencode(zi->ci.keys, zi->ci.pcrc_32_tab, zi->ci.buffered_data[i],t);
+#endif
+    }
+
+    if (ZWRITE64(zi->z_filefunc,zi->filestream,zi->ci.buffered_data,zi->ci.pos_in_buffered_data) != zi->ci.pos_in_buffered_data)
+      err = ZIP_ERRNO;
+
+    zi->ci.totalCompressedData += zi->ci.pos_in_buffered_data;
+
+#ifdef HAVE_BZIP2
+    if(zi->ci.method == Z_BZIP2ED)
+    {
+      zi->ci.totalUncompressedData += zi->ci.bstream.total_in_lo32;
+      zi->ci.bstream.total_in_lo32 = 0;
+      zi->ci.bstream.total_in_hi32 = 0;
+    }
+    else
+#endif
+    {
+      zi->ci.totalUncompressedData += zi->ci.stream.total_in;
+      zi->ci.stream.total_in = 0;
+    }
+
+
+    zi->ci.pos_in_buffered_data = 0;
+
+    return err;
+}
+
+extern int ZEXPORT zipWriteInFileInZip (zipFile file,const void* buf,unsigned int len)
+{
+    zip64_internal* zi;
+    int err=ZIP_OK;
+
+    if (file == NULL)
+        return ZIP_PARAMERROR;
+    zi = (zip64_internal*)file;
+
+    if (zi->in_opened_file_inzip == 0)
+        return ZIP_PARAMERROR;
+
+    zi->ci.crc32 = crc32(zi->ci.crc32,buf,(uInt)len);
+
+#ifdef HAVE_BZIP2
+    if(zi->ci.method == Z_BZIP2ED && (!zi->ci.raw))
+    {
+      zi->ci.bstream.next_in = (void*)buf;
+      zi->ci.bstream.avail_in = len;
+      err = BZ_RUN_OK;
+
+      while ((err==BZ_RUN_OK) && (zi->ci.bstream.avail_in>0))
+      {
+        if (zi->ci.bstream.avail_out == 0)
+        {
+          if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO)
+            err = ZIP_ERRNO;
+          zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE;
+          zi->ci.bstream.next_out = (char*)zi->ci.buffered_data;
+        }
+
+
+        if(err != BZ_RUN_OK)
+          break;
+
+        if ((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw))
+        {
+          uLong uTotalOutBefore_lo = zi->ci.bstream.total_out_lo32;
+//          uLong uTotalOutBefore_hi = zi->ci.bstream.total_out_hi32;
+          err=BZ2_bzCompress(&zi->ci.bstream,  BZ_RUN);
+
+          zi->ci.pos_in_buffered_data += (uInt)(zi->ci.bstream.total_out_lo32 - uTotalOutBefore_lo) ;
+        }
+      }
+
+      if(err == BZ_RUN_OK)
+        err = ZIP_OK;
+    }
+    else
+#endif
+    {
+      zi->ci.stream.next_in = (Bytef*)buf;
+      zi->ci.stream.avail_in = len;
+
+      while ((err==ZIP_OK) && (zi->ci.stream.avail_in>0))
+      {
+          if (zi->ci.stream.avail_out == 0)
+          {
+              if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO)
+                  err = ZIP_ERRNO;
+              zi->ci.stream.avail_out = (uInt)Z_BUFSIZE;
+              zi->ci.stream.next_out = zi->ci.buffered_data;
+          }
+
+
+          if(err != ZIP_OK)
+              break;
+
+          if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw))
+          {
+              uLong uTotalOutBefore = zi->ci.stream.total_out;
+              err=deflate(&zi->ci.stream,  Z_NO_FLUSH);
+              if(uTotalOutBefore > zi->ci.stream.total_out)
+              {
+                int bBreak = 0;
+                bBreak++;
+              }
+
+              zi->ci.pos_in_buffered_data += (uInt)(zi->ci.stream.total_out - uTotalOutBefore) ;
+          }
+          else
+          {
+              uInt copy_this,i;
+              if (zi->ci.stream.avail_in < zi->ci.stream.avail_out)
+                  copy_this = zi->ci.stream.avail_in;
+              else
+                  copy_this = zi->ci.stream.avail_out;
+
+              for (i = 0; i < copy_this; i++)
+                  *(((char*)zi->ci.stream.next_out)+i) =
+                      *(((const char*)zi->ci.stream.next_in)+i);
+              {
+                  zi->ci.stream.avail_in -= copy_this;
+                  zi->ci.stream.avail_out-= copy_this;
+                  zi->ci.stream.next_in+= copy_this;
+                  zi->ci.stream.next_out+= copy_this;
+                  zi->ci.stream.total_in+= copy_this;
+                  zi->ci.stream.total_out+= copy_this;
+                  zi->ci.pos_in_buffered_data += copy_this;
+              }
+          }
+      }// while(...)
+    }
+
+    return err;
+}
+
+extern int ZEXPORT zipCloseFileInZipRaw (zipFile file, uLong uncompressed_size, uLong crc32)
+{
+    return zipCloseFileInZipRaw64 (file, uncompressed_size, crc32);
+}
+
+extern int ZEXPORT zipCloseFileInZipRaw64 (zipFile file, ZPOS64_T uncompressed_size, uLong crc32)
+{
+    zip64_internal* zi;
+    ZPOS64_T compressed_size;
+    uLong invalidValue = 0xffffffff;
+    short datasize = 0;
+    int err=ZIP_OK;
+
+    if (file == NULL)
+        return ZIP_PARAMERROR;
+    zi = (zip64_internal*)file;
+
+    if (zi->in_opened_file_inzip == 0)
+        return ZIP_PARAMERROR;
+    zi->ci.stream.avail_in = 0;
+
+    if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw))
+                {
+                        while (err==ZIP_OK)
+                        {
+                                uLong uTotalOutBefore;
+                                if (zi->ci.stream.avail_out == 0)
+                                {
+                                        if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO)
+                                                err = ZIP_ERRNO;
+                                        zi->ci.stream.avail_out = (uInt)Z_BUFSIZE;
+                                        zi->ci.stream.next_out = zi->ci.buffered_data;
+                                }
+                                uTotalOutBefore = zi->ci.stream.total_out;
+                                err=deflate(&zi->ci.stream,  Z_FINISH);
+                                zi->ci.pos_in_buffered_data += (uInt)(zi->ci.stream.total_out - uTotalOutBefore) ;
+                        }
+                }
+    else if ((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw))
+    {
+#ifdef HAVE_BZIP2
+      err = BZ_FINISH_OK;
+      while (err==BZ_FINISH_OK)
+      {
+        uLong uTotalOutBefore;
+        if (zi->ci.bstream.avail_out == 0)
+        {
+          if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO)
+            err = ZIP_ERRNO;
+          zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE;
+          zi->ci.bstream.next_out = (char*)zi->ci.buffered_data;
+        }
+        uTotalOutBefore = zi->ci.bstream.total_out_lo32;
+        err=BZ2_bzCompress(&zi->ci.bstream,  BZ_FINISH);
+        if(err == BZ_STREAM_END)
+          err = Z_STREAM_END;
+
+        zi->ci.pos_in_buffered_data += (uInt)(zi->ci.bstream.total_out_lo32 - uTotalOutBefore);
+      }
+
+      if(err == BZ_FINISH_OK)
+        err = ZIP_OK;
+#endif
+    }
+
+    if (err==Z_STREAM_END)
+        err=ZIP_OK; /* this is normal */
+
+    if ((zi->ci.pos_in_buffered_data>0) && (err==ZIP_OK))
+                {
+        if (zip64FlushWriteBuffer(zi)==ZIP_ERRNO)
+            err = ZIP_ERRNO;
+                }
+
+    if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw))
+    {
+        int tmp_err = deflateEnd(&zi->ci.stream);
+        if (err == ZIP_OK)
+            err = tmp_err;
+        zi->ci.stream_initialised = 0;
+    }
+#ifdef HAVE_BZIP2
+    else if((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw))
+    {
+      int tmperr = BZ2_bzCompressEnd(&zi->ci.bstream);
+                        if (err==ZIP_OK)
+                                err = tmperr;
+                        zi->ci.stream_initialised = 0;
+    }
+#endif
+
+    if (!zi->ci.raw)
+    {
+        crc32 = (uLong)zi->ci.crc32;
+        uncompressed_size = zi->ci.totalUncompressedData;
+    }
+    compressed_size = zi->ci.totalCompressedData;
+
+#    ifndef NOCRYPT
+    compressed_size += zi->ci.crypt_header_size;
+#    endif
+
+    // update Current Item crc and sizes,
+    if(compressed_size >= 0xffffffff || uncompressed_size >= 0xffffffff || zi->ci.pos_local_header >= 0xffffffff)
+    {
+      /*version Made by*/
+      zip64local_putValue_inmemory(zi->ci.central_header+4,(uLong)45,2);
+      /*version needed*/
+      zip64local_putValue_inmemory(zi->ci.central_header+6,(uLong)45,2);
+
+    }
+
+    zip64local_putValue_inmemory(zi->ci.central_header+16,crc32,4); /*crc*/
+
+
+    if(compressed_size >= 0xffffffff)
+      zip64local_putValue_inmemory(zi->ci.central_header+20, invalidValue,4); /*compr size*/
+    else
+      zip64local_putValue_inmemory(zi->ci.central_header+20, compressed_size,4); /*compr size*/
+
+    /// set internal file attributes field
+    if (zi->ci.stream.data_type == Z_ASCII)
+        zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)Z_ASCII,2);
+
+    if(uncompressed_size >= 0xffffffff)
+      zip64local_putValue_inmemory(zi->ci.central_header+24, invalidValue,4); /*uncompr size*/
+    else
+      zip64local_putValue_inmemory(zi->ci.central_header+24, uncompressed_size,4); /*uncompr size*/
+
+    // Add ZIP64 extra info field for uncompressed size
+    if(uncompressed_size >= 0xffffffff)
+      datasize += 8;
+
+    // Add ZIP64 extra info field for compressed size
+    if(compressed_size >= 0xffffffff)
+      datasize += 8;
+
+    // Add ZIP64 extra info field for relative offset to local file header of current file
+    if(zi->ci.pos_local_header >= 0xffffffff)
+      datasize += 8;
+
+    if(datasize > 0)
+    {
+      char* p = NULL;
+
+      if((uLong)(datasize + 4) > zi->ci.size_centralExtraFree)
+      {
+        // we can not write more data to the buffer that we have room for.
+        return ZIP_BADZIPFILE;
+      }
+
+      p = zi->ci.central_header + zi->ci.size_centralheader;
+
+      // Add Extra Information Header for 'ZIP64 information'
+      zip64local_putValue_inmemory(p, 0x0001, 2); // HeaderID
+      p += 2;
+      zip64local_putValue_inmemory(p, datasize, 2); // DataSize
+      p += 2;
+
+      if(uncompressed_size >= 0xffffffff)
+      {
+        zip64local_putValue_inmemory(p, uncompressed_size, 8);
+        p += 8;
+      }
+
+      if(compressed_size >= 0xffffffff)
+      {
+        zip64local_putValue_inmemory(p, compressed_size, 8);
+        p += 8;
+      }
+
+      if(zi->ci.pos_local_header >= 0xffffffff)
+      {
+        zip64local_putValue_inmemory(p, zi->ci.pos_local_header, 8);
+        p += 8;
+      }
+
+      // Update how much extra free space we got in the memory buffer
+      // and increase the centralheader size so the new ZIP64 fields are included
+      // ( 4 below is the size of HeaderID and DataSize field )
+      zi->ci.size_centralExtraFree -= datasize + 4;
+      zi->ci.size_centralheader += datasize + 4;
+
+      // Update the extra info size field
+      zi->ci.size_centralExtra += datasize + 4;
+      zip64local_putValue_inmemory(zi->ci.central_header+30,(uLong)zi->ci.size_centralExtra,2);
+    }
+
+    if (err==ZIP_OK)
+        err = add_data_in_datablock(&zi->central_dir, zi->ci.central_header, (uLong)zi->ci.size_centralheader);
+
+    free(zi->ci.central_header);
+
+    if (err==ZIP_OK)
+    {
+        // Update the LocalFileHeader with the new values.
+
+        ZPOS64_T cur_pos_inzip = ZTELL64(zi->z_filefunc,zi->filestream);
+
+        if (ZSEEK64(zi->z_filefunc,zi->filestream, zi->ci.pos_local_header + 14,ZLIB_FILEFUNC_SEEK_SET)!=0)
+            err = ZIP_ERRNO;
+
+        if (err==ZIP_OK)
+            err = zip64local_putValue(&zi->z_filefunc,zi->filestream,crc32,4); /* crc 32, unknown */
+
+        if(uncompressed_size >= 0xffffffff || compressed_size >= 0xffffffff )
+        {
+          if(zi->ci.pos_zip64extrainfo > 0)
+          {
+            // Update the size in the ZIP64 extended field.
+            if (ZSEEK64(zi->z_filefunc,zi->filestream, zi->ci.pos_zip64extrainfo + 4,ZLIB_FILEFUNC_SEEK_SET)!=0)
+              err = ZIP_ERRNO;
+
+            if (err==ZIP_OK) /* compressed size, unknown */
+              err = zip64local_putValue(&zi->z_filefunc, zi->filestream, uncompressed_size, 8);
+
+            if (err==ZIP_OK) /* uncompressed size, unknown */
+              err = zip64local_putValue(&zi->z_filefunc, zi->filestream, compressed_size, 8);
+          }
+          else
+              err = ZIP_BADZIPFILE; // Caller passed zip64 = 0, so no room for zip64 info -> fatal
+        }
+        else
+        {
+          if (err==ZIP_OK) /* compressed size, unknown */
+              err = zip64local_putValue(&zi->z_filefunc,zi->filestream,compressed_size,4);
+
+          if (err==ZIP_OK) /* uncompressed size, unknown */
+              err = zip64local_putValue(&zi->z_filefunc,zi->filestream,uncompressed_size,4);
+        }
+
+        if (ZSEEK64(zi->z_filefunc,zi->filestream, cur_pos_inzip,ZLIB_FILEFUNC_SEEK_SET)!=0)
+            err = ZIP_ERRNO;
+    }
+
+    zi->number_entry ++;
+    zi->in_opened_file_inzip = 0;
+
+    return err;
+}
+
+extern int ZEXPORT zipCloseFileInZip (zipFile file)
+{
+    return zipCloseFileInZipRaw (file,0,0);
+}
+
+int Write_Zip64EndOfCentralDirectoryLocator(zip64_internal* zi, ZPOS64_T zip64eocd_pos_inzip)
+{
+  int err = ZIP_OK;
+  ZPOS64_T pos = zip64eocd_pos_inzip - zi->add_position_when_writting_offset;
+
+  err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ZIP64ENDLOCHEADERMAGIC,4);
+
+  /*num disks*/
+    if (err==ZIP_OK) /* number of the disk with the start of the central directory */
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4);
+
+  /*relative offset*/
+    if (err==ZIP_OK) /* Relative offset to the Zip64EndOfCentralDirectory */
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream, pos,8);
+
+  /*total disks*/ /* Do not support spawning of disk so always say 1 here*/
+    if (err==ZIP_OK) /* number of the disk with the start of the central directory */
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)1,4);
+
+    return err;
+}
+
+int Write_Zip64EndOfCentralDirectoryRecord(zip64_internal* zi, uLong size_centraldir, ZPOS64_T centraldir_pos_inzip)
+{
+  int err = ZIP_OK;
+
+  uLong Zip64DataSize = 44;
+
+  err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ZIP64ENDHEADERMAGIC,4);
+
+  if (err==ZIP_OK) /* size of this 'zip64 end of central directory' */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(ZPOS64_T)Zip64DataSize,8); // why ZPOS64_T of this ?
+
+  if (err==ZIP_OK) /* version made by */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2);
+
+  if (err==ZIP_OK) /* version needed */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2);
+
+  if (err==ZIP_OK) /* number of this disk */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4);
+
+  if (err==ZIP_OK) /* number of the disk with the start of the central directory */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4);
+
+  if (err==ZIP_OK) /* total number of entries in the central dir on this disk */
+    err = zip64local_putValue(&zi->z_filefunc, zi->filestream, zi->number_entry, 8);
+
+  if (err==ZIP_OK) /* total number of entries in the central dir */
+    err = zip64local_putValue(&zi->z_filefunc, zi->filestream, zi->number_entry, 8);
+
+  if (err==ZIP_OK) /* size of the central directory */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(ZPOS64_T)size_centraldir,8);
+
+  if (err==ZIP_OK) /* offset of start of central directory with respect to the starting disk number */
+  {
+    ZPOS64_T pos = centraldir_pos_inzip - zi->add_position_when_writting_offset;
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (ZPOS64_T)pos,8);
+  }
+  return err;
+}
+int Write_EndOfCentralDirectoryRecord(zip64_internal* zi, uLong size_centraldir, ZPOS64_T centraldir_pos_inzip)
+{
+  int err = ZIP_OK;
+
+  /*signature*/
+  err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ENDHEADERMAGIC,4);
+
+  if (err==ZIP_OK) /* number of this disk */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,2);
+
+  if (err==ZIP_OK) /* number of the disk with the start of the central directory */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,2);
+
+  if (err==ZIP_OK) /* total number of entries in the central dir on this disk */
+  {
+    {
+      if(zi->number_entry >= 0xFFFF)
+        err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xffff,2); // use value in ZIP64 record
+      else
+        err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->number_entry,2);
+    }
+  }
+
+  if (err==ZIP_OK) /* total number of entries in the central dir */
+  {
+    if(zi->number_entry >= 0xFFFF)
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xffff,2); // use value in ZIP64 record
+    else
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->number_entry,2);
+  }
+
+  if (err==ZIP_OK) /* size of the central directory */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_centraldir,4);
+
+  if (err==ZIP_OK) /* offset of start of central directory with respect to the starting disk number */
+  {
+    ZPOS64_T pos = centraldir_pos_inzip - zi->add_position_when_writting_offset;
+    if(pos >= 0xffffffff)
+    {
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (uLong)0xffffffff,4);
+    }
+    else
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (uLong)(centraldir_pos_inzip - zi->add_position_when_writting_offset),4);
+  }
+
+   return err;
+}
+
+int Write_GlobalComment(zip64_internal* zi, const char* global_comment)
+{
+  int err = ZIP_OK;
+  uInt size_global_comment = 0;
+
+  if(global_comment != NULL)
+    size_global_comment = (uInt)strlen(global_comment);
+
+  err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_global_comment,2);
+
+  if (err == ZIP_OK && size_global_comment > 0)
+  {
+    if (ZWRITE64(zi->z_filefunc,zi->filestream, global_comment, size_global_comment) != size_global_comment)
+      err = ZIP_ERRNO;
+  }
+  return err;
+}
+
+extern int ZEXPORT zipClose (zipFile file, const char* global_comment)
+{
+    zip64_internal* zi;
+    int err = 0;
+    uLong size_centraldir = 0;
+    ZPOS64_T centraldir_pos_inzip;
+    ZPOS64_T pos;
+
+    if (file == NULL)
+        return ZIP_PARAMERROR;
+
+    zi = (zip64_internal*)file;
+
+    if (zi->in_opened_file_inzip == 1)
+    {
+        err = zipCloseFileInZip (file);
+    }
+
+#ifndef NO_ADDFILEINEXISTINGZIP
+    if (global_comment==NULL)
+        global_comment = zi->globalcomment;
+#endif
+
+    centraldir_pos_inzip = ZTELL64(zi->z_filefunc,zi->filestream);
+
+    if (err==ZIP_OK)
+    {
+        linkedlist_datablock_internal* ldi = zi->central_dir.first_block;
+        while (ldi!=NULL)
+        {
+            if ((err==ZIP_OK) && (ldi->filled_in_this_block>0))
+            {
+                if (ZWRITE64(zi->z_filefunc,zi->filestream, ldi->data, ldi->filled_in_this_block) != ldi->filled_in_this_block)
+                    err = ZIP_ERRNO;
+            }
+
+            size_centraldir += ldi->filled_in_this_block;
+            ldi = ldi->next_datablock;
+        }
+    }
+    free_linkedlist(&(zi->central_dir));
+
+    pos = centraldir_pos_inzip - zi->add_position_when_writting_offset;
+    if(pos >= 0xffffffff || zi->number_entry > 0xFFFF)
+    {
+      ZPOS64_T Zip64EOCDpos = ZTELL64(zi->z_filefunc,zi->filestream);
+      Write_Zip64EndOfCentralDirectoryRecord(zi, size_centraldir, centraldir_pos_inzip);
+
+      Write_Zip64EndOfCentralDirectoryLocator(zi, Zip64EOCDpos);
+    }
+
+    if (err==ZIP_OK)
+      err = Write_EndOfCentralDirectoryRecord(zi, size_centraldir, centraldir_pos_inzip);
+
+    if(err == ZIP_OK)
+      err = Write_GlobalComment(zi, global_comment);
+
+    if (ZCLOSE64(zi->z_filefunc,zi->filestream) != 0)
+        if (err == ZIP_OK)
+            err = ZIP_ERRNO;
+
+#ifndef NO_ADDFILEINEXISTINGZIP
+    TRYFREE(zi->globalcomment);
+#endif
+    TRYFREE(zi);
+
+    return err;
+}
+
+extern int ZEXPORT zipRemoveExtraInfoBlock (char* pData, int* dataLen, short sHeader)
+{
+  char* p = pData;
+  int size = 0;
+  char* pNewHeader;
+  char* pTmp;
+  short header;
+  short dataSize;
+
+  int retVal = ZIP_OK;
+
+  if(pData == NULL || *dataLen < 4)
+    return ZIP_PARAMERROR;
+
+  pNewHeader = (char*)ALLOC(*dataLen);
+  pTmp = pNewHeader;
+
+  while(p < (pData + *dataLen))
+  {
+    header = *(short*)p;
+    dataSize = *(((short*)p)+1);
+
+    if( header == sHeader ) // Header found.
+    {
+      p += dataSize + 4; // skip it. do not copy to temp buffer
+    }
+    else
+    {
+      // Extra Info block should not be removed, So copy it to the temp buffer.
+      memcpy(pTmp, p, dataSize + 4);
+      p += dataSize + 4;
+      size += dataSize + 4;
+    }
+
+  }
+
+  if(size < *dataLen)
+  {
+    // clean old extra info block.
+    memset(pData,0, *dataLen);
+
+    // copy the new extra info block over the old
+    if(size > 0)
+      memcpy(pData, pNewHeader, size);
+
+    // set the new extra info size
+    *dataLen = size;
+
+    retVal = ZIP_OK;
+  }
+  else
+    retVal = ZIP_ERRNO;
+
+  TRYFREE(pNewHeader);
+
+  return retVal;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/ThirdParty/minizip/zip.h	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,362 @@
+/* zip.h -- IO on .zip files using zlib
+   Version 1.1, February 14h, 2010
+   part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Modifications for Zip64 support
+         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
+
+         For more info read MiniZip_info.txt
+
+         ---------------------------------------------------------------------------
+
+   Condition of use and distribution are the same than zlib :
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+        ---------------------------------------------------------------------------
+
+        Changes
+
+        See header of zip.h
+
+*/
+
+#ifndef _zip12_H
+#define _zip12_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//#define HAVE_BZIP2
+
+#ifndef _ZLIB_H
+#include "zlib.h"
+#endif
+
+#ifndef _ZLIBIOAPI_H
+#include "ioapi.h"
+#endif
+
+#ifdef HAVE_BZIP2
+#include "bzlib.h"
+#endif
+
+#define Z_BZIP2ED 12
+
+#if defined(STRICTZIP) || defined(STRICTZIPUNZIP)
+/* like the STRICT of WIN32, we define a pointer that cannot be converted
+    from (void*) without cast */
+typedef struct TagzipFile__ { int unused; } zipFile__;
+typedef zipFile__ *zipFile;
+#else
+typedef voidp zipFile;
+#endif
+
+#define ZIP_OK                          (0)
+#define ZIP_EOF                         (0)
+#define ZIP_ERRNO                       (Z_ERRNO)
+#define ZIP_PARAMERROR                  (-102)
+#define ZIP_BADZIPFILE                  (-103)
+#define ZIP_INTERNALERROR               (-104)
+
+#ifndef DEF_MEM_LEVEL
+#  if MAX_MEM_LEVEL >= 8
+#    define DEF_MEM_LEVEL 8
+#  else
+#    define DEF_MEM_LEVEL  MAX_MEM_LEVEL
+#  endif
+#endif
+/* default memLevel */
+
+/* tm_zip contain date/time info */
+typedef struct tm_zip_s
+{
+    uInt tm_sec;            /* seconds after the minute - [0,59] */
+    uInt tm_min;            /* minutes after the hour - [0,59] */
+    uInt tm_hour;           /* hours since midnight - [0,23] */
+    uInt tm_mday;           /* day of the month - [1,31] */
+    uInt tm_mon;            /* months since January - [0,11] */
+    uInt tm_year;           /* years - [1980..2044] */
+} tm_zip;
+
+typedef struct
+{
+    tm_zip      tmz_date;       /* date in understandable format           */
+    uLong       dosDate;       /* if dos_date == 0, tmu_date is used      */
+/*    uLong       flag;        */   /* general purpose bit flag        2 bytes */
+
+    uLong       internal_fa;    /* internal file attributes        2 bytes */
+    uLong       external_fa;    /* external file attributes        4 bytes */
+} zip_fileinfo;
+
+typedef const char* zipcharpc;
+
+
+#define APPEND_STATUS_CREATE        (0)
+#define APPEND_STATUS_CREATEAFTER   (1)
+#define APPEND_STATUS_ADDINZIP      (2)
+
+extern zipFile ZEXPORT zipOpen OF((const char *pathname, int append));
+extern zipFile ZEXPORT zipOpen64 OF((const void *pathname, int append));
+/*
+  Create a zipfile.
+     pathname contain on Windows XP a filename like "c:\\zlib\\zlib113.zip" or on
+       an Unix computer "zlib/zlib113.zip".
+     if the file pathname exist and append==APPEND_STATUS_CREATEAFTER, the zip
+       will be created at the end of the file.
+         (useful if the file contain a self extractor code)
+     if the file pathname exist and append==APPEND_STATUS_ADDINZIP, we will
+       add files in existing zip (be sure you don't add file that doesn't exist)
+     If the zipfile cannot be opened, the return value is NULL.
+     Else, the return value is a zipFile Handle, usable with other function
+       of this zip package.
+*/
+
+/* Note : there is no delete function into a zipfile.
+   If you want delete file into a zipfile, you must open a zipfile, and create another
+   Of couse, you can use RAW reading and writing to copy the file you did not want delte
+*/
+
+extern zipFile ZEXPORT zipOpen2 OF((const char *pathname,
+                                   int append,
+                                   zipcharpc* globalcomment,
+                                   zlib_filefunc_def* pzlib_filefunc_def));
+
+extern zipFile ZEXPORT zipOpen2_64 OF((const void *pathname,
+                                   int append,
+                                   zipcharpc* globalcomment,
+                                   zlib_filefunc64_def* pzlib_filefunc_def));
+
+extern int ZEXPORT zipOpenNewFileInZip OF((zipFile file,
+                       const char* filename,
+                       const zip_fileinfo* zipfi,
+                       const void* extrafield_local,
+                       uInt size_extrafield_local,
+                       const void* extrafield_global,
+                       uInt size_extrafield_global,
+                       const char* comment,
+                       int method,
+                       int level));
+
+extern int ZEXPORT zipOpenNewFileInZip64 OF((zipFile file,
+                       const char* filename,
+                       const zip_fileinfo* zipfi,
+                       const void* extrafield_local,
+                       uInt size_extrafield_local,
+                       const void* extrafield_global,
+                       uInt size_extrafield_global,
+                       const char* comment,
+                       int method,
+                       int level,
+                       int zip64));
+
+/*
+  Open a file in the ZIP for writing.
+  filename : the filename in zip (if NULL, '-' without quote will be used
+  *zipfi contain supplemental information
+  if extrafield_local!=NULL and size_extrafield_local>0, extrafield_local
+    contains the extrafield data the the local header
+  if extrafield_global!=NULL and size_extrafield_global>0, extrafield_global
+    contains the extrafield data the the local header
+  if comment != NULL, comment contain the comment string
+  method contain the compression method (0 for store, Z_DEFLATED for deflate)
+  level contain the level of compression (can be Z_DEFAULT_COMPRESSION)
+  zip64 is set to 1 if a zip64 extended information block should be added to the local file header.
+                    this MUST be '1' if the uncompressed size is >= 0xffffffff.
+
+*/
+
+
+extern int ZEXPORT zipOpenNewFileInZip2 OF((zipFile file,
+                                            const char* filename,
+                                            const zip_fileinfo* zipfi,
+                                            const void* extrafield_local,
+                                            uInt size_extrafield_local,
+                                            const void* extrafield_global,
+                                            uInt size_extrafield_global,
+                                            const char* comment,
+                                            int method,
+                                            int level,
+                                            int raw));
+
+
+extern int ZEXPORT zipOpenNewFileInZip2_64 OF((zipFile file,
+                                            const char* filename,
+                                            const zip_fileinfo* zipfi,
+                                            const void* extrafield_local,
+                                            uInt size_extrafield_local,
+                                            const void* extrafield_global,
+                                            uInt size_extrafield_global,
+                                            const char* comment,
+                                            int method,
+                                            int level,
+                                            int raw,
+                                            int zip64));
+/*
+  Same than zipOpenNewFileInZip, except if raw=1, we write raw file
+ */
+
+extern int ZEXPORT zipOpenNewFileInZip3 OF((zipFile file,
+                                            const char* filename,
+                                            const zip_fileinfo* zipfi,
+                                            const void* extrafield_local,
+                                            uInt size_extrafield_local,
+                                            const void* extrafield_global,
+                                            uInt size_extrafield_global,
+                                            const char* comment,
+                                            int method,
+                                            int level,
+                                            int raw,
+                                            int windowBits,
+                                            int memLevel,
+                                            int strategy,
+                                            const char* password,
+                                            uLong crcForCrypting));
+
+extern int ZEXPORT zipOpenNewFileInZip3_64 OF((zipFile file,
+                                            const char* filename,
+                                            const zip_fileinfo* zipfi,
+                                            const void* extrafield_local,
+                                            uInt size_extrafield_local,
+                                            const void* extrafield_global,
+                                            uInt size_extrafield_global,
+                                            const char* comment,
+                                            int method,
+                                            int level,
+                                            int raw,
+                                            int windowBits,
+                                            int memLevel,
+                                            int strategy,
+                                            const char* password,
+                                            uLong crcForCrypting,
+                                            int zip64
+                                            ));
+
+/*
+  Same than zipOpenNewFileInZip2, except
+    windowBits,memLevel,,strategy : see parameter strategy in deflateInit2
+    password : crypting password (NULL for no crypting)
+    crcForCrypting : crc of file to compress (needed for crypting)
+ */
+
+extern int ZEXPORT zipOpenNewFileInZip4 OF((zipFile file,
+                                            const char* filename,
+                                            const zip_fileinfo* zipfi,
+                                            const void* extrafield_local,
+                                            uInt size_extrafield_local,
+                                            const void* extrafield_global,
+                                            uInt size_extrafield_global,
+                                            const char* comment,
+                                            int method,
+                                            int level,
+                                            int raw,
+                                            int windowBits,
+                                            int memLevel,
+                                            int strategy,
+                                            const char* password,
+                                            uLong crcForCrypting,
+                                            uLong versionMadeBy,
+                                            uLong flagBase
+                                            ));
+
+
+extern int ZEXPORT zipOpenNewFileInZip4_64 OF((zipFile file,
+                                            const char* filename,
+                                            const zip_fileinfo* zipfi,
+                                            const void* extrafield_local,
+                                            uInt size_extrafield_local,
+                                            const void* extrafield_global,
+                                            uInt size_extrafield_global,
+                                            const char* comment,
+                                            int method,
+                                            int level,
+                                            int raw,
+                                            int windowBits,
+                                            int memLevel,
+                                            int strategy,
+                                            const char* password,
+                                            uLong crcForCrypting,
+                                            uLong versionMadeBy,
+                                            uLong flagBase,
+                                            int zip64
+                                            ));
+/*
+  Same than zipOpenNewFileInZip4, except
+    versionMadeBy : value for Version made by field
+    flag : value for flag field (compression level info will be added)
+ */
+
+
+extern int ZEXPORT zipWriteInFileInZip OF((zipFile file,
+                       const void* buf,
+                       unsigned len));
+/*
+  Write data in the zipfile
+*/
+
+extern int ZEXPORT zipCloseFileInZip OF((zipFile file));
+/*
+  Close the current file in the zipfile
+*/
+
+extern int ZEXPORT zipCloseFileInZipRaw OF((zipFile file,
+                                            uLong uncompressed_size,
+                                            uLong crc32));
+
+extern int ZEXPORT zipCloseFileInZipRaw64 OF((zipFile file,
+                                            ZPOS64_T uncompressed_size,
+                                            uLong crc32));
+
+/*
+  Close the current file in the zipfile, for file opened with
+    parameter raw=1 in zipOpenNewFileInZip2
+  uncompressed_size and crc32 are value for the uncompressed size
+*/
+
+extern int ZEXPORT zipClose OF((zipFile file,
+                const char* global_comment));
+/*
+  Close the zipfile
+*/
+
+
+extern int ZEXPORT zipRemoveExtraInfoBlock OF((char* pData, int* dataLen, short sHeader));
+/*
+  zipRemoveExtraInfoBlock -  Added by Mathias Svensson
+
+  Remove extra information block from a extra information data for the local file header or central directory header
+
+  It is needed to remove ZIP64 extra information blocks when before data is written if using RAW mode.
+
+  0x0001 is the signature header for the ZIP64 extra information blocks
+
+  usage.
+                        Remove ZIP64 Extra information from a central director extra field data
+              zipRemoveExtraInfoBlock(pCenDirExtraFieldData, &nCenDirExtraFieldDataLen, 0x0001);
+
+                        Remove ZIP64 Extra information from a Local File Header extra field data
+        zipRemoveExtraInfoBlock(pLocalHeaderExtraFieldData, &nLocalHeaderExtraFieldDataLen, 0x0001);
+*/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _zip64_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/ThirdParty/patch/NOTES.txt	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,70 @@
+===========
+INFORMATION
+===========
+
+This is a precompiled version of the "patch" standard tool for
+Windows. It was compiled using the MSYS framework.
+
+The binaries originate from the "Git for Windows 1.9.5" package
+(https://msysgit.github.io/). The build instructions have been
+provided on the discussion group of Git for Windows [1]. They are
+copied/pasted below for reference.
+
+
+
+================
+UPSTREAM PROJECT
+================
+
+URL to the upstream project:
+http://savannah.gnu.org/projects/patch/
+
+License of patch: GPLv2 (GNU General Public License v2)
+
+Copyright (C) 1988 Larry Wall "with lots o' patches by Paul Eggert"
+Copyright (C) 1997 Free Software Foundation, Inc.
+
+
+
+======================
+BUILD INSTRUCTIONS [1]
+======================
+
+The easiest way to find out about this is to install the Git SDK, then 
+run 
+
+     pacman -Qu $(which patch.exe) 
+
+to find out which package contains the `patch.exe` binary. It so happens 
+to be patch.2.7.5-1 at the moment. Since this is an MSys2 package (not a 
+MinGW one, otherwise the patch utility would be in /mingw64/bin/, not 
+/usr/bin/), this package is built from the recipes in 
+
+     https://github.com/msys2/MSYS2-packages 
+
+The `patch` package is obviously built from the subdirectory 
+
+     https://github.com/Alexpux/MSYS2-packages/tree/master/patch 
+
+and the PKGBUILD file specifies that the source is fetched from 
+ftp://ftp.gnu.org/gnu/patch/patch-2.7.5.tar.xz: 
+
+https://github.com/Alexpux/MSYS2-packages/blob/900744becd072f687029b0f830ab6fe95cf533d6/patch/PKGBUILD#L14 
+
+and then these two patches are applied before building: 
+     
+https://github.com/Alexpux/MSYS2-packages/blob/900744becd072f687029b0f830ab6fe95cf533d6/patch/msys2-patch-2.7.1.patch 
+
+and 
+      
+https://github.com/Alexpux/MSYS2-packages/blob/900744becd072f687029b0f830ab6fe95cf533d6/patch/msys2-patch-manifest.patch 
+
+As you can see, some light changes are applied, i.e. `patch.exe` will 
+always write in binary mode with MSys2, and the executable will have a 
+manifest embedded that allows it to run as non-administrator. 
+
+Ciao, 
+Johannes Schindelin
+
+
+[1] https://groups.google.com/d/msg/git-for-windows/xWyVr4z6Ri0/6RKeV028EAAJ
Binary file Resources/Orthanc/Resources/ThirdParty/patch/msys-1.0.dll has changed
Binary file Resources/Orthanc/Resources/ThirdParty/patch/patch.exe has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/ThirdParty/patch/patch.exe.manifest	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+  <assemblyIdentity version="7.95.0.0"
+     processorArchitecture="X86"
+     name="patch.exe"
+     type="win32"/>
+
+  <!-- Identify the application security requirements. -->
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
+    <security>
+      <requestedPrivileges>
+        <requestedExecutionLevel
+          level="asInvoker"
+          uiAccess="false"/>
+        </requestedPrivileges>
+       </security>
+  </trustInfo>
+</assembly>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/WindowsResources.py	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,90 @@
+#!/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-2018 Osimis S.A., 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 os
+import sys
+import datetime
+
+if len(sys.argv) != 5:
+    sys.stderr.write('Usage: %s <Version> <ProductName> <Filename> <Description>\n\n' % sys.argv[0])
+    sys.stderr.write('Example: %s 0.9.1 Orthanc Orthanc.exe "Lightweight, RESTful DICOM server for medical imaging"\n' % sys.argv[0])
+    sys.exit(-1)
+
+SOURCE = os.path.join(os.path.dirname(__file__), 'WindowsResources.rc')
+
+VERSION = sys.argv[1]
+PRODUCT = sys.argv[2]
+FILENAME = sys.argv[3]
+DESCRIPTION = sys.argv[4]
+
+if VERSION == 'mainline':
+    VERSION = '999.999.999'
+    RELEASE = 'This is a mainline build, not an official release'
+else:
+    RELEASE = 'Release %s' % VERSION
+
+v = VERSION.split('.')
+if len(v) != 2 and len(v) != 3:
+    sys.stderr.write('Bad version number: %s\n' % VERSION)
+    sys.exit(-1)
+
+if len(v) == 2:
+    v.append('0')
+
+extension = os.path.splitext(FILENAME)[1]
+if extension.lower() == '.dll':
+    BLOCK = '040904E4'
+    TYPE = 'VFT_DLL'
+elif extension.lower() == '.exe':
+    #BLOCK = '040904B0'   # LANG_ENGLISH/SUBLANG_ENGLISH_US,
+    BLOCK = '040904E4'   # Lang=US English, CharSet=Windows Multilingual
+    TYPE = 'VFT_APP'
+else:
+    sys.stderr.write('Unsupported extension (.EXE or .DLL only): %s\n' % extension)
+    sys.exit(-1)
+
+
+with open(SOURCE, 'r') as source:
+    content = source.read()
+    content = content.replace('${VERSION_MAJOR}', v[0])
+    content = content.replace('${VERSION_MINOR}', v[1])
+    content = content.replace('${VERSION_PATCH}', v[2])
+    content = content.replace('${RELEASE}', RELEASE)
+    content = content.replace('${DESCRIPTION}', DESCRIPTION)
+    content = content.replace('${PRODUCT}', PRODUCT)   
+    content = content.replace('${FILENAME}', FILENAME)   
+    content = content.replace('${YEAR}', str(datetime.datetime.now().year))
+    content = content.replace('${BLOCK}', BLOCK)
+    content = content.replace('${TYPE}', TYPE)
+
+    sys.stdout.write(content)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/WindowsResources.rc	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,30 @@
+#include <winver.h>
+
+VS_VERSION_INFO VERSIONINFO
+   FILEVERSION ${VERSION_MAJOR},${VERSION_MINOR},0,${VERSION_PATCH}
+   PRODUCTVERSION ${VERSION_MAJOR},${VERSION_MINOR},0,0
+   FILEOS VOS_NT_WINDOWS32
+   FILETYPE ${TYPE}
+   BEGIN
+      BLOCK "StringFileInfo"
+      BEGIN
+         BLOCK "${BLOCK}"
+         BEGIN
+            VALUE "Comments", "${RELEASE}"
+            VALUE "CompanyName", "University Hospital of Liege, Belgium"
+            VALUE "FileDescription", "${DESCRIPTION}"
+            VALUE "FileVersion", "${VERSION_MAJOR}.${VERSION_MINOR}.0.${VERSION_PATCH}"
+            VALUE "InternalName", "${PRODUCT}"
+            VALUE "LegalCopyright", "(c) 2012-${YEAR}, Sebastien Jodogne, University Hospital of Liege, Belgium"
+            VALUE "LegalTrademarks", "Licensing information is available at http://www.orthanc-server.com/"
+            VALUE "OriginalFilename", "${FILENAME}"
+            VALUE "ProductName", "${PRODUCT}"
+            VALUE "ProductVersion", "${VERSION_MAJOR}.${VERSION_MINOR}"
+         END
+      END
+
+      BLOCK "VarFileInfo"
+      BEGIN
+        VALUE "Translation", 0x409, 1252  // U.S. English
+      END
+   END
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/SyncOrthancFolder.py	Fri Mar 23 11:04:03 2018 +0100
@@ -0,0 +1,216 @@
+#!/usr/bin/python
+
+#
+# This maintenance script updates the content of the "Orthanc" folder
+# to match the latest version of the Orthanc source code.
+#
+
+import multiprocessing
+import os
+import stat
+import sys
+import urllib2
+
+TARGET = os.path.join(os.path.dirname(__file__), 'Orthanc')
+BRANCH = 'default'
+REPOSITORY = 'https://bitbucket.org/sjodogne/orthanc/raw'
+
+FILES = [
+    'Core/Cache/ICachePageProvider.h',
+    'Core/Cache/LeastRecentlyUsedIndex.h',
+    'Core/Cache/MemoryCache.cpp',
+    'Core/Cache/MemoryCache.h',
+    'Core/Cache/SharedArchive.cpp',
+    'Core/Cache/SharedArchive.h',
+    'Core/ChunkedBuffer.cpp',
+    'Core/ChunkedBuffer.h',
+    'Core/Compression/DeflateBaseCompressor.cpp',
+    'Core/Compression/DeflateBaseCompressor.h',
+    'Core/Compression/GzipCompressor.cpp',
+    'Core/Compression/GzipCompressor.h',
+    'Core/Compression/HierarchicalZipWriter.cpp',
+    'Core/Compression/HierarchicalZipWriter.h',
+    'Core/Compression/IBufferCompressor.h',
+    'Core/Compression/ZipWriter.cpp',
+    'Core/Compression/ZipWriter.h',
+    'Core/Compression/ZlibCompressor.cpp',
+    'Core/Compression/ZlibCompressor.h',
+    'Core/DicomFormat/DicomArray.cpp',
+    'Core/DicomFormat/DicomArray.h',
+    'Core/DicomFormat/DicomElement.h',
+    'Core/DicomFormat/DicomImageInformation.cpp',
+    'Core/DicomFormat/DicomImageInformation.h',
+    'Core/DicomFormat/DicomInstanceHasher.cpp',
+    'Core/DicomFormat/DicomInstanceHasher.h',
+    'Core/DicomFormat/DicomIntegerPixelAccessor.cpp',
+    'Core/DicomFormat/DicomIntegerPixelAccessor.h',
+    'Core/DicomFormat/DicomMap.cpp',
+    'Core/DicomFormat/DicomMap.h',
+    'Core/DicomFormat/DicomTag.cpp',
+    'Core/DicomFormat/DicomTag.h',
+    'Core/DicomFormat/DicomValue.cpp',
+    'Core/DicomFormat/DicomValue.h',
+    'Core/Endianness.h',
+    'Core/Enumerations.cpp',
+    'Core/Enumerations.h',
+    'Core/FileStorage/FileInfo.h',
+    'Core/FileStorage/FilesystemStorage.cpp',
+    'Core/FileStorage/FilesystemStorage.h',
+    'Core/FileStorage/IStorageArea.h',
+    'Core/FileStorage/StorageAccessor.cpp',
+    'Core/FileStorage/StorageAccessor.h',
+    'Core/HttpClient.cpp',
+    'Core/HttpClient.h',
+    'Core/ICommand.h',
+    'Core/IDynamicObject.h',
+    'Core/Images/Font.cpp',
+    'Core/Images/Font.h',
+    'Core/Images/FontRegistry.cpp',
+    'Core/Images/FontRegistry.h',
+    'Core/Images/IImageWriter.cpp',
+    'Core/Images/IImageWriter.h',
+    'Core/Images/Image.cpp',
+    'Core/Images/Image.h',
+    'Core/Images/ImageAccessor.cpp',
+    'Core/Images/ImageAccessor.h',
+    'Core/Images/ImageBuffer.cpp',
+    'Core/Images/ImageBuffer.h',
+    'Core/Images/ImageProcessing.cpp',
+    'Core/Images/ImageProcessing.h',
+    'Core/Images/ImageTraits.h',
+    'Core/Images/JpegErrorManager.cpp',
+    'Core/Images/JpegErrorManager.h',
+    'Core/Images/JpegReader.cpp',
+    'Core/Images/JpegReader.h',
+    'Core/Images/JpegWriter.cpp',
+    'Core/Images/JpegWriter.h',
+    'Core/Images/PixelTraits.h',
+    'Core/Images/PngReader.cpp',
+    'Core/Images/PngReader.h',
+    'Core/Images/PngWriter.cpp',
+    'Core/Images/PngWriter.h',
+    'Core/Logging.cpp',
+    'Core/Logging.h',
+    'Core/MultiThreading/BagOfTasks.h',
+    'Core/MultiThreading/BagOfTasksProcessor.cpp',
+    'Core/MultiThreading/BagOfTasksProcessor.h',
+    'Core/MultiThreading/ILockable.h',
+    'Core/MultiThreading/IRunnableBySteps.h',
+    'Core/MultiThreading/Mutex.cpp',
+    'Core/MultiThreading/Mutex.h',
+    'Core/MultiThreading/ReaderWriterLock.cpp',
+    'Core/MultiThreading/ReaderWriterLock.h',
+    'Core/MultiThreading/RunnableWorkersPool.cpp',
+    'Core/MultiThreading/RunnableWorkersPool.h',
+    'Core/MultiThreading/Semaphore.cpp',
+    'Core/MultiThreading/Semaphore.h',
+    'Core/MultiThreading/SharedMessageQueue.cpp',
+    'Core/MultiThreading/SharedMessageQueue.h',
+    'Core/OrthancException.h',
+    'Core/PrecompiledHeaders.cpp',
+    'Core/PrecompiledHeaders.h',
+    'Core/SharedLibrary.cpp',
+    'Core/SharedLibrary.h',
+    'Core/SystemToolbox.cpp',
+    'Core/SystemToolbox.h',
+    'Core/TemporaryFile.cpp',
+    'Core/TemporaryFile.h',
+    'Core/Toolbox.cpp',
+    'Core/Toolbox.h',
+    'Core/WebServiceParameters.cpp',
+    'Core/WebServiceParameters.h',
+    'NEWS',
+    'Plugins/Samples/Common/DicomDatasetReader.cpp',
+    'Plugins/Samples/Common/DicomDatasetReader.h',
+    'Plugins/Samples/Common/DicomPath.cpp',
+    'Plugins/Samples/Common/DicomPath.h',
+    'Plugins/Samples/Common/DicomTag.h',
+    'Plugins/Samples/Common/FullOrthancDataset.cpp',
+    'Plugins/Samples/Common/FullOrthancDataset.h',
+    'Plugins/Samples/Common/IDicomDataset.h',
+    'Plugins/Samples/Common/IOrthancConnection.cpp',
+    'Plugins/Samples/Common/IOrthancConnection.h',
+    'Plugins/Samples/Common/OrthancHttpConnection.cpp',
+    'Plugins/Samples/Common/OrthancHttpConnection.h',
+    'Plugins/Samples/Common/OrthancPluginException.h',
+    'Resources/CMake/AutoGeneratedCode.cmake',
+    'Resources/CMake/BoostConfiguration.cmake',
+    'Resources/CMake/Compiler.cmake',
+    'Resources/CMake/DownloadPackage.cmake',
+    'Resources/CMake/GoogleTestConfiguration.cmake',
+    'Resources/CMake/JsonCppConfiguration.cmake',
+    'Resources/CMake/LibCurlConfiguration.cmake',
+    'Resources/CMake/LibJpegConfiguration.cmake',
+    'Resources/CMake/LibPngConfiguration.cmake',
+    'Resources/CMake/OpenSslConfiguration.cmake',
+    'Resources/CMake/OrthancFrameworkConfiguration.cmake',
+    'Resources/CMake/OrthancFrameworkParameters.cmake',
+    'Resources/CMake/UuidConfiguration.cmake',
+    'Resources/CMake/ZlibConfiguration.cmake',
+    'Resources/EmbedResources.py',
+    'Resources/LinuxStandardBaseToolchain.cmake',
+    'Resources/MinGW-W64-Toolchain32.cmake',
+    'Resources/MinGW-W64-Toolchain64.cmake',
+    'Resources/MinGWToolchain.cmake',
+    'Resources/Patches/boost-1.65.1-linux-standard-base.patch',
+    'Resources/ThirdParty/VisualStudio/stdint.h',
+    'Resources/ThirdParty/base64/base64.cpp',
+    'Resources/ThirdParty/base64/base64.h',
+    'Resources/ThirdParty/md5/md5.c',
+    'Resources/ThirdParty/md5/md5.h',
+    'Resources/ThirdParty/minizip/NOTES',
+    'Resources/ThirdParty/minizip/crypt.h',
+    'Resources/ThirdParty/minizip/ioapi.c',
+    'Resources/ThirdParty/minizip/ioapi.h',
+    'Resources/ThirdParty/minizip/zip.c',
+    'Resources/ThirdParty/minizip/zip.h',
+    'Resources/ThirdParty/patch/NOTES.txt',
+    'Resources/ThirdParty/patch/msys-1.0.dll',
+    'Resources/ThirdParty/patch/patch.exe',
+    'Resources/ThirdParty/patch/patch.exe.manifest',
+    'Resources/WindowsResources.py',
+    'Resources/WindowsResources.rc',
+]
+
+EXE = [
+    'Resources/EmbedResources.py',
+    'Resources/WindowsResources.py',
+]
+
+
+def Download(x):
+    branch = x[0]
+    source = x[1]
+    target = os.path.join(TARGET, x[2])
+    print target
+
+    try:
+        os.makedirs(os.path.dirname(target))
+    except:
+        pass
+
+    url = '%s/%s/%s' % (REPOSITORY, branch, source)
+
+    try:
+        with open(target, 'w') as f:
+            f.write(urllib2.urlopen(url).read())
+    except:
+        sys.stderr.write('Cannot download: %s\n' % url)
+        raise
+
+
+commands = []
+
+for f in FILES:
+    commands.append([ BRANCH, f, f ])
+
+pool = multiprocessing.Pool(10)  # simultaneous downloads
+
+# https://stackoverflow.com/a/1408476/881731
+pool.map_async(Download, commands).get(timeout = 20)
+
+
+for exe in EXE:
+    path = os.path.join(TARGET, exe)
+    st = os.stat(path)
+    os.chmod(path, st.st_mode | stat.S_IEXEC)