[med-svn] [orthanc-dicomweb] 01/06: New upstream version 0.4+dfsg

Sebastien Jodogne jodogne-guest at moszumanska.debian.org
Thu Jul 20 12:08:36 UTC 2017


This is an automated email from the git hooks/post-receive script.

jodogne-guest pushed a commit to branch master
in repository orthanc-dicomweb.

commit 0e0f2ec615381c0ee278768731e4ac38c9e2b369
Author: jodogne-guest <s.jodogne at gmail.com>
Date:   Thu Jul 20 13:48:58 2017 +0200

    New upstream version 0.4+dfsg
---
 .hg_archival.txt                                   |   7 +-
 AUTHORS                                            |  11 +-
 CMakeLists.txt                                     |   6 +-
 NEWS                                               |  20 +-
 Orthanc/Core/ChunkedBuffer.cpp                     |   1 +
 Orthanc/Core/ChunkedBuffer.h                       |   1 +
 Orthanc/Core/Enumerations.cpp                      | 113 +++-
 Orthanc/Core/Enumerations.h                        |  11 +-
 Orthanc/Core/Logging.h                             |  69 ++-
 Orthanc/Core/OrthancException.h                    |   3 +-
 Orthanc/Core/PrecompiledHeaders.h                  |   4 +-
 Orthanc/Core/SystemToolbox.cpp                     | 552 +++++++++++++++++
 .../Core/{OrthancException.h => SystemToolbox.h}   | 101 ++--
 Orthanc/Core/Toolbox.cpp                           | 650 ++++++---------------
 Orthanc/Core/Toolbox.h                             | 106 ++--
 Orthanc/Core/WebServiceParameters.cpp              |  14 +-
 Orthanc/Core/WebServiceParameters.h                |   7 +
 .../Samples/Common/OrthancPluginCppWrapper.cpp     | 595 +++++++++++++++----
 .../Samples/Common/OrthancPluginCppWrapper.h       | 280 ++++++---
 .../Samples/Common/OrthancPluginException.h        | 101 ++++
 Orthanc/Resources/CMake/BoostConfiguration.cmake   |  60 +-
 Orthanc/Resources/CMake/Compiler.cmake             |  29 +-
 Orthanc/Resources/CMake/DownloadPackage.cmake      |  22 +-
 .../Resources/CMake/GoogleTestConfiguration.cmake  |  29 +-
 Orthanc/Resources/CMake/JsonCppConfiguration.cmake |   2 +-
 Orthanc/Resources/CMake/PugixmlConfiguration.cmake |   8 +-
 Orthanc/Resources/CMake/ZlibConfiguration.cmake    |   6 +-
 Orthanc/Resources/ThirdParty/VisualStudio/stdint.h | 506 ++++++++--------
 Orthanc/Resources/WindowsResources.py              |   1 +
 Plugin/Configuration.cpp                           | 223 ++++---
 Plugin/Configuration.h                             |   1 +
 Plugin/Dicom.cpp                                   |  11 +-
 Plugin/Dicom.h                                     |   2 +
 Plugin/DicomResults.cpp                            |  17 +-
 Plugin/DicomResults.h                              |   1 +
 Plugin/DicomWebClient.cpp                          | 119 ++--
 Plugin/DicomWebClient.h                            |   1 +
 Plugin/DicomWebServers.cpp                         |  13 +-
 Plugin/DicomWebServers.h                           |   1 +
 Plugin/Plugin.cpp                                  |  18 +-
 Plugin/Plugin.h                                    |   1 +
 Plugin/QidoRs.cpp                                  |  37 +-
 Plugin/QidoRs.h                                    |   1 +
 Plugin/StowRs.cpp                                  |   7 +
 Plugin/StowRs.h                                    |   1 +
 Plugin/WadoRs.cpp                                  |  21 +-
 Plugin/WadoRs.h                                    |   1 +
 Plugin/WadoRsRetrieveFrames.cpp                    | 133 +++--
 Plugin/WadoUri.cpp                                 |  15 +-
 Plugin/WadoUri.h                                   |   1 +
 README                                             |   5 +-
 Resources/BuildInstructions.txt                    |  11 +-
 Resources/CMake/GdcmConfiguration.cmake            |   4 +-
 Resources/Samples/JavaScript/qido-rs.js            |   1 +
 Resources/Samples/JavaScript/stow-rs.js            |   1 +
 Resources/Samples/Python/SendStow.py               |   8 +-
 Resources/Samples/Python/WadoRetrieveStudy.py      |   1 +
 Resources/SyncOrthancFolder.py                     |   3 +
 Status.txt                                         |  16 +
 UnitTestsSources/UnitTestsMain.cpp                 |  10 +
 Usage.txt                                          | 284 ---------
 61 files changed, 2724 insertions(+), 1560 deletions(-)

diff --git a/.hg_archival.txt b/.hg_archival.txt
index 447ae03..7e15e2f 100644
--- a/.hg_archival.txt
+++ b/.hg_archival.txt
@@ -1,5 +1,6 @@
 repo: d5f45924411123cfd02d035fd50b8e37536eadef
-node: b6f0743a917faa06e53e0b34d663a74b2ef9d4ab
-branch: OrthancDicomWeb-0.3
+node: f58f8d749b95d85a792e786a0256d0cc2278cc4d
+branch: OrthancDicomWeb-0.4
 latesttag: null
-latesttagdistance: 141
+latesttagdistance: 179
+changessincelatesttag: 189
diff --git a/AUTHORS b/AUTHORS
index 2d804cf..bdc3e42 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -6,8 +6,15 @@ Authors
 -------
 
 * Sebastien Jodogne <s.jodogne at gmail.com>
-  Department of Medical Physics
+
+  Overall design and lead developer.
+
+* Department of Medical Physics
   University Hospital of Liege
+  4000 Liege
   Belgium
 
-  Overall design and lead developer.
+* Osimis <info at osimis.io>
+  Rue des Chasseurs Ardennais 3
+  4031 Liege 
+  Belgium
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d41d2a6..dd4c806 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017 Osimis, Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU Affero General Public License
@@ -20,7 +21,7 @@ cmake_minimum_required(VERSION 2.8)
 
 project(OrthancDicomWeb)
 
-set(ORTHANC_DICOM_WEB_VERSION "0.3")
+set(ORTHANC_DICOM_WEB_VERSION "0.4")
 
 
 # Parameters of the build
@@ -42,6 +43,7 @@ mark_as_advanced(USE_GTEST_DEBIAN_SOURCE_PACKAGE)
 
 set(USE_PUGIXML ON)
 set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}/Orthanc)
+set(ORTHANC_DISABLE_PATCH ON)  # No need for the "patch" command-line tool
 include(CheckIncludeFiles)
 include(CheckIncludeFileCXX)
 include(CheckLibraryExists)
@@ -102,6 +104,7 @@ add_definitions(
   -DORTHANC_ENABLE_MD5=0
   -DORTHANC_ENABLE_BASE64=0
   -DORTHANC_ENABLE_LOGGING=0
+  -DORTHANC_SANDBOXED=0
   -DHAS_ORTHANC_EXCEPTION=1
   )
 
@@ -116,6 +119,7 @@ set(CORE_SOURCES
   ${ORTHANC_ROOT}/Core/ChunkedBuffer.cpp
   ${ORTHANC_ROOT}/Core/Enumerations.cpp
   ${ORTHANC_ROOT}/Core/Toolbox.cpp
+  ${ORTHANC_ROOT}/Core/SystemToolbox.cpp
   ${ORTHANC_ROOT}/Core/WebServiceParameters.cpp
   ${ORTHANC_ROOT}/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
 
diff --git a/NEWS b/NEWS
index ccf9faf..295a72a 100644
--- a/NEWS
+++ b/NEWS
@@ -2,7 +2,19 @@ Pending changes in the mainline
 ===============================
 
 
-Version 0.3 (2016/06/28)
+Version 0.4 (2017-07-19)
+========================
+
+* Improved robustness in the STOW-RS server (occurrences of "\r\n\r\n" in DICOM are supported)
+* Performance warning if runtime debug assertions are turned on
+* WADO-RS client supports quoted Content-Type header in HTTP answers
+* Added "Arguments" to WADO-RS and STOW-RS client to handle query arguments in uri
+* Using MIME types of DICOM version 2017c in WADO RetrieveFrames
+* Fix issue #53 (DICOMWeb plugin support for "limit" and "offset" parameters in QIDO-RS)
+* Fix issue #28 (Non-compliant enumerations for "accept" header for WADO RetrieveFrames)
+
+
+Version 0.3 (2016-06-28)
 ========================
 
 => Minimum SDK version: 1.1.0 <=
@@ -15,7 +27,7 @@ Version 0.3 (2016/06/28)
 * Fix issue #14 (Aggregate fields empty for QIDO-RS study/series-level queries)
 
 
-Version 0.2 (2015/12/10)
+Version 0.2 (2015-12-10)
 ========================
 
 => Minimum SDK version: 0.9.5 <=
@@ -25,7 +37,7 @@ Version 0.2 (2015/12/10)
 * Upgrade to GDCM 2.6.0 for static and Windows builds
 
 
-Version 0.1 (2015/08/03)
+Version 0.1 (2015-08-03)
 ========================
 
 => Minimum SDK version: 0.9.1 <=
@@ -45,7 +57,7 @@ Production
 * Upgrade to Boost 1.58.0 for static and Windows builds
 
 
-2015/03/13
+2015-03-13
 ==========
 
 * Initial commit
diff --git a/Orthanc/Core/ChunkedBuffer.cpp b/Orthanc/Core/ChunkedBuffer.cpp
index 5d2c2c8..22a3c0e 100644
--- a/Orthanc/Core/ChunkedBuffer.cpp
+++ b/Orthanc/Core/ChunkedBuffer.cpp
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
diff --git a/Orthanc/Core/ChunkedBuffer.h b/Orthanc/Core/ChunkedBuffer.h
index 552c1ec..05c724e 100644
--- a/Orthanc/Core/ChunkedBuffer.h
+++ b/Orthanc/Core/ChunkedBuffer.h
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
diff --git a/Orthanc/Core/Enumerations.cpp b/Orthanc/Core/Enumerations.cpp
index e600828..8309e1a 100644
--- a/Orthanc/Core/Enumerations.cpp
+++ b/Orthanc/Core/Enumerations.cpp
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -64,7 +65,7 @@ namespace Orthanc
         return "Parameter out of range";
 
       case ErrorCode_NotEnoughMemory:
-        return "Not enough memory";
+        return "The server hosting Orthanc is running out of memory";
 
       case ErrorCode_BadParameterType:
         return "Bad type for a parameter";
@@ -156,6 +157,9 @@ namespace Orthanc
       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_SQLiteNotOpened:
         return "SQLite: The database is not opened";
 
@@ -632,10 +636,10 @@ namespace Orthanc
         return "RGB";
 
       case PhotometricInterpretation_Monochrome1:
-        return "Monochrome1";
+        return "MONOCHROME1";
 
       case PhotometricInterpretation_Monochrome2:
-        return "Monochrome2";
+        return "MONOCHROME2";
 
       case PhotometricInterpretation_ARGB:
         return "ARGB";
@@ -647,25 +651,25 @@ namespace Orthanc
         return "HSV";
 
       case PhotometricInterpretation_Palette:
-        return "Palette color";
+        return "PALETTE COLOR";
 
       case PhotometricInterpretation_YBRFull:
-        return "YBR full";
+        return "YBR_FULL";
 
       case PhotometricInterpretation_YBRFull422:
-        return "YBR full 422";
+        return "YBR_FULL_422";
 
       case PhotometricInterpretation_YBRPartial420:
-        return "YBR partial 420"; 
+        return "YBR_PARTIAL_420"; 
 
       case PhotometricInterpretation_YBRPartial422:
-        return "YBR partial 422"; 
+        return "YBR_PARTIAL_422"; 
 
       case PhotometricInterpretation_YBR_ICT:
-        return "YBR ICT"; 
+        return "YBR_ICT"; 
 
       case PhotometricInterpretation_YBR_RCT:
-        return "YBR RCT"; 
+        return "YBR_RCT"; 
 
       case PhotometricInterpretation_Unknown:
         return "Unknown";
@@ -733,6 +737,9 @@ namespace Orthanc
       case PixelFormat_RGBA32:
         return "RGBA32";
 
+      case PixelFormat_BGRA32:
+        return "BGRA32";
+
       case PixelFormat_Grayscale8:
         return "Grayscale (unsigned 8bpp)";
 
@@ -1046,6 +1053,80 @@ namespace Orthanc
   }
 
 
+  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);
+  }
+  
+
   unsigned int GetBytesPerPixel(PixelFormat format)
   {
     switch (format)
@@ -1061,6 +1142,7 @@ namespace Orthanc
         return 3;
 
       case PixelFormat_RGBA32:
+      case PixelFormat_BGRA32:
         return 4;
 
       case PixelFormat_Float32:
@@ -1076,15 +1158,18 @@ namespace Orthanc
   bool GetDicomEncoding(Encoding& encoding,
                         const char* specificCharacterSet)
   {
-    std::string s = 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_IR 192" ||
         s == "ISO 2022 IR 6")
     {
+      encoding = Encoding_Ascii;
+    }
+    else if (s == "ISO_IR 192")
+    {
       encoding = Encoding_Utf8;
     }
     else if (s == "ISO_IR 100" ||
@@ -1231,8 +1316,10 @@ namespace Orthanc
     // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2
     switch (encoding)
     {
-      case Encoding_Utf8:
       case Encoding_Ascii:
+        return "ISO_IR 6";
+
+      case Encoding_Utf8:
         return "ISO_IR 192";
 
       case Encoding_Latin1:
diff --git a/Orthanc/Core/Enumerations.h b/Orthanc/Core/Enumerations.h
index 7eef119..aae6627 100644
--- a/Orthanc/Core/Enumerations.h
+++ b/Orthanc/Core/Enumerations.h
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -52,7 +53,7 @@ namespace Orthanc
     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    /*!< Not enough memory */,
+    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 */,
@@ -83,6 +84,7 @@ namespace Orthanc
     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_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 */,
@@ -195,7 +197,10 @@ namespace Orthanc
      * {summary}{Graylevel, floating-point image.}
      * {description}{The image is graylevel. Each pixel is floating-point and stored in 4 bytes.}
      **/
-    PixelFormat_Float32 = 6
+    PixelFormat_Float32 = 6,
+
+    // This is the memory layout for Cairo
+    PixelFormat_BGRA32 = 7
   };
 
 
@@ -519,6 +524,8 @@ namespace Orthanc
   ValueRepresentation StringToValueRepresentation(const std::string& vr,
                                                   bool throwIfUnsupported);
 
+  PhotometricInterpretation StringToPhotometricInterpretation(const char* value);
+  
   unsigned int GetBytesPerPixel(PixelFormat format);
 
   bool GetDicomEncoding(Encoding& encoding,
diff --git a/Orthanc/Core/Logging.h b/Orthanc/Core/Logging.h
index 7318246..8c419e2 100644
--- a/Orthanc/Core/Logging.h
+++ b/Orthanc/Core/Logging.h
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -34,11 +35,31 @@
 
 #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 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();
 
@@ -66,6 +87,12 @@ namespace Orthanc
       {
         return *this;
       }
+
+      // This overload fixes build problems with Visual Studio 2015
+      std::ostream& operator<< (const char* message)
+      {
+        return *this;
+      }
     };
   }
 }
@@ -76,7 +103,41 @@ namespace Orthanc
 #  define LOG(level)   ::Orthanc::Logging::NullStream()
 #  define VLOG(level)  ::Orthanc::Logging::NullStream()
 
-#else  /* ORTHANC_ENABLE_LOGGING == 1 */
+
+#elif ORTHANC_ENABLE_LOGGING_PLUGIN == 1
+
+#  include <boost/noncopyable.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 : public boost::noncopyable
+    {
+    private:
+      std::string level_;
+      std::string message_;
+
+    public:
+      InternalLogger(const char* level,
+                     const char* file,
+                     int line);
+
+      ~InternalLogger();
+      
+      InternalLogger& operator<< (const std::string& message);
+
+      InternalLogger& operator<< (const char* message);
+
+      InternalLogger& operator<< (int message);
+    };
+  }
+}
+
+
+#else  /* ORTHANC_ENABLE_LOGGING_PLUGIN == 0 && ORTHANC_ENABLE_LOGGING == 1 */
 
 #  include <boost/thread/mutex.hpp>
 #  define LOG(level)  ::Orthanc::Logging::InternalLogger(#level,  __FILE__, __LINE__)
@@ -104,6 +165,12 @@ namespace Orthanc
       {
         return (*stream_) << message;
       }
+
+      // This overload fixes build problems with Visual Studio 2015
+      std::ostream& operator<< (const char* message)
+      {
+        return (*stream_) << message;
+      }
     };
   }
 }
diff --git a/Orthanc/Core/OrthancException.h b/Orthanc/Core/OrthancException.h
index 5afa41f..ee9b603 100644
--- a/Orthanc/Core/OrthancException.h
+++ b/Orthanc/Core/OrthancException.h
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -45,7 +46,7 @@ namespace Orthanc
     HttpStatus httpStatus_;
 
   public:
-    OrthancException(ErrorCode errorCode) : 
+    explicit OrthancException(ErrorCode errorCode) : 
       errorCode_(errorCode),
       httpStatus_(ConvertErrorCodeToHttpStatus(errorCode))
     {
diff --git a/Orthanc/Core/PrecompiledHeaders.h b/Orthanc/Core/PrecompiledHeaders.h
index e22f012..4fc6435 100644
--- a/Orthanc/Core/PrecompiledHeaders.h
+++ b/Orthanc/Core/PrecompiledHeaders.h
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -48,7 +49,7 @@
 
 #include <json/value.h>
 
-#if ORTHANC_PUGIXML_ENABLED == 1
+#if ORTHANC_ENABLE_PUGIXML == 1
 #include <pugixml.hpp>
 #endif
 
@@ -56,6 +57,5 @@
 #include "Logging.h"
 #include "OrthancException.h"
 #include "Toolbox.h"
-#include "Uuid.h"
 
 #endif
diff --git a/Orthanc/Core/SystemToolbox.cpp b/Orthanc/Core/SystemToolbox.cpp
new file mode 100644
index 0000000..3e4e36d
--- /dev/null
+++ b/Orthanc/Core/SystemToolbox.cpp
@@ -0,0 +1,552 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, 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 BOOST_HAS_DATE_TIME == 1
+#  include <boost/date_time/posix_time/posix_time.hpp>
+#endif
+
+
+#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
+
+
+// Inclusions for UUID
+// http://stackoverflow.com/a/1626302
+
+extern "C"
+{
+#ifdef 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>
+
+
+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(__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(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 = 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()
+  {
+    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);
+  }
+
+#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;
+  }
+
+
+#if BOOST_HAS_DATE_TIME == 1
+  std::string SystemToolbox::GetNowIsoString()
+  {
+    boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
+    return boost::posix_time::to_iso_string(now);
+  }
+
+  void SystemToolbox::GetNowDicom(std::string& date,
+                                  std::string& time)
+  {
+    boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
+    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);
+  }
+#endif
+}
diff --git a/Orthanc/Core/OrthancException.h b/Orthanc/Core/SystemToolbox.h
similarity index 51%
copy from Orthanc/Core/OrthancException.h
copy to Orthanc/Core/SystemToolbox.h
index 5afa41f..0b3fe3f 100644
--- a/Orthanc/Core/OrthancException.h
+++ b/Orthanc/Core/SystemToolbox.h
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -32,45 +33,73 @@
 
 #pragma once
 
-#include <stdint.h>
-#include <string>
+#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
 {
-  class OrthancException
+  namespace SystemToolbox
   {
-  protected:
-    ErrorCode  errorCode_;
-    HttpStatus httpStatus_;
-
-  public:
-    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_);
-    }
-  };
+    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();
+
+#if BOOST_HAS_DATE_TIME == 1
+    std::string GetNowIsoString();
+
+    void GetNowDicom(std::string& date,
+                     std::string& time);
+#endif
+  }
 }
diff --git a/Orthanc/Core/Toolbox.cpp b/Orthanc/Core/Toolbox.cpp
index 4990204..4a67878 100644
--- a/Orthanc/Core/Toolbox.cpp
+++ b/Orthanc/Core/Toolbox.cpp
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -36,57 +37,31 @@
 #include "OrthancException.h"
 #include "Logging.h"
 
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/locale.hpp>
+#include <boost/uuid/sha1.hpp>
+
 #include <string>
 #include <stdint.h>
 #include <string.h>
-#include <boost/filesystem.hpp>
-#include <boost/filesystem/fstream.hpp>
-#include <boost/uuid/sha1.hpp>
-#include <boost/lexical_cast.hpp>
 #include <algorithm>
 #include <ctype.h>
 
-#if BOOST_HAS_DATE_TIME == 1
-#include <boost/date_time/posix_time/posix_time.hpp>
-#endif
-
 #if BOOST_HAS_REGEX == 1
-#include <boost/regex.hpp> 
-#endif
-
-#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>
+#  include <boost/regex.hpp> 
 #endif
 
 #if BOOST_HAS_LOCALE != 1
-#error Since version 0.7.6, Orthanc entirely relies on boost::locale
+#  error Since version 0.7.6, Orthanc entirely relies on boost::locale
 #endif
 
-#include <boost/locale.hpp>
-
-
-#if !defined(ORTHANC_ENABLE_MD5) || ORTHANC_ENABLE_MD5 == 1
-#include "../Resources/ThirdParty/md5/md5.h"
+#if ORTHANC_ENABLE_MD5 == 1
+#  include "../Resources/ThirdParty/md5/md5.h"
 #endif
 
-
-#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
-#include "../Resources/ThirdParty/base64/base64.h"
+#if ORTHANC_ENABLE_BASE64 == 1
+#  include "../Resources/ThirdParty/base64/base64.h"
 #endif
 
 
@@ -103,93 +78,19 @@ extern "C"
 #endif
 
 
-#if ORTHANC_PUGIXML_ENABLED == 1
-#include "ChunkedBuffer.h"
-#include <pugixml.hpp>
-#endif
-
-
-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
-
-
-  void Toolbox::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__)
-    usleep(microSeconds);
-#else
-#error Support your platform here
+#  include <windows.h>   // For ::Sleep
 #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_))
-    {
-      Toolbox::USleep(100 * 1000);
-    }
-
-#if defined(_WIN32)
-    SetConsoleCtrlHandler(ConsoleControlHandler, false);
-#else
-    signal(SIGINT, NULL);
-    signal(SIGQUIT, NULL);
-    signal(SIGTERM, NULL);
-    signal(SIGHUP, NULL);
+#if ORTHANC_ENABLE_PUGIXML == 1
+#  include "ChunkedBuffer.h"
+#  include <pugixml.hpp>
 #endif
 
-    return barrierEvent_;
-  }
-
-
-  ServerBarrierEvent Toolbox::ServerBarrier(const bool& stopFlag)
-  {
-    return ServerBarrierInternal(&stopFlag);
-  }
-
-  ServerBarrierEvent Toolbox::ServerBarrier()
-  {
-    const bool stopFlag = false;
-    return ServerBarrierInternal(&stopFlag);
-  }
-
 
+namespace Orthanc
+{
   void Toolbox::ToUpperCase(std::string& s)
   {
     std::transform(s.begin(), s.end(), s.begin(), toupper);
@@ -217,134 +118,6 @@ namespace Orthanc
   }
 
 
-  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 Toolbox::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(size);
-    if (size != 0)
-    {
-      f.read(reinterpret_cast<char*>(&content[0]), size);
-    }
-
-    f.close();
-  }
-
-
-  bool Toolbox::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 = 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 Toolbox::WriteFile(const void* content,
-                          size_t size,
-                          const std::string& path)
-  {
-    boost::filesystem::ofstream f;
-    f.open(path, std::ofstream::binary);
-    if (!f.good())
-    {
-      throw OrthancException(ErrorCode_CannotWriteFile);
-    }
-
-    if (size != 0)
-    {
-      f.write(reinterpret_cast<const char*>(content), size);
-    }
-
-    f.close();
-  }
-
-
-  void Toolbox::WriteFile(const std::string& content,
-                          const std::string& path)
-  {
-    WriteFile(content.size() > 0 ? content.c_str() : NULL,
-              content.size(), path);
-  }
-
-
-  void Toolbox::RemoveFile(const std::string& path)
-  {
-    if (boost::filesystem::exists(path))
-    {
-      if (IsRegularFile(path))
-      {
-        boost::filesystem::remove(path);
-      }
-      else
-      {
-        throw OrthancException(ErrorCode_RegularFileExpected);
-      }
-    }
-  }
-
-
-
   void Toolbox::SplitUriComponents(UriComponents& components,
                                    const std::string& uri)
   {
@@ -512,21 +285,7 @@ namespace Orthanc
   }
 
 
-
-  uint64_t Toolbox::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);
-    }
-  }
-
-
-#if !defined(ORTHANC_ENABLE_MD5) || ORTHANC_ENABLE_MD5 == 1
+#if ORTHANC_ENABLE_MD5 == 1
   static char GetHexadecimalCharacter(uint8_t value)
   {
     assert(value < 16);
@@ -583,7 +342,7 @@ namespace Orthanc
 #endif
 
 
-#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
+#if ORTHANC_ENABLE_BASE64 == 1
   void Toolbox::EncodeBase64(std::string& result, 
                              const std::string& data)
   {
@@ -642,60 +401,6 @@ namespace Orthanc
 #endif
 
 
-
-#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()
-  {
-    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);
-  }
-
-#else
-#error Support your platform here
-#endif
-
-
-  std::string Toolbox::GetPathToExecutable()
-  {
-    boost::filesystem::path p(GetPathToExecutableInternal());
-    return boost::filesystem::absolute(p).string();
-  }
-
-
-  std::string Toolbox::GetDirectoryOfExecutable()
-  {
-    boost::filesystem::path p(GetPathToExecutableInternal());
-    return boost::filesystem::absolute(p.parent_path()).string();
-  }
-
-
   static const char* GetBoostLocaleEncoding(const Encoding sourceEncoding)
   {
     switch (sourceEncoding)
@@ -820,6 +525,23 @@ namespace Orthanc
   }
 
 
+  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;
+  }
+
+
   std::string Toolbox::ConvertToAscii(const std::string& source)
   {
     std::string result;
@@ -956,30 +678,6 @@ namespace Orthanc
   }
 
 
-#if BOOST_HAS_DATE_TIME == 1
-  std::string Toolbox::GetNowIsoString()
-  {
-    boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
-    return boost::posix_time::to_iso_string(now);
-  }
-
-  void Toolbox::GetNowDicom(std::string& date,
-                            std::string& time)
-  {
-    boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
-    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);
-  }
-#endif
-
-
   std::string Toolbox::StripSpaces(const std::string& source)
   {
     size_t first = 0;
@@ -1137,32 +835,7 @@ namespace Orthanc
   }
 
 
-  void Toolbox::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 Toolbox::IsExistingFile(const std::string& path)
-  {
-    return boost::filesystem::exists(path);
-  }
-
-
-#if ORTHANC_PUGIXML_ENABLED == 1
+#if ORTHANC_ENABLE_PUGIXML == 1
   class ChunkedBufferWriter : public pugi::xml_writer
   {
   private:
@@ -1284,64 +957,6 @@ namespace Orthanc
 #endif
 
 
-  void Toolbox::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);
-    }
-  }
-
   
   bool Toolbox::IsInteger(const std::string& str)
   {
@@ -1449,64 +1064,6 @@ namespace Orthanc
       return str.compare(0, prefix.size(), prefix) == 0;
     }
   }
-
-
-  int Toolbox::GetProcessId()
-  {
-#if defined(_WIN32)
-    return static_cast<int>(_getpid());
-#else
-    return static_cast<int>(getpid());
-#endif
-  }
-
-
-  bool Toolbox::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* Toolbox::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);
-  }
-
   
 
   static bool IsUnreservedCharacter(char c)
@@ -1564,5 +1121,134 @@ namespace Orthanc
         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));
+  }
 }
diff --git a/Orthanc/Core/Toolbox.h b/Orthanc/Core/Toolbox.h
index cb67473..d79907b 100644
--- a/Orthanc/Core/Toolbox.h
+++ b/Orthanc/Core/Toolbox.h
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -39,6 +40,35 @@
 #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_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
+
+#if !defined(BOOST_HAS_REGEX)
+#  error The macro BOOST_HAS_REGEX 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;
@@ -49,10 +79,6 @@ namespace Orthanc
 
   namespace Toolbox
   {
-    ServerBarrierEvent ServerBarrier(const bool& stopFlag);
-
-    ServerBarrierEvent ServerBarrier();
-
     void ToUpperCase(std::string& s);  // Inplace version
 
     void ToLowerCase(std::string& s);  // Inplace version
@@ -63,24 +89,6 @@ namespace Orthanc
     void ToLowerCase(std::string& result,
                      const std::string& source);
 
-    void ReadFile(std::string& content,
-                  const std::string& path);
-
-    bool ReadHeader(std::string& header,
-                    const std::string& path,
-                    size_t headerSize);
-
-    void WriteFile(const std::string& content,
-                   const std::string& path);
-
-    void WriteFile(const void* content,
-                   size_t size,
-                   const std::string& path);
-
-    void USleep(uint64_t microSeconds);
-
-    void RemoveFile(const std::string& path);
-
     void SplitUriComponents(UriComponents& components,
                             const std::string& uri);
   
@@ -96,9 +104,7 @@ namespace Orthanc
     std::string FlattenUri(const UriComponents& components,
                            size_t fromLevel = 0);
 
-    uint64_t GetFileSize(const std::string& path);
-
-#if !defined(ORTHANC_ENABLE_MD5) || ORTHANC_ENABLE_MD5 == 1
+#if ORTHANC_ENABLE_MD5 == 1
     void ComputeMD5(std::string& result,
                     const std::string& data);
 
@@ -119,7 +125,7 @@ namespace Orthanc
 
     bool IsSHA1(const std::string& s);
 
-#if !defined(ORTHANC_ENABLE_BASE64) || ORTHANC_ENABLE_BASE64 == 1
+#if ORTHANC_ENABLE_BASE64 == 1
     void DecodeBase64(std::string& result, 
                       const std::string& data);
 
@@ -137,27 +143,19 @@ namespace Orthanc
                              const std::string& content);
 #endif
 
-    std::string GetPathToExecutable();
-
-    std::string GetDirectoryOfExecutable();
-
     std::string ConvertToUtf8(const std::string& source,
                               Encoding sourceEncoding);
 
     std::string ConvertFromUtf8(const std::string& source,
                                 Encoding targetEncoding);
 
+    bool IsAsciiString(const void* data,
+                       size_t size);
+
     std::string ConvertToAscii(const std::string& source);
 
     std::string StripSpaces(const std::string& source);
 
-#if BOOST_HAS_DATE_TIME == 1
-    std::string GetNowIsoString();
-
-    void GetNowDicom(std::string& date,
-                     std::string& time);
-#endif
-
     // In-place percent-decoding for URL
     void UrlDecode(std::string& s);
 
@@ -171,20 +169,13 @@ namespace Orthanc
                         const std::string& source,
                         char separator);
 
-    void MakeDirectory(const std::string& path);
-
-    bool IsExistingFile(const std::string& path);
-
-#if ORTHANC_PUGIXML_ENABLED == 1
+#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
 
-    void ExecuteSystemCommand(const std::string& command,
-                              const std::vector<std::string>& arguments);
-
     bool IsInteger(const std::string& str);
 
     void CopyJsonWithoutComments(Json::Value& target,
@@ -193,14 +184,27 @@ namespace Orthanc
     bool StartsWith(const std::string& str,
                     const std::string& prefix);
 
-    int GetProcessId();
+    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 IsRegularFile(const std::string& path);
+    bool GetJsonBooleanField(const ::Json::Value& json,
+                             const std::string& key,
+                             bool defaultValue);
 
-    FILE* OpenFile(const std::string& path,
-                   FileMode mode);
+    int GetJsonIntegerField(const ::Json::Value& json,
+                            const std::string& key,
+                            int defaultValue);
 
-    void UriEncode(std::string& target,
-                   const std::string& source);
+    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);
   }
 }
diff --git a/Orthanc/Core/WebServiceParameters.cpp b/Orthanc/Core/WebServiceParameters.cpp
index 52e0ea6..2d44a85 100644
--- a/Orthanc/Core/WebServiceParameters.cpp
+++ b/Orthanc/Core/WebServiceParameters.cpp
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -34,9 +35,12 @@
 #include "WebServiceParameters.h"
 
 #include "../Core/Logging.h"
-#include "../Core/Toolbox.h"
 #include "../Core/OrthancException.h"
 
+#if ORTHANC_SANDBOXED == 0
+#  include "../Core/SystemToolbox.h"
+#endif
+
 #include <cassert>
 
 namespace Orthanc
@@ -57,6 +61,7 @@ namespace Orthanc
   }
 
 
+#if ORTHANC_SANDBOXED == 0
   void WebServiceParameters::SetClientCertificate(const std::string& certificateFile,
                                                   const std::string& certificateKeyFile,
                                                   const std::string& certificateKeyPassword)
@@ -66,14 +71,14 @@ namespace Orthanc
       throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
 
-    if (!Toolbox::IsRegularFile(certificateFile))
+    if (!SystemToolbox::IsRegularFile(certificateFile))
     {
       LOG(ERROR) << "Cannot open certificate file: " << certificateFile;
       throw OrthancException(ErrorCode_InexistentFile);
     }
 
     if (!certificateKeyFile.empty() && 
-        !Toolbox::IsRegularFile(certificateKeyFile))
+        !SystemToolbox::IsRegularFile(certificateKeyFile))
     {
       LOG(ERROR) << "Cannot open key file: " << certificateKeyFile;
       throw OrthancException(ErrorCode_InexistentFile);
@@ -84,6 +89,7 @@ namespace Orthanc
     certificateKeyFile_ = certificateKeyFile;
     certificateKeyPassword_ = certificateKeyPassword;
   }
+#endif
 
 
   static void AddTrailingSlash(std::string& url)
@@ -171,12 +177,14 @@ namespace Orthanc
     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"))
     {
diff --git a/Orthanc/Core/WebServiceParameters.h b/Orthanc/Core/WebServiceParameters.h
index 40d6cf9..5249f85 100644
--- a/Orthanc/Core/WebServiceParameters.h
+++ b/Orthanc/Core/WebServiceParameters.h
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -32,6 +33,10 @@
 
 #pragma once
 
+#if !defined(ORTHANC_SANDBOXED)
+#  error The macro ORTHANC_SANDBOXED must be defined
+#endif
+
 #include <string>
 #include <json/json.h>
 
@@ -88,9 +93,11 @@ namespace Orthanc
 
     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
     {
diff --git a/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp b/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
index 6a7dbc0..23bd4d4 100644
--- a/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
+++ b/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -33,20 +34,44 @@
 #include "OrthancPluginCppWrapper.h"
 
 #include <json/reader.h>
+#include <json/writer.h>
 
 
 namespace OrthancPlugins
 {
-  const char* PluginException::GetErrorDescription(OrthancPluginContext* context) const
+  void MemoryBuffer::Check(OrthancPluginErrorCode code)
   {
-    const char* description = OrthancPluginGetErrorDescription(context, code_);
-    if (description)
+    if (code != OrthancPluginErrorCode_Success)
     {
-      return description;
+      // Prevent using garbage information
+      buffer_.data = NULL;
+      buffer_.size = 0;
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+
+
+  bool MemoryBuffer::CheckHttp(OrthancPluginErrorCode code)
+  {
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      // Prevent using garbage information
+      buffer_.data = NULL;
+      buffer_.size = 0;
+    }
+
+    if (code == OrthancPluginErrorCode_Success)
+    {
+      return true;
+    }
+    else if (code == OrthancPluginErrorCode_UnknownResource ||
+             code == OrthancPluginErrorCode_InexistentItem)
+    {
+      return false;
     }
     else
     {
-      return "No description available";
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
     }
   }
 
@@ -100,7 +125,7 @@ namespace OrthancPlugins
     if (buffer_.data == NULL ||
         buffer_.size == 0)
     {
-      throw PluginException(OrthancPluginErrorCode_InternalError);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
     }
 
     const char* tmp = reinterpret_cast<const char*>(buffer_.data);
@@ -109,7 +134,7 @@ namespace OrthancPlugins
     if (!reader.parse(tmp, tmp + buffer_.size, target))
     {
       OrthancPluginLogError(context_, "Cannot convert some memory buffer to JSON");
-      throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
     }
   }
 
@@ -119,29 +144,13 @@ namespace OrthancPlugins
   {
     Clear();
 
-    OrthancPluginErrorCode error;
-
     if (applyPlugins)
     {
-      error = OrthancPluginRestApiGetAfterPlugins(context_, &buffer_, uri.c_str());
+      return CheckHttp(OrthancPluginRestApiGetAfterPlugins(context_, &buffer_, uri.c_str()));
     }
     else
     {
-      error = OrthancPluginRestApiGet(context_, &buffer_, uri.c_str());
-    }
-
-    if (error == OrthancPluginErrorCode_Success)
-    {
-      return true;
-    }
-    else if (error == OrthancPluginErrorCode_UnknownResource ||
-             error == OrthancPluginErrorCode_InexistentItem)
-    {
-      return false;
-    }
-    else
-    {
-      throw PluginException(error);
+      return CheckHttp(OrthancPluginRestApiGet(context_, &buffer_, uri.c_str()));
     }
   }
 
@@ -153,29 +162,13 @@ namespace OrthancPlugins
   {
     Clear();
 
-    OrthancPluginErrorCode error;
-
     if (applyPlugins)
     {
-      error = OrthancPluginRestApiPostAfterPlugins(context_, &buffer_, uri.c_str(), body, bodySize);
+      return CheckHttp(OrthancPluginRestApiPostAfterPlugins(context_, &buffer_, uri.c_str(), body, bodySize));
     }
     else
     {
-      error = OrthancPluginRestApiPost(context_, &buffer_, uri.c_str(), body, bodySize);
-    }
-
-    if (error == OrthancPluginErrorCode_Success)
-    {
-      return true;
-    }
-    else if (error == OrthancPluginErrorCode_UnknownResource ||
-             error == OrthancPluginErrorCode_InexistentItem)
-    {
-      return false;
-    }
-    else
-    {
-      throw PluginException(error);
+      return CheckHttp(OrthancPluginRestApiPost(context_, &buffer_, uri.c_str(), body, bodySize));
     }
   }
 
@@ -187,41 +180,75 @@ namespace OrthancPlugins
   {
     Clear();
 
-    OrthancPluginErrorCode error;
-
     if (applyPlugins)
     {
-      error = OrthancPluginRestApiPutAfterPlugins(context_, &buffer_, uri.c_str(), body, bodySize);
+      return CheckHttp(OrthancPluginRestApiPutAfterPlugins(context_, &buffer_, uri.c_str(), body, bodySize));
     }
     else
     {
-      error = OrthancPluginRestApiPut(context_, &buffer_, uri.c_str(), body, bodySize);
+      return CheckHttp(OrthancPluginRestApiPut(context_, &buffer_, uri.c_str(), body, bodySize));
     }
+  }
 
-    if (error == OrthancPluginErrorCode_Success)
-    {
-      return true;
-    }
-    else if (error == OrthancPluginErrorCode_UnknownResource ||
-             error == OrthancPluginErrorCode_InexistentItem)
+
+  bool MemoryBuffer::RestApiPost(const std::string& uri,
+                                 const Json::Value& body,
+                                 bool applyPlugins)
+  {
+    Json::FastWriter writer;
+    return RestApiPost(uri, writer.write(body), applyPlugins);
+  }
+
+
+  bool MemoryBuffer::RestApiPut(const std::string& uri,
+                                const Json::Value& body,
+                                bool applyPlugins)
+  {
+    Json::FastWriter writer;
+    return RestApiPut(uri, writer.write(body), applyPlugins);
+  }
+
+
+  void MemoryBuffer::CreateDicom(const Json::Value& tags,
+                                 OrthancPluginCreateDicomFlags flags)
+  {
+    Clear();
+
+    Json::FastWriter writer;
+    std::string s = writer.write(tags);
+    
+    Check(OrthancPluginCreateDicom(context_, &buffer_, s.c_str(), NULL, flags));
+  }
+
+
+  void MemoryBuffer::ReadFile(const std::string& path)
+  {
+    Clear();
+    Check(OrthancPluginReadFile(context_, &buffer_, path.c_str()));
+  }
+
+
+  void MemoryBuffer::GetDicomQuery(const OrthancPluginWorklistQuery* query)
+  {
+    Clear();
+    Check(OrthancPluginWorklistGetDicomQuery(context_, &buffer_, query));
+  }
+
+
+  void OrthancString::Assign(char* str)
+  {
+    if (str == NULL)
     {
-      return false;
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
     }
     else
     {
-      throw PluginException(error);
+      Clear();
+      str_ = str;
     }
   }
 
 
-  OrthancString::OrthancString(OrthancPluginContext* context,
-                               char* str) :
-    context_(context),
-    str_(str)
-  {
-  }
-
-
   void OrthancString::Clear()
   {
     if (str_ != NULL)
@@ -250,14 +277,89 @@ namespace OrthancPlugins
     if (str_ == NULL)
     {
       OrthancPluginLogError(context_, "Cannot convert an empty memory buffer to JSON");
-      throw PluginException(OrthancPluginErrorCode_InternalError);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
     }
 
     Json::Reader reader;
     if (!reader.parse(str_, target))
     {
       OrthancPluginLogError(context_, "Cannot convert some memory buffer to JSON");
-      throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+  
+  void MemoryBuffer::DicomToJson(Json::Value& target,
+                                 OrthancPluginDicomToJsonFormat format,
+                                 OrthancPluginDicomToJsonFlags flags,
+                                 uint32_t maxStringLength)
+  {
+    OrthancString str(context_);
+    str.Assign(OrthancPluginDicomBufferToJson(context_, GetData(), GetSize(), format, flags, maxStringLength));
+    str.ToJson(target);
+  }
+
+
+  bool MemoryBuffer::HttpGet(const std::string& url,
+                             const std::string& username,
+                             const std::string& password)
+  {
+    Clear();
+    return CheckHttp(OrthancPluginHttpGet(context_, &buffer_, url.c_str(),
+                                          username.empty() ? NULL : username.c_str(),
+                                          password.empty() ? NULL : password.c_str()));
+  }
+
+  
+  bool MemoryBuffer::HttpPost(const std::string& url,
+                              const std::string& body,
+                              const std::string& username,
+                              const std::string& password)
+  {
+    Clear();
+    return CheckHttp(OrthancPluginHttpPost(context_, &buffer_, url.c_str(),
+                                           body.c_str(), body.size(),
+                                           username.empty() ? NULL : username.c_str(),
+                                           password.empty() ? NULL : password.c_str()));
+  }
+  
+ 
+  bool MemoryBuffer::HttpPut(const std::string& url,
+                             const std::string& body,
+                             const std::string& username,
+                             const std::string& password)
+  {
+    Clear();
+    return CheckHttp(OrthancPluginHttpPut(context_, &buffer_, url.c_str(),
+                                          body.empty() ? NULL : body.c_str(),
+                                          body.size(),
+                                          username.empty() ? NULL : username.c_str(),
+                                          password.empty() ? NULL : password.c_str()));
+  }
+  
+ 
+  bool HttpDelete(OrthancPluginContext* context_,
+                  const std::string& url,
+                  const std::string& username,
+                  const std::string& password)
+  {
+    OrthancPluginErrorCode error = OrthancPluginHttpDelete
+      (context_, url.c_str(),
+       username.empty() ? NULL : username.c_str(),
+       password.empty() ? NULL : password.c_str());
+  
+    if (error == OrthancPluginErrorCode_Success)
+    {
+      return true;
+    }
+    else if (error == OrthancPluginErrorCode_UnknownResource ||
+             error == OrthancPluginErrorCode_InexistentItem)
+    {
+      return false;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
     }
   }
   
@@ -265,12 +367,13 @@ namespace OrthancPlugins
   OrthancConfiguration::OrthancConfiguration(OrthancPluginContext* context) : 
     context_(context)
   {
-    OrthancString str(context, OrthancPluginGetConfiguration(context));
+    OrthancString str(context);
+    str.Assign(OrthancPluginGetConfiguration(context));
 
     if (str.GetContent() == NULL)
     {
       OrthancPluginLogError(context, "Cannot access the Orthanc configuration");
-      throw PluginException(OrthancPluginErrorCode_InternalError);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
     }
 
     str.ToJson(configuration_);
@@ -278,7 +381,7 @@ namespace OrthancPlugins
     if (configuration_.type() != Json::objectValue)
     {
       OrthancPluginLogError(context, "Unable to read the Orthanc configuration");
-      throw PluginException(OrthancPluginErrorCode_InternalError);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
     }
   }
 
@@ -287,7 +390,7 @@ namespace OrthancPlugins
   {
     if (context_ == NULL)
     {
-      throw PluginException(OrthancPluginErrorCode_Plugin);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
     }
     else
     {
@@ -309,6 +412,15 @@ namespace OrthancPlugins
   }
 
 
+  bool OrthancConfiguration::IsSection(const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    return (configuration_.isMember(key) &&
+            configuration_[key].type() == Json::objectValue);
+  }
+
+
   void OrthancConfiguration::GetSection(OrthancConfiguration& target,
                                         const std::string& key) const
   {
@@ -331,7 +443,7 @@ namespace OrthancPlugins
           OrthancPluginLogError(context_, s.c_str());
         }
 
-        throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
       }
 
       target.configuration_ = configuration_[key];
@@ -357,7 +469,7 @@ namespace OrthancPlugins
         OrthancPluginLogError(context_, s.c_str());
       }
 
-      throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
     }
 
     target = configuration_[key].asString();
@@ -392,7 +504,7 @@ namespace OrthancPlugins
           OrthancPluginLogError(context_, s.c_str());
         }
 
-        throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
     }
   }
 
@@ -414,7 +526,7 @@ namespace OrthancPlugins
         OrthancPluginLogError(context_, s.c_str());
       }
 
-      throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
     }
     else
     {
@@ -442,7 +554,7 @@ namespace OrthancPlugins
         OrthancPluginLogError(context_, s.c_str());
       }
 
-      throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
     }
 
     target = configuration_[key].asBool();
@@ -467,11 +579,11 @@ namespace OrthancPlugins
         return true;
         
       case Json::intValue:
-        target = configuration_[key].asInt();
+        target = static_cast<float>(configuration_[key].asInt());
         return true;
         
       case Json::uintValue:
-        target = configuration_[key].asUInt();
+        target = static_cast<float>(configuration_[key].asUInt());
         return true;
         
       default:
@@ -481,7 +593,95 @@ namespace OrthancPlugins
           OrthancPluginLogError(context_, s.c_str());
         }
 
-        throw PluginException(OrthancPluginErrorCode_BadFileFormat);
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  bool OrthancConfiguration::LookupListOfStrings(std::list<std::string>& target,
+                                                 const std::string& key,
+                                                 bool allowSingleString) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    target.clear();
+
+    if (!configuration_.isMember(key))
+    {
+      return false;
+    }
+
+    switch (configuration_[key].type())
+    {
+      case Json::arrayValue:
+      {
+        bool ok = true;
+    
+        for (Json::Value::ArrayIndex i = 0; ok && i < configuration_[key].size(); i++)
+        {
+          if (configuration_[key][i].type() == Json::stringValue)
+          {
+            target.push_back(configuration_[key][i].asString());
+          }
+          else
+          {
+            ok = false;
+          }
+        }
+
+        if (ok)
+        {
+          return true;
+        }
+
+        break;
+      }
+
+      case Json::stringValue:
+        if (allowSingleString)
+        {
+          target.push_back(configuration_[key].asString());
+          return true;
+        }
+
+        break;
+
+      default:
+        break;
+    }
+
+    if (context_ != NULL)
+    {
+      std::string s = ("The configuration option \"" + GetPath(key) +
+                       "\" is not a list of strings as expected");
+      OrthancPluginLogError(context_, s.c_str());
+    }
+
+    ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+  }
+
+
+  bool OrthancConfiguration::LookupSetOfStrings(std::set<std::string>& target,
+                                                const std::string& key,
+                                                bool allowSingleString) const
+  {
+    std::list<std::string> lst;
+
+    if (LookupListOfStrings(lst, key, allowSingleString))
+    {
+      target.clear();
+
+      for (std::list<std::string>::const_iterator
+             it = lst.begin(); it != lst.end(); ++it)
+      {
+        target.insert(*it);
+      }
+
+      return true;
+    }
+    else
+    {
+      return false;
     }
   }
 
@@ -576,7 +776,7 @@ namespace OrthancPlugins
     if (image_ == NULL)
     {
       OrthancPluginLogError(context_, "Trying to access a NULL image");
-      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
     }
   }
 
@@ -587,7 +787,7 @@ namespace OrthancPlugins
   {
     if (context == NULL)
     {
-      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
     }
   }
 
@@ -599,7 +799,7 @@ namespace OrthancPlugins
   {
     if (context == NULL)
     {
-      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
     }
   }
   
@@ -612,7 +812,7 @@ namespace OrthancPlugins
   {
     if (context == NULL)
     {
-      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
     }
     else
     {
@@ -629,7 +829,7 @@ namespace OrthancPlugins
     if (image_ == NULL)
     {
       OrthancPluginLogError(context_, "Cannot uncompress a PNG image");
-      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
     }
   }
 
@@ -642,7 +842,7 @@ namespace OrthancPlugins
     if (image_ == NULL)
     {
       OrthancPluginLogError(context_, "Cannot uncompress a JPEG image");
-      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
     }
   }
 
@@ -656,7 +856,7 @@ namespace OrthancPlugins
     if (image_ == NULL)
     {
       OrthancPluginLogError(context_, "Cannot uncompress a DICOM image");
-      throw PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
     }
   }
 
@@ -738,10 +938,87 @@ namespace OrthancPlugins
   }
 
 
-  bool RestApiGetJson(Json::Value& result,
-                      OrthancPluginContext* context,
-                      const std::string& uri,
-                      bool applyPlugins)
+
+#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1
+  FindMatcher::FindMatcher(OrthancPluginContext*              context,
+                           const OrthancPluginWorklistQuery*  worklist) :
+    context_(context),
+    matcher_(NULL),
+    worklist_(worklist)
+  {
+    if (worklist_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  void FindMatcher::SetupDicom(OrthancPluginContext*  context,
+                               const void*            query,
+                               uint32_t               size)
+  {
+    context_ = context;
+    worklist_ = NULL;
+
+    matcher_ = OrthancPluginCreateFindMatcher(context_, query, size);
+    if (matcher_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+
+
+  FindMatcher::~FindMatcher()
+  {
+    // The "worklist_" field 
+
+    if (matcher_ != NULL)
+    {
+      OrthancPluginFreeFindMatcher(context_, matcher_);
+    }
+  }
+
+
+
+  bool FindMatcher::IsMatch(const void*  dicom,
+                            uint32_t     size) const
+  {
+    int32_t result;
+
+    if (matcher_ != NULL)
+    {
+      result = OrthancPluginFindMatcherIsMatch(context_, matcher_, dicom, size);
+    }
+    else if (worklist_ != NULL)
+    {
+      result = OrthancPluginWorklistIsMatch(context_, worklist_, dicom, size);
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    if (result == 0)
+    {
+      return false;
+    }
+    else if (result == 1)
+    {
+      return true;
+    }
+    else
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+
+#endif /* HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1 */
+
+
+  bool RestApiGet(Json::Value& result,
+                  OrthancPluginContext* context,
+                  const std::string& uri,
+                  bool applyPlugins)
   {
     MemoryBuffer answer(context);
     if (!answer.RestApiGet(uri, applyPlugins))
@@ -756,12 +1033,12 @@ namespace OrthancPlugins
   }
 
 
-  bool RestApiPostJson(Json::Value& result,
-                       OrthancPluginContext* context,
-                       const std::string& uri,
-                       const char* body,
-                       size_t bodySize,
-                       bool applyPlugins)
+  bool RestApiPost(Json::Value& result,
+                   OrthancPluginContext* context,
+                   const std::string& uri,
+                   const char* body,
+                   size_t bodySize,
+                   bool applyPlugins)
   {
     MemoryBuffer answer(context);
     if (!answer.RestApiPost(uri, body, bodySize, applyPlugins))
@@ -776,12 +1053,23 @@ namespace OrthancPlugins
   }
 
 
-  bool RestApiPutJson(Json::Value& result,
-                      OrthancPluginContext* context,
-                      const std::string& uri,
-                      const char* body,
-                      size_t bodySize,
-                      bool applyPlugins)
+  bool RestApiPost(Json::Value& result,
+                   OrthancPluginContext* context,
+                   const std::string& uri,
+                   const Json::Value& body,
+                   bool applyPlugins)
+  {
+    Json::FastWriter writer;
+    return RestApiPost(result, context, uri, writer.write(body), applyPlugins);
+  }
+
+
+  bool RestApiPut(Json::Value& result,
+                  OrthancPluginContext* context,
+                  const std::string& uri,
+                  const char* body,
+                  size_t bodySize,
+                  bool applyPlugins)
   {
     MemoryBuffer answer(context);
     if (!answer.RestApiPut(uri, body, bodySize, applyPlugins))
@@ -796,6 +1084,17 @@ namespace OrthancPlugins
   }
 
 
+  bool RestApiPut(Json::Value& result,
+                   OrthancPluginContext* context,
+                   const std::string& uri,
+                   const Json::Value& body,
+                   bool applyPlugins)
+  {
+    Json::FastWriter writer;
+    return RestApiPut(result, context, uri, writer.write(body), applyPlugins);
+  }
+
+
   bool RestApiDelete(OrthancPluginContext* context,
                      const std::string& uri,
                      bool applyPlugins)
@@ -822,7 +1121,101 @@ namespace OrthancPlugins
     }
     else
     {
-      throw PluginException(error);
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(error);
+    }
+  }
+
+
+  void ReportMinimalOrthancVersion(OrthancPluginContext* context,
+                                   unsigned int major,
+                                   unsigned int minor,
+                                   unsigned int revision)
+  {
+    std::string s = ("Your version of the Orthanc core (" +
+                     std::string(context->orthancVersion) +
+                     ") is too old to run this plugin (version " +
+                     boost::lexical_cast<std::string>(major) + "." +
+                     boost::lexical_cast<std::string>(minor) + "." +
+                     boost::lexical_cast<std::string>(revision) + 
+                     " is required)");
+    
+    OrthancPluginLogError(context, s.c_str());
+  }
+
+
+  bool CheckMinimalOrthancVersion(OrthancPluginContext* context,
+                                  unsigned int major,
+                                  unsigned int minor,
+                                  unsigned int revision)
+  {
+    if (context == NULL)
+    {
+      OrthancPluginLogError(context, "Bad Orthanc context in the plugin");      
+      return false;
+    }
+
+    if (!strcmp(context->orthancVersion, "mainline"))
+    {
+      // Assume compatibility with the mainline
+      return true;
+    }
+
+    // Parse the version of the Orthanc core
+    int aa, bb, cc;
+    if ( 
+#ifdef _MSC_VER
+      sscanf_s
+#else
+      sscanf
+#endif
+      (context->orthancVersion, "%4d.%4d.%4d", &aa, &bb, &cc) != 3 ||
+      aa < 0 ||
+      bb < 0 ||
+      cc < 0)
+    {
+      throw false;
+    }
+
+    unsigned int a = static_cast<unsigned int>(aa);
+    unsigned int b = static_cast<unsigned int>(bb);
+    unsigned int c = static_cast<unsigned int>(cc);
+
+    // Check the major version number
+
+    if (a > major)
+    {
+      return true;
+    }
+
+    if (a < major)
+    {
+      return false;
+    }
+
+
+    // Check the minor version number
+    assert(a == major);
+
+    if (b > minor)
+    {
+      return true;
+    }
+
+    if (b < minor)
+    {
+      return false;
+    }
+
+    // Check the patch level version number
+    assert(a == major && b == minor);
+
+    if (c >= revision)
+    {
+      return true;
+    }
+    else
+    {
+      return false;
     }
   }
 }
diff --git a/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h b/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h
index 33b550e..4f8c1df 100644
--- a/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h
+++ b/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -32,48 +33,50 @@
 
 #pragma once
 
+#include "OrthancPluginException.h"
+
 #include <orthanc/OrthancCPlugin.h>
 #include <boost/noncopyable.hpp>
 #include <boost/lexical_cast.hpp>
 #include <json/value.h>
+#include <list>
+#include <set>
+
+
+
+#define ORTHANC_PLUGINS_VERSION_IS_ABOVE(major, minor, revision) \
+  (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER > major ||               \
+   (ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER == major &&             \
+    (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER > minor ||             \
+     (ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER == minor &&           \
+      ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER >= revision))))
+  
 
-#if HAS_ORTHANC_EXCEPTION == 1
-#  include <OrthancException.h>
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 0)
+// The "OrthancPluginFindMatcher()" primitive was introduced in Orthanc 1.2.0
+#  define HAS_ORTHANC_PLUGIN_FIND_MATCHER  1
+#else
+#  define HAS_ORTHANC_PLUGIN_FIND_MATCHER  0
 #endif
 
 
+
 namespace OrthancPlugins
 {
   typedef void (*RestCallback) (OrthancPluginRestOutput* output,
                                 const char* url,
                                 const OrthancPluginHttpRequest* request);
 
-
-  class PluginException
-  {
-  private:
-    OrthancPluginErrorCode  code_;
-
-  public:
-    PluginException(OrthancPluginErrorCode code) : code_(code)
-    {
-    }
-
-    OrthancPluginErrorCode GetErrorCode() const
-    {
-      return code_;
-    }
-
-    const char* GetErrorDescription(OrthancPluginContext* context) const;
-  };
-
-
   class MemoryBuffer : public boost::noncopyable
   {
   private:
     OrthancPluginContext*      context_;
     OrthancPluginMemoryBuffer  buffer_;
 
+    void Check(OrthancPluginErrorCode code);
+
+    bool CheckHttp(OrthancPluginErrorCode code);
+
   public:
     MemoryBuffer(OrthancPluginContext* context);
 
@@ -127,6 +130,14 @@ namespace OrthancPlugins
                     bool applyPlugins);
 
     bool RestApiPost(const std::string& uri,
+                     const Json::Value& body,
+                     bool applyPlugins);
+
+    bool RestApiPut(const std::string& uri,
+                    const Json::Value& body,
+                    bool applyPlugins);
+
+    bool RestApiPost(const std::string& uri,
                      const std::string& body,
                      bool applyPlugins)
     {
@@ -139,6 +150,32 @@ namespace OrthancPlugins
     {
       return RestApiPut(uri, body.empty() ? NULL : body.c_str(), body.size(), applyPlugins);
     }
+
+    void CreateDicom(const Json::Value& tags,
+                     OrthancPluginCreateDicomFlags flags);
+
+    void ReadFile(const std::string& path);
+
+    void GetDicomQuery(const OrthancPluginWorklistQuery* query);
+
+    void DicomToJson(Json::Value& target,
+                     OrthancPluginDicomToJsonFormat format,
+                     OrthancPluginDicomToJsonFlags flags,
+                     uint32_t maxStringLength);
+
+    bool HttpGet(const std::string& url,
+                 const std::string& username,
+                 const std::string& password);
+ 
+    bool HttpPost(const std::string& url,
+                  const std::string& body,
+                  const std::string& username,
+                  const std::string& password);
+ 
+    bool HttpPut(const std::string& url,
+                 const std::string& body,
+                 const std::string& username,
+                 const std::string& password);
   };
 
 
@@ -148,16 +185,23 @@ namespace OrthancPlugins
     OrthancPluginContext*  context_;
     char*                  str_;
 
+    void Clear();
+
   public:
-    OrthancString(OrthancPluginContext* context,
-                  char* str);
+    OrthancString(OrthancPluginContext* context) :
+      context_(context),
+      str_(NULL)
+    {
+    }
 
     ~OrthancString()
     {
       Clear();
     }
 
-    void Clear();
+    // This transfers ownership, warning: The string must have been
+    // allocated by the Orthanc core
+    void Assign(char* str);
 
     const char* GetContent() const
     {
@@ -174,7 +218,7 @@ namespace OrthancPlugins
   {
   private:
     OrthancPluginContext*  context_;
-    Json::Value            configuration_;
+    Json::Value            configuration_;  // Necessarily a Json::objectValue
     std::string            path_;
 
     std::string GetPath(const std::string& key) const;
@@ -193,6 +237,8 @@ namespace OrthancPlugins
       return configuration_;
     }
 
+    bool IsSection(const std::string& key) const;
+
     void GetSection(OrthancConfiguration& target,
                     const std::string& key) const;
 
@@ -211,6 +257,14 @@ namespace OrthancPlugins
     bool LookupFloatValue(float& target,
                           const std::string& key) const;
 
+    bool LookupListOfStrings(std::list<std::string>& target,
+                             const std::string& key,
+                             bool allowSingleString) const;
+
+    bool LookupSetOfStrings(std::set<std::string>& target,
+                            const std::string& key,
+                            bool allowSingleString) const;
+
     std::string GetStringValue(const std::string& key,
                                const std::string& defaultValue) const;
 
@@ -227,7 +281,7 @@ namespace OrthancPlugins
                         float defaultValue) const;
   };
 
-  class OrthancImage
+  class OrthancImage : public boost::noncopyable
   {
   private:
     OrthancPluginContext*  context_;
@@ -285,52 +339,144 @@ namespace OrthancPlugins
   };
 
 
-  bool RestApiGetJson(Json::Value& result,
-                      OrthancPluginContext* context,
-                      const std::string& uri,
-                      bool applyPlugins);
-
-  bool RestApiPostJson(Json::Value& result,
-                       OrthancPluginContext* context,
-                       const std::string& uri,
-                       const char* body,
-                       size_t bodySize,
-                       bool applyPlugins);
-
-  bool RestApiPutJson(Json::Value& result,
-                      OrthancPluginContext* context,
-                      const std::string& uri,
-                      const char* body,
-                      size_t bodySize,
-                      bool applyPlugins);
-
-  inline bool RestApiPostJson(Json::Value& result,
-                              OrthancPluginContext* context,
-                              const std::string& uri,
-                              const std::string& body,
-                              bool applyPlugins)
+#if HAS_ORTHANC_PLUGIN_FIND_MATCHER == 1
+  class FindMatcher : public boost::noncopyable
+  {
+  private:
+    OrthancPluginContext*              context_;
+    OrthancPluginFindMatcher*          matcher_;
+    const OrthancPluginWorklistQuery*  worklist_;
+
+    void SetupDicom(OrthancPluginContext*  context,
+                    const void*            query,
+                    uint32_t               size);
+
+  public:
+    FindMatcher(OrthancPluginContext*              context,
+                const OrthancPluginWorklistQuery*  worklist);
+
+    FindMatcher(OrthancPluginContext*  context,
+                const void*            query,
+                uint32_t               size)
+    {
+      SetupDicom(context, query, size);
+    }
+
+    FindMatcher(OrthancPluginContext*  context,
+                const MemoryBuffer&    dicom)
+    {
+      SetupDicom(context, dicom.GetData(), dicom.GetSize());
+    }
+
+    ~FindMatcher();
+
+    bool IsMatch(const void*  dicom,
+                 uint32_t     size) const;
+
+    bool IsMatch(const MemoryBuffer& dicom) const
+    {
+      return IsMatch(dicom.GetData(), dicom.GetSize());
+    }
+  };
+#endif
+
+
+  bool RestApiGet(Json::Value& result,
+                  OrthancPluginContext* context,
+                  const std::string& uri,
+                  bool applyPlugins);
+
+  bool RestApiPost(Json::Value& result,
+                   OrthancPluginContext* context,
+                   const std::string& uri,
+                   const char* body,
+                   size_t bodySize,
+                   bool applyPlugins);
+
+  bool RestApiPost(Json::Value& result,
+                   OrthancPluginContext* context,
+                   const std::string& uri,
+                   const Json::Value& body,
+                   bool applyPlugins);
+
+  inline bool RestApiPost(Json::Value& result,
+                          OrthancPluginContext* context,
+                          const std::string& uri,
+                          const std::string& body,
+                          bool applyPlugins)
+  {
+    return RestApiPost(result, context, uri, body.empty() ? NULL : body.c_str(), 
+                       body.size(), applyPlugins);
+  }
+
+  bool RestApiPut(Json::Value& result,
+                  OrthancPluginContext* context,
+                  const std::string& uri,
+                  const char* body,
+                  size_t bodySize,
+                  bool applyPlugins);
+
+  bool RestApiPut(Json::Value& result,
+                  OrthancPluginContext* context,
+                  const std::string& uri,
+                  const Json::Value& body,
+                  bool applyPlugins);
+
+  inline bool RestApiPut(Json::Value& result,
+                         OrthancPluginContext* context,
+                         const std::string& uri,
+                         const std::string& body,
+                         bool applyPlugins)
   {
-    return RestApiPostJson(result, context, uri, body.empty() ? NULL : body.c_str(), 
-                           body.size(), applyPlugins);
+    return RestApiPut(result, context, uri, body.empty() ? NULL : body.c_str(), 
+                      body.size(), applyPlugins);
   }
 
   bool RestApiDelete(OrthancPluginContext* context,
                      const std::string& uri,
                      bool applyPlugins);
 
-  inline bool RestApiPutJson(Json::Value& result,
-                             OrthancPluginContext* context,
-                             const std::string& uri,
-                             const std::string& body,
-                             bool applyPlugins)
+  bool HttpDelete(OrthancPluginContext* context,
+                  const std::string& url,
+                  const std::string& username,
+                  const std::string& password);
+
+  inline void LogError(OrthancPluginContext* context,
+                       const std::string& message)
   {
-    return RestApiPutJson(result, context, uri, body.empty() ? NULL : body.c_str(), 
-                          body.size(), applyPlugins);
+    if (context != NULL)
+    {
+      OrthancPluginLogError(context, message.c_str());
+    }
   }
 
-  bool RestApiDelete(OrthancPluginContext* context,
-                     const std::string& uri,
-                     bool applyPlugins);
+  inline void LogWarning(OrthancPluginContext* context,
+                         const std::string& message)
+  {
+    if (context != NULL)
+    {
+      OrthancPluginLogWarning(context, message.c_str());
+    }
+  }
+
+  inline void LogInfo(OrthancPluginContext* context,
+                      const std::string& message)
+  {
+    if (context != NULL)
+    {
+      OrthancPluginLogInfo(context, message.c_str());
+    }
+  }
+
+  void ReportMinimalOrthancVersion(OrthancPluginContext* context,
+                                   unsigned int major,
+                                   unsigned int minor,
+                                   unsigned int revision);
+  
+  bool CheckMinimalOrthancVersion(OrthancPluginContext* context,
+                                  unsigned int major,
+                                  unsigned int minor,
+                                  unsigned int revision);
 
 
   namespace Internals
@@ -345,16 +491,10 @@ namespace OrthancPlugins
         Callback(output, url, request);
         return OrthancPluginErrorCode_Success;
       }
-      catch (OrthancPlugins::PluginException& e)
-      {
-        return e.GetErrorCode();
-      }
-#if HAS_ORTHANC_EXCEPTION == 1
-      catch (Orthanc::OrthancException& e)
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
       {
         return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
       }
-#endif
       catch (boost::bad_lexical_cast&)
       {
         return OrthancPluginErrorCode_BadFileFormat;
diff --git a/Orthanc/Plugins/Samples/Common/OrthancPluginException.h b/Orthanc/Plugins/Samples/Common/OrthancPluginException.h
new file mode 100644
index 0000000..e549a26
--- /dev/null
+++ b/Orthanc/Plugins/Samples/Common/OrthancPluginException.h
@@ -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 Osimis, 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
+}
diff --git a/Orthanc/Resources/CMake/BoostConfiguration.cmake b/Orthanc/Resources/CMake/BoostConfiguration.cmake
index 20c50df..f7c9287 100644
--- a/Orthanc/Resources/CMake/BoostConfiguration.cmake
+++ b/Orthanc/Resources/CMake/BoostConfiguration.cmake
@@ -43,7 +43,7 @@ if (BOOST_STATIC)
   set(BOOST_NAME boost_1_60_0)
   set(BOOST_BCP_SUFFIX bcpdigest-1.0.1)
   set(BOOST_MD5 "a789f8ec2056ad1c2d5f0cb64687cc7b")
-  set(BOOST_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/${BOOST_NAME}_${BOOST_BCP_SUFFIX}.tar.gz")
+  set(BOOST_URL "http://www.orthanc-server.com/downloads/third-party/${BOOST_NAME}_${BOOST_BCP_SUFFIX}.tar.gz")
   set(BOOST_FILESYSTEM_SOURCES_DIR "${BOOST_NAME}/libs/filesystem/src") 
   set(BOOST_SOURCES_DIR ${CMAKE_BINARY_DIR}/${BOOST_NAME})
 
@@ -54,8 +54,12 @@ if (BOOST_STATIC)
   if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
       ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR
       ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
-      ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD")
+      ${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/atomic/src/lockpool.cpp
       ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/once.cpp
       ${BOOST_SOURCES_DIR}/libs/thread/src/pthread/thread.cpp
       )
@@ -65,7 +69,10 @@ if (BOOST_STATIC)
       -DBOOST_LOCALE_NO_STD_BACKEND=1
       )
 
-    if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+    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()
 
@@ -92,6 +99,13 @@ if (BOOST_STATIC)
       -DBOOST_LOCALE_NO_POSIX_BACKEND=1
       -DBOOST_LOCALE_NO_STD_BACKEND=1
       )
+
+  elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten")
+    add_definitions(
+      -DBOOST_LOCALE_NO_POSIX_BACKEND=1
+      -DBOOST_LOCALE_NO_STD_BACKEND=1
+      )
+
   else()
     message(FATAL_ERROR "Support your platform here")
   endif()
@@ -107,19 +121,43 @@ if (BOOST_STATIC)
   list(APPEND BOOST_SOURCES
     ${BOOST_REGEX_SOURCES}
     ${BOOST_SOURCES_DIR}/libs/date_time/src/gregorian/greg_month.cpp
-    ${BOOST_FILESYSTEM_SOURCES_DIR}/codecvt_error_category.cpp
-    ${BOOST_FILESYSTEM_SOURCES_DIR}/operations.cpp
-    ${BOOST_FILESYSTEM_SOURCES_DIR}/path.cpp
-    ${BOOST_FILESYSTEM_SOURCES_DIR}/path_traits.cpp
-    ${BOOST_SOURCES_DIR}/libs/locale/src/encoding/codepage.cpp
     ${BOOST_SOURCES_DIR}/libs/system/src/error_code.cpp
     )
 
+  if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten")
+    list(APPEND BOOST_SOURCES
+      ${BOOST_SOURCES_DIR}/libs/locale/src/encoding/codepage.cpp
+      )
+  endif()
+
+  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_FILESYSTEM_SOURCES_DIR}/codecvt_error_category.cpp
+      ${BOOST_FILESYSTEM_SOURCES_DIR}/operations.cpp
+      ${BOOST_FILESYSTEM_SOURCES_DIR}/path.cpp
+      ${BOOST_FILESYSTEM_SOURCES_DIR}/path_traits.cpp
+      )
+  endif()
+
   if (USE_BOOST_LOCALE_BACKENDS)
     if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
         ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR
         ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
-        ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD")
+        ${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
@@ -175,7 +213,6 @@ if (BOOST_STATIC)
     -DBOOST_SYSTEM_NO_LIB
     -DBOOST_LOCALE_NO_LIB
     -DBOOST_HAS_LOCALE=1
-    -DBOOST_HAS_FILESYSTEM_V3=1
     )
 
   if (CMAKE_COMPILER_IS_GNUCXX)
@@ -186,7 +223,8 @@ if (BOOST_STATIC)
     ${BOOST_SOURCES_DIR}
     )
 
-  source_group(ThirdParty\\Boost REGULAR_EXPRESSION ${BOOST_SOURCES_DIR}/.*)
+  source_group(ThirdParty\\boost REGULAR_EXPRESSION ${BOOST_SOURCES_DIR}/.*)
+
 else()
   add_definitions(
     -DBOOST_HAS_LOCALE=1
diff --git a/Orthanc/Resources/CMake/Compiler.cmake b/Orthanc/Resources/CMake/Compiler.cmake
index 52f4fab..fa7577d 100644
--- a/Orthanc/Resources/CMake/Compiler.cmake
+++ b/Orthanc/Resources/CMake/Compiler.cmake
@@ -41,7 +41,17 @@ elseif (MSVC)
     -D_CRT_SECURE_NO_WARNINGS=1
     -D_CRT_SECURE_NO_DEPRECATE=1
     )
-  include_directories(${ORTHANC_ROOT}/Resources/ThirdParty/VisualStudio)
+
+  if (MSVC_VERSION LESS 1600)
+    # Starting with Visual Studio >= 2010 (i.e. macro _MSC_VER >=
+    # 1600), Microsoft ships a standard-compliant <stdint.h>
+    # header. For earlier versions of Visual Studio, give access to a
+    # compatibility header.
+    # http://stackoverflow.com/a/70630/881731
+    # https://en.wikibooks.org/wiki/C_Programming/C_Reference/stdint.h#External_links
+    include_directories(${ORTHANC_ROOT}/Resources/ThirdParty/VisualStudio)
+  endif()
+
   link_libraries(netapi32)
 endif()
 
@@ -154,6 +164,23 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
 endif()
 
 
+if (DEFINED ENABLE_PROFILING AND ENABLE_PROFILING)
+  if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
+    message(WARNING "Enabling profiling on a non-debug build will not produce full information")
+  endif()
+
+  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 (STATIC_BUILD)
   add_definitions(-DORTHANC_STATIC=1)
 else()
diff --git a/Orthanc/Resources/CMake/DownloadPackage.cmake b/Orthanc/Resources/CMake/DownloadPackage.cmake
index 492a352..2a73cda 100644
--- a/Orthanc/Resources/CMake/DownloadPackage.cmake
+++ b/Orthanc/Resources/CMake/DownloadPackage.cmake
@@ -15,12 +15,18 @@ endmacro()
 ## Setup the patch command-line tool
 ##
 
-if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
-  set(PATCH_EXECUTABLE ${CMAKE_SOURCE_DIR}/Resources/ThirdParty/patch/patch.exe)
-else ()
-  find_program(PATCH_EXECUTABLE patch)
-  if (${PATCH_EXECUTABLE} MATCHES "PATCH_EXECUTABLE-NOTFOUND")
-    message(FATAL_ERROR "Please install the 'patch' standard 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()
 
@@ -70,7 +76,9 @@ macro(DownloadPackage MD5 Url TargetDirectory)
 	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}")
+      file(DOWNLOAD "${Url}" "${TMP_PATH}" 
+        SHOW_PROGRESS EXPECTED_MD5 "${MD5}"
+        TIMEOUT 60 INACTIVITY_TIMEOUT 60)
     else()
       message("Using local copy of ${Url}")
     endif()
diff --git a/Orthanc/Resources/CMake/GoogleTestConfiguration.cmake b/Orthanc/Resources/CMake/GoogleTestConfiguration.cmake
index af6e67b..b9074b4 100644
--- a/Orthanc/Resources/CMake/GoogleTestConfiguration.cmake
+++ b/Orthanc/Resources/CMake/GoogleTestConfiguration.cmake
@@ -1,15 +1,32 @@
 if (USE_GTEST_DEBIAN_SOURCE_PACKAGE)
-  set(GTEST_SOURCES /usr/src/gtest/src/gtest-all.cc)
-  include_directories(/usr/src/gtest)
+  find_path(GTEST_DEBIAN_SOURCES_DIR
+    NAMES src/gtest-all.cc
+    PATHS
+    /usr/src/gtest
+    /usr/src/googletest/googletest
+    PATH_SUFFIXES src
+    )
+
+  find_path(GTEST_DEBIAN_INCLUDE_DIR
+    NAMES gtest.h
+    PATHS
+    /usr/include/gtest
+    )
 
-  if (NOT EXISTS /usr/include/gtest/gtest.h OR
-      NOT EXISTS ${GTEST_SOURCES})
+  message("Path to the Debian Google Test sources: ${GTEST_DEBIAN_SOURCES_DIR}")
+  message("Path to the Debian Google Test includes: ${GTEST_DEBIAN_INCLUDE_DIR}")
+
+  set(GTEST_SOURCES ${GTEST_DEBIAN_SOURCES_DIR}/src/gtest-all.cc)
+  include_directories(${GTEST_DEBIAN_SOURCES_DIR})
+
+  if (NOT EXISTS ${GTEST_SOURCES} OR
+      NOT EXISTS ${GTEST_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(GTEST_SOURCES_DIR ${CMAKE_BINARY_DIR}/gtest-1.7.0)
-  set(GTEST_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/gtest-1.7.0.zip")
+  set(GTEST_URL "http://www.orthanc-server.com/downloads/third-party/gtest-1.7.0.zip")
   set(GTEST_MD5 "2d6ec8ccdf5c46b05ba54a9fd1d130d7")
 
   DownloadPackage(${GTEST_MD5} ${GTEST_URL} "${GTEST_SOURCES_DIR}")
@@ -28,6 +45,8 @@ elseif (STATIC_BUILD OR NOT USE_SYSTEM_GOOGLE_TEST)
     add_definitions(/D _VARIADIC_MAX=10)
   endif()
 
+  source_group(ThirdParty\\GoogleTest REGULAR_EXPRESSION ${GTEST_SOURCES_DIR}/.*)
+
 else()
   include(FindGTest)
   if (NOT GTEST_FOUND)
diff --git a/Orthanc/Resources/CMake/JsonCppConfiguration.cmake b/Orthanc/Resources/CMake/JsonCppConfiguration.cmake
index bad61b8..1532a81 100644
--- a/Orthanc/Resources/CMake/JsonCppConfiguration.cmake
+++ b/Orthanc/Resources/CMake/JsonCppConfiguration.cmake
@@ -1,6 +1,6 @@
 if (STATIC_BUILD OR NOT USE_SYSTEM_JSONCPP)
   set(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-0.10.5)
-  set(JSONCPP_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/jsoncpp-0.10.5.tar.gz")
+  set(JSONCPP_URL "http://www.orthanc-server.com/downloads/third-party/jsoncpp-0.10.5.tar.gz")
   set(JSONCPP_MD5 "db146bac5a126ded9bd728ab7b61ed6b")
 
   DownloadPackage(${JSONCPP_MD5} ${JSONCPP_URL} "${JSONCPP_SOURCES_DIR}")
diff --git a/Orthanc/Resources/CMake/PugixmlConfiguration.cmake b/Orthanc/Resources/CMake/PugixmlConfiguration.cmake
index 4094fa3..4d57b12 100644
--- a/Orthanc/Resources/CMake/PugixmlConfiguration.cmake
+++ b/Orthanc/Resources/CMake/PugixmlConfiguration.cmake
@@ -1,10 +1,10 @@
 if (USE_PUGIXML)
-  add_definitions(-DORTHANC_PUGIXML_ENABLED=1)
+  add_definitions(-DORTHANC_ENABLE_PUGIXML=1)
 
   if (STATIC_BUILD OR NOT USE_SYSTEM_PUGIXML)
     set(PUGIXML_SOURCES_DIR ${CMAKE_BINARY_DIR}/pugixml-1.4)
     set(PUGIXML_MD5 "7c56c91cfe3ecdee248a8e4892ef5781")
-    set(PUGIXML_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/pugixml-1.4.tar.gz")
+    set(PUGIXML_URL "http://www.orthanc-server.com/downloads/third-party/pugixml-1.4.tar.gz")
 
     DownloadPackage(${PUGIXML_MD5} ${PUGIXML_URL} "${PUGIXML_SOURCES_DIR}")
 
@@ -26,6 +26,8 @@ if (USE_PUGIXML)
     link_libraries(pugixml)
   endif()
 
+  source_group(ThirdParty\\pugixml REGULAR_EXPRESSION ${PUGIXML_SOURCES_DIR}/.*)
+
 else()
-  add_definitions(-DORTHANC_PUGIXML_ENABLED=0)
+  add_definitions(-DORTHANC_ENABLE_PUGIXML=0)
 endif()
diff --git a/Orthanc/Resources/CMake/ZlibConfiguration.cmake b/Orthanc/Resources/CMake/ZlibConfiguration.cmake
index e1940c7..4487c1e 100644
--- a/Orthanc/Resources/CMake/ZlibConfiguration.cmake
+++ b/Orthanc/Resources/CMake/ZlibConfiguration.cmake
@@ -1,6 +1,6 @@
 if (STATIC_BUILD OR NOT USE_SYSTEM_ZLIB)
   SET(ZLIB_SOURCES_DIR ${CMAKE_BINARY_DIR}/zlib-1.2.7)
-  SET(ZLIB_URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/zlib-1.2.7.tar.gz")
+  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}")
@@ -27,10 +27,10 @@ if (STATIC_BUILD OR NOT USE_SYSTEM_ZLIB)
     ${ZLIB_SOURCES_DIR}/zutil.c
     )
 
+  source_group(ThirdParty\\zlib REGULAR_EXPRESSION ${ZLIB_SOURCES_DIR}/.*)
+
 else()
   include(FindZLIB)
   include_directories(${ZLIB_INCLUDE_DIRS})
   link_libraries(${ZLIB_LIBRARIES})
 endif()
-
-source_group(ThirdParty\\ZLib REGULAR_EXPRESSION ${ZLIB_SOURCES_DIR}/.*)
diff --git a/Orthanc/Resources/ThirdParty/VisualStudio/stdint.h b/Orthanc/Resources/ThirdParty/VisualStudio/stdint.h
index 59d0673..4fe0ef9 100644
--- a/Orthanc/Resources/ThirdParty/VisualStudio/stdint.h
+++ b/Orthanc/Resources/ThirdParty/VisualStudio/stdint.h
@@ -1,247 +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-2008 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. The name of the author 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
-
-#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
-#define INTMAX_C   INT64_C
-#define UINTMAX_C  UINT64_C
-
-#endif // __STDC_CONSTANT_MACROS ]
-
-
-#endif // _MSC_STDINT_H_ ]
+// 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_ ]
diff --git a/Orthanc/Resources/WindowsResources.py b/Orthanc/Resources/WindowsResources.py
index c56733b..b59e77a 100755
--- a/Orthanc/Resources/WindowsResources.py
+++ b/Orthanc/Resources/WindowsResources.py
@@ -3,6 +3,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017 Osimis, Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as
diff --git a/Plugin/Configuration.cpp b/Plugin/Configuration.cpp
index 950db7f..d9c2e6f 100644
--- a/Plugin/Configuration.cpp
+++ b/Plugin/Configuration.cpp
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -67,15 +68,15 @@ namespace OrthancPlugins
     Orthanc::Toolbox::StripSpaces(application);
     Orthanc::Toolbox::ToLowerCase(application);
 
-    boost::regex pattern("\\s*([^=]+)\\s*=\\s*([^=]+)\\s*");
-
+    boost::regex pattern("\\s*([^=]+)\\s*=\\s*(([^=\"]+)|\"([^=\"]+)\")\\s*");
+    
     for (size_t i = 1; i < tokens.size(); i++)
     {
       boost::cmatch what;
       if (boost::regex_match(tokens[i].c_str(), what, pattern))
       {
         std::string key(what[1]);
-        std::string value(what[2]);
+        std::string value(what.length(3) != 0 ? what[3] : what[4]);
         Orthanc::Toolbox::ToLowerCase(key);
         attributes[key] = value;
       }
@@ -83,107 +84,163 @@ namespace OrthancPlugins
   }
 
 
-  void ParseMultipartBody(std::vector<MultipartItem>& result,
-                          OrthancPluginContext* context,
-                          const char* body,
-                          const uint64_t bodySize,
-                          const std::string& boundary)
-  {
-    // Reference:
-    // https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
+  static const boost::regex MULTIPART_HEADERS_ENDING("(.*?\r\n)\r\n(.*)");
+  static const boost::regex MULTIPART_HEADERS_LINE(".*?\r\n");
 
-    result.clear();
+  static void ParseMultipartHeaders(bool& hasLength /* out */,
+                                    size_t& length /* out */,
+                                    std::string& contentType /* out */,
+                                    OrthancPluginContext* context,
+                                    const char* startHeaders,
+                                    const char* endHeaders)
+  {
+    hasLength = false;
+    contentType = "application/octet-stream";
 
-    const boost::regex separator("(^|\r\n)--" + boundary + "(--|\r\n)");
-    const boost::regex encapsulation("(.*)\r\n\r\n(.*)");
- 
-    std::vector< std::pair<const char*, const char*> > parts;
-    
-    const char* start = body;
-    const char* end = body + bodySize;
+    // Loop over the HTTP headers of this multipart item
+    boost::cregex_token_iterator it(startHeaders, endHeaders, MULTIPART_HEADERS_LINE, 0);
+    boost::cregex_token_iterator iteratorEnd;
 
-    boost::cmatch what;
-    boost::match_flag_type flags = boost::match_perl | boost::match_single_line;
-    while (boost::regex_search(start, end, what, separator, flags))   
+    for (; it != iteratorEnd; ++it)
     {
-      if (start != body)  // Ignore the first separator
+      const std::string line(*it);
+      size_t colon = line.find(':');
+      size_t eol = line.find('\r');
+
+      if (colon != std::string::npos &&
+          eol != std::string::npos &&
+          colon < eol &&
+          eol + 2 == line.length())
       {
-        parts.push_back(std::make_pair(start, what[0].first));
-      }
+        std::string key = Orthanc::Toolbox::StripSpaces(line.substr(0, colon));
+        Orthanc::Toolbox::ToLowerCase(key);
 
-      if (*what[2].first == '-')
-      {
-        // This is the last separator (there is a trailing "--")
-        break;
+        const std::string value = Orthanc::Toolbox::StripSpaces(line.substr(colon + 1, eol - colon - 1));
+
+        if (key == "content-length")
+        {
+          try
+          {
+            int tmp = boost::lexical_cast<int>(value);
+            if (tmp >= 0)
+            {
+              hasLength = true;
+              length = tmp;
+            }
+          }
+          catch (boost::bad_lexical_cast&)
+          {
+            OrthancPluginLogWarning(context, "Unable to parse the Content-Length of a multipart item");
+          }
+        }
+        else if (key == "content-type")
+        {
+          contentType = value;
+        }
       }
+    }
+  }
 
-      start = what[0].second;
-      flags |= boost::match_prev_avail;
+
+  static const char* ParseMultipartItem(std::vector<MultipartItem>& result,
+                                        OrthancPluginContext* context,
+                                        const char* start,
+                                        const char* end,
+                                        const boost::regex& nextSeparator)
+  {
+    // Just before "start", it is guaranteed that "--[BOUNDARY]\r\n" is present
+
+    boost::cmatch what;
+    if (!boost::regex_match(start, end, what, MULTIPART_HEADERS_ENDING, boost::match_perl))
+    {
+      // Cannot find the HTTP headers of this multipart item
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
     }
 
-    for (size_t i = 0; i < parts.size(); i++)
+    // Some aliases for more clarity
+    assert(what[1].first == start);
+    const char* startHeaders = what[1].first;
+    const char* endHeaders = what[1].second;
+    const char* startBody = what[2].first;
+
+    bool hasLength;
+    size_t length;
+    std::string contentType;
+    ParseMultipartHeaders(hasLength, length, contentType, context, startHeaders, endHeaders);
+
+    boost::cmatch separator;
+
+    if (hasLength)
     {
-      if (boost::regex_match(parts[i].first, parts[i].second, what, encapsulation, boost::match_perl))
+      if (!boost::regex_match(startBody + length, end, separator, nextSeparator, boost::match_perl) ||
+          startBody + length != separator[1].first)
       {
-        size_t dicomSize = what[2].second - what[2].first;
+        // Cannot find the separator after skipping the "Content-Length" bytes
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+    }
+    else
+    {
+      if (!boost::regex_match(startBody, end, separator, nextSeparator, boost::match_perl))
+      {
+        // No more occurrence of the boundary separator
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+      }
+    }
 
-        std::string contentType = "application/octet-stream";
-        std::vector<std::string> headers;
+    MultipartItem item;
+    item.data_ = startBody;
+    item.size_ = separator[1].first - startBody;
+    item.contentType_ = contentType;
+    result.push_back(item);
 
-        {
-          std::string tmp;
-          tmp.assign(what[1].first, what[1].second);
-          Orthanc::Toolbox::TokenizeString(headers, tmp, '\n');
-        }
+    return separator[1].second;  // Return the end of the separator
+  }
 
-        bool valid = true;
 
-        for (size_t j = 0; j < headers.size(); j++)
-        {
-          std::vector<std::string> tokens;
-          Orthanc::Toolbox::TokenizeString(tokens, headers[j], ':');
+  void ParseMultipartBody(std::vector<MultipartItem>& result,
+                          OrthancPluginContext* context,
+                          const char* body,
+                          const uint64_t bodySize,
+                          const std::string& boundary)
+  {
+    // Reference:
+    // https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
 
-          if (tokens.size() == 2)
-          {
-            std::string key = Orthanc::Toolbox::StripSpaces(tokens[0]);
-            std::string value = Orthanc::Toolbox::StripSpaces(tokens[1]);
-            Orthanc::Toolbox::ToLowerCase(key);
+    result.clear();
 
-            if (key == "content-type")
-            {
-              contentType = value;
-            }
-            else if (key == "content-length")
-            {
-              try
-              {
-                size_t s = boost::lexical_cast<size_t>(value);
-                if (s != dicomSize)
-                {
-                  valid = false;
-                }
-              }
-              catch (boost::bad_lexical_cast&)
-              {
-                valid = false;
-              }
-            }
-          }
-        }
+    // Look for the first boundary separator in the body (note the "?"
+    // to request non-greedy search)
+    const boost::regex firstSeparator1("--" + boundary + "(--|\r\n).*");
+    const boost::regex firstSeparator2(".*?\r\n--" + boundary + "(--|\r\n).*");
+
+    // Look for the next boundary separator in the body (note the "?"
+    // to request non-greedy search)
+    const boost::regex nextSeparator(".*?(\r\n--" + boundary + ").*");
 
-        if (valid)
+    const char* end = body + bodySize;
+
+    boost::cmatch what;
+    if (boost::regex_match(body, end, what, firstSeparator1, boost::match_perl | boost::match_single_line) ||
+        boost::regex_match(body, end, what, firstSeparator2, boost::match_perl | boost::match_single_line))
+    {
+      const char* current = what[1].first;
+
+      while (current != NULL &&
+             current + 2 < end)
+      {
+        if (current[0] != '\r' ||
+            current[1] != '\n')
         {
-          MultipartItem item;
-          item.data_ = what[2].first;
-          item.size_ = dicomSize;
-          item.contentType_ = contentType;
-          result.push_back(item);          
+          // We reached a separator with a trailing "--", which
+          // means that reading the multipart body is done
+          break;
         }
         else
         {
-          OrthancPluginLogWarning(context, "Ignoring a badly-formatted item in a multipart body");
+          current = ParseMultipartItem(result, context, current + 2, end, nextSeparator);
         }
-      }      
+      }
     }
   }
 
@@ -195,7 +252,7 @@ namespace OrthancPlugins
     if (value.type() != Json::objectValue)
     {
       OrthancPlugins::Configuration::LogError("This is not a JSON object");
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
     }
 
     if (!value.isMember(key))
@@ -209,7 +266,7 @@ namespace OrthancPlugins
     {
       OrthancPlugins::Configuration::LogError("The field \"" + key + "\" of a JSON object is "
                                               "not a JSON associative array as expected");
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
     }
 
     Json::Value::Members names = tmp.getMemberNames();
@@ -220,7 +277,7 @@ namespace OrthancPlugins
       {
         OrthancPlugins::Configuration::LogError("Some value in the associative array \"" + key + 
                                                 "\" is not a string as expected");
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
       }
       else
       {
diff --git a/Plugin/Configuration.h b/Plugin/Configuration.h
index 7d2de3a..50d7e25 100644
--- a/Plugin/Configuration.h
+++ b/Plugin/Configuration.h
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
diff --git a/Plugin/Dicom.cpp b/Plugin/Dicom.cpp
index 901de8e..ce78917 100644
--- a/Plugin/Dicom.cpp
+++ b/Plugin/Dicom.cpp
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -176,7 +177,7 @@ namespace OrthancPlugins
     {
       OrthancPlugins::Configuration::LogError("GDCM cannot decode this DICOM instance of length " +
                                               boost::lexical_cast<std::string>(dicom.size()));
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
     }
   }
 
@@ -253,7 +254,7 @@ namespace OrthancPlugins
   {
     if (!GetDataSet().FindDataElement(tag))
     {
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InexistentTag);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentTag);
     }
 
     const gdcm::DataElement& element = GetDataSet().GetDataElement(tag);
@@ -700,7 +701,7 @@ namespace OrthancPlugins
     if (key.find('.') != std::string::npos)
     {
       OrthancPlugins::Configuration::LogError("This DICOMweb plugin does not support hierarchical queries: " + key);
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NotImplemented);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
     }
 
     if (key.size() == 8 &&  // This is the DICOMweb convention
@@ -740,12 +741,12 @@ namespace OrthancPlugins
         if (key.find('.') != std::string::npos)
         {
           OrthancPlugins::Configuration::LogError("This QIDO-RS implementation does not support search over sequences: " + key);
-          throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NotImplemented);
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
         }
         else
         {
           OrthancPlugins::Configuration::LogError("Illegal tag name in QIDO-RS: " + key);
-          throw OrthancPlugins::PluginException(OrthancPluginErrorCode_UnknownDicomTag);
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownDicomTag);
         }
       }
 
diff --git a/Plugin/Dicom.h b/Plugin/Dicom.h
index c9af9fb..dbfc53e 100644
--- a/Plugin/Dicom.h
+++ b/Plugin/Dicom.h
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -49,6 +50,7 @@ namespace OrthancPlugins
   static const gdcm::Tag DICOM_TAG_ACCESSION_NUMBER(0x0008, 0x0050);
   static const gdcm::Tag DICOM_TAG_SPECIFIC_CHARACTER_SET(0x0008, 0x0005);
   static const gdcm::Tag DICOM_TAG_PIXEL_DATA(0x7fe0, 0x0010);
+  static const gdcm::Tag DICOM_TAG_SAMPLES_PER_PIXEL(0x0028, 0x0002);
   static const gdcm::Tag DICOM_TAG_COLUMNS(0x0028, 0x0011);
   static const gdcm::Tag DICOM_TAG_ROWS(0x0028, 0x0010);
   static const gdcm::Tag DICOM_TAG_BITS_ALLOCATED(0x0028, 0x0100);
diff --git a/Plugin/DicomResults.cpp b/Plugin/DicomResults.cpp
index a4ef1d7..80003cb 100644
--- a/Plugin/DicomResults.cpp
+++ b/Plugin/DicomResults.cpp
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -47,7 +48,7 @@ namespace OrthancPlugins
         OrthancPluginStartMultipartAnswer(context_, output_, "related", "application/dicom+xml") != 0)
     {
       OrthancPlugins::Configuration::LogError("Unable to create a multipart stream of DICOM+XML answers");
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
     }
 
     jsonWriter_.AddChunk("[\n");
@@ -61,7 +62,7 @@ namespace OrthancPlugins
       if (OrthancPluginSendMultipartItem(context_, output_, item.c_str(), item.size()) != 0)
       {
         OrthancPlugins::Configuration::LogError("Unable to create a multipart stream of DICOM+XML answers");
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
       }
     }
     else
@@ -120,7 +121,7 @@ namespace OrthancPlugins
       {
         if (source.type() != Json::objectValue)
         {
-          throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
         }
 
         Json::Value::Members members = source.getMemberNames();
@@ -133,7 +134,7 @@ namespace OrthancPlugins
               !source[members[i]].isMember("Type") ||
               source[members[i]]["Type"].type() != Json::stringValue)
           {
-            throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
           }        
 
           const Json::Value& value = source[members[i]]["Value"];
@@ -213,7 +214,7 @@ namespace OrthancPlugins
           if (type != "Sequence" ||
               value.type() != Json::arrayValue)
           {
-            throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
           }
 
           node["Value"] = Json::arrayValue;
@@ -222,7 +223,7 @@ namespace OrthancPlugins
           {
             if (value[i].type() != Json::objectValue)
             {
-              throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+              throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
             }
 
             Json::Value child;
@@ -307,14 +308,14 @@ namespace OrthancPlugins
           if (type != "Sequence" ||
               value.type() != Json::arrayValue)
           {
-            throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
           }
 
           for (Json::Value::ArrayIndex i = 0; i < value.size(); i++)
           {
             if (value[i].type() != Json::objectValue)
             {
-              throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+              throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
             }
 
             pugi::xml_node child = node.append_child("Item");
diff --git a/Plugin/DicomResults.h b/Plugin/DicomResults.h
index bc7e09a..a348c0c 100644
--- a/Plugin/DicomResults.h
+++ b/Plugin/DicomResults.h
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
diff --git a/Plugin/DicomWebClient.cpp b/Plugin/DicomWebClient.cpp
index 4259c02..8b8fcda 100644
--- a/Plugin/DicomWebClient.cpp
+++ b/Plugin/DicomWebClient.cpp
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -39,7 +40,7 @@ static void AddInstance(std::list<std::string>& target,
       !instance.isMember("ID") ||
       instance["ID"].type() != Json::stringValue)
   {
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
   }
   else
   {
@@ -72,7 +73,7 @@ static bool GetSequenceSize(size_t& result,
   {
     OrthancPlugins::Configuration::LogError("The STOW-RS JSON response from DICOMweb server " + server + 
                                             " does not contain the mandatory tag " + upper);
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
   }
   else
   {
@@ -84,7 +85,7 @@ static bool GetSequenceSize(size_t& result,
       (*value) ["Value"].type() != Json::arrayValue)
   {
     OrthancPlugins::Configuration::LogError("Unable to parse STOW-RS JSON response from DICOMweb server " + server);
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
   }
 
   result = (*value) ["Value"].size();
@@ -95,10 +96,12 @@ static bool GetSequenceSize(size_t& result,
 
 static void ParseStowRequest(std::list<std::string>& instances /* out */,
                              std::map<std::string, std::string>& httpHeaders /* out */,
+                             std::map<std::string, std::string>& queryArguments /* out */,
                              const OrthancPluginHttpRequest* request /* in */)
 {
   static const char* RESOURCES = "Resources";
   static const char* HTTP_HEADERS = "HttpHeaders";
+  static const char* QUERY_ARGUMENTS = "Arguments";
 
   OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
 
@@ -112,9 +115,10 @@ static void ParseStowRequest(std::list<std::string>& instances /* out */,
     OrthancPlugins::Configuration::LogError("A request to the DICOMweb STOW-RS client must provide a JSON object "
                                             "with the field \"" + std::string(RESOURCES) + 
                                             "\" containing an array of resources to be sent");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
   }
 
+  OrthancPlugins::ParseAssociativeArray(queryArguments, body, QUERY_ARGUMENTS);
   OrthancPlugins::ParseAssociativeArray(httpHeaders, body, HTTP_HEADERS);
 
   Json::Value& resources = body[RESOURCES];
@@ -124,32 +128,32 @@ static void ParseStowRequest(std::list<std::string>& instances /* out */,
   {
     if (resources[i].type() != Json::stringValue)
     {
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
     }
 
     std::string resource = resources[i].asString();
     if (resource.empty())
     {
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_UnknownResource);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
     }
 
     // Test whether this resource is an instance
     Json::Value tmp;
-    if (OrthancPlugins::RestApiGetJson(tmp, context, "/instances/" + resource, false))
+    if (OrthancPlugins::RestApiGet(tmp, context, "/instances/" + resource, false))
     {
       AddInstance(instances, tmp);
     }
     // This was not an instance, successively try with series/studies/patients
-    else if ((OrthancPlugins::RestApiGetJson(tmp, context, "/series/" + resource, false) &&
-              OrthancPlugins::RestApiGetJson(tmp, context, "/series/" + resource + "/instances", false)) ||
-             (OrthancPlugins::RestApiGetJson(tmp, context, "/studies/" + resource, false) &&
-              OrthancPlugins::RestApiGetJson(tmp, context, "/studies/" + resource + "/instances", false)) ||
-             (OrthancPlugins::RestApiGetJson(tmp, context, "/patients/" + resource, false) &&
-              OrthancPlugins::RestApiGetJson(tmp, context, "/patients/" + resource + "/instances", false)))
+    else if ((OrthancPlugins::RestApiGet(tmp, context, "/series/" + resource, false) &&
+              OrthancPlugins::RestApiGet(tmp, context, "/series/" + resource + "/instances", false)) ||
+             (OrthancPlugins::RestApiGet(tmp, context, "/studies/" + resource, false) &&
+              OrthancPlugins::RestApiGet(tmp, context, "/studies/" + resource + "/instances", false)) ||
+             (OrthancPlugins::RestApiGet(tmp, context, "/patients/" + resource, false) &&
+              OrthancPlugins::RestApiGet(tmp, context, "/patients/" + resource + "/instances", false)))
     {
       if (tmp.type() != Json::arrayValue)
       {
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
       }
 
       for (Json::Value::ArrayIndex j = 0; j < tmp.size(); j++)
@@ -159,7 +163,7 @@ static void ParseStowRequest(std::list<std::string>& instances /* out */,
     }
     else
     {
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_UnknownResource);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
     }   
   }
 }
@@ -167,6 +171,7 @@ static void ParseStowRequest(std::list<std::string>& instances /* out */,
 
 static void SendStowChunks(const Orthanc::WebServiceParameters& server,
                            const std::map<std::string, std::string>& httpHeaders,
+                           const std::map<std::string, std::string>& queryArguments,
                            const std::string& boundary,
                            Orthanc::ChunkedBuffer& chunks,
                            size_t& countInstances,
@@ -186,8 +191,12 @@ static void SendStowChunks(const Orthanc::WebServiceParameters& server,
 
     OrthancPlugins::MemoryBuffer answerBody(OrthancPlugins::Configuration::GetContext());
     std::map<std::string, std::string> answerHeaders;
+
+    std::string uri;
+    OrthancPlugins::UriEncode(uri, "studies", queryArguments);
+
     OrthancPlugins::CallServer(answerBody, answerHeaders, server, OrthancPluginHttpMethod_Post,
-                               httpHeaders, "studies", body);
+                               httpHeaders, uri, body);
 
     Json::Value response;
     Json::Reader reader;
@@ -200,7 +209,7 @@ static void SendStowChunks(const Orthanc::WebServiceParameters& server,
         !response.isMember("00081199"))
     {
       OrthancPlugins::Configuration::LogError("Unable to parse STOW-RS JSON response from DICOMweb server " + server.GetUrl());
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
     }
 
     size_t size;
@@ -210,7 +219,7 @@ static void SendStowChunks(const Orthanc::WebServiceParameters& server,
       OrthancPlugins::Configuration::LogError("The STOW-RS server was only able to receive " + 
                                               boost::lexical_cast<std::string>(size) + " instances out of " +
                                               boost::lexical_cast<std::string>(countInstances));
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
     }
 
     if (GetSequenceSize(size, response, "00081198", false, server.GetUrl()) &&
@@ -219,7 +228,7 @@ static void SendStowChunks(const Orthanc::WebServiceParameters& server,
       OrthancPlugins::Configuration::LogError("The response from the STOW-RS server contains " + 
                                               boost::lexical_cast<std::string>(size) + 
                                               " items in its Failed SOP Sequence (0008,1198) tag");
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);    
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);    
     }
 
     if (GetSequenceSize(size, response, "0008119A", false, server.GetUrl()) &&
@@ -228,7 +237,7 @@ static void SendStowChunks(const Orthanc::WebServiceParameters& server,
       OrthancPlugins::Configuration::LogError("The response from the STOW-RS server contains " + 
                                               boost::lexical_cast<std::string>(size) + 
                                               " items in its Other Failures Sequence (0008,119A) tag");
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);    
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);    
     }
 
     countInstances = 0;
@@ -244,7 +253,7 @@ void StowClient(OrthancPluginRestOutput* output,
 
   if (request->groupsCount != 1)
   {
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadRequest);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest);
   }
 
   if (request->method != OrthancPluginHttpMethod_Post)
@@ -266,7 +275,7 @@ void StowClient(OrthancPluginRestOutput* output,
     catch (...)
     {
       OrthancPluginFreeString(context, uuid);
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NotEnoughMemory);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
     }
 
     OrthancPluginFreeString(context, uuid);
@@ -274,15 +283,16 @@ void StowClient(OrthancPluginRestOutput* output,
 
   std::string mime = "multipart/related; type=application/dicom; boundary=" + boundary;
 
+  std::map<std::string, std::string> queryArguments;
   std::map<std::string, std::string> httpHeaders;
   httpHeaders["Accept"] = "application/json";
   httpHeaders["Expect"] = "";
   httpHeaders["Content-Type"] = mime;
 
   std::list<std::string> instances;
-  ParseStowRequest(instances, httpHeaders, request);
+  ParseStowRequest(instances, httpHeaders, queryArguments, request);
 
-  OrthancPlugins::Configuration::LogInfo("Sending " + boost::lexical_cast<std::string>(instances.size()) + 
+  OrthancPlugins::Configuration::LogInfo("Sending " + boost::lexical_cast<std::string>(instances.size()) +
                                          " instances using STOW-RS to DICOMweb server: " + server.GetUrl());
 
   Orthanc::ChunkedBuffer chunks;
@@ -300,11 +310,11 @@ void StowClient(OrthancPluginRestOutput* output,
       chunks.AddChunk(dicom.GetData(), dicom.GetSize());
       countInstances ++;
 
-      SendStowChunks(server, httpHeaders, boundary, chunks, countInstances, false);
+      SendStowChunks(server, httpHeaders, queryArguments, boundary, chunks, countInstances, false);
     }
   }
 
-  SendStowChunks(server, httpHeaders, boundary, chunks, countInstances, true);
+  SendStowChunks(server, httpHeaders, queryArguments, boundary, chunks, countInstances, true);
 
   std::string answer = "{}\n";
   OrthancPluginAnswerBuffer(context, output, answer.c_str(), answer.size(), "application/json");
@@ -317,7 +327,7 @@ static bool GetStringValue(std::string& target,
 {
   if (json.type() != Json::objectValue)
   {
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
   }
   else if (!json.isMember(key))
   {
@@ -327,7 +337,7 @@ static bool GetStringValue(std::string& target,
   else if (json[key].type() != Json::stringValue)
   {
     OrthancPlugins::Configuration::LogError("The field \"" + key + "\" in a JSON object should be a string");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
   }
   else
   {
@@ -362,9 +372,9 @@ void GetFromServer(OrthancPluginRestOutput* output,
       body.type() != Json::objectValue ||
       !GetStringValue(tmp, body, URI))
   {
-    OrthancPlugins::Configuration::LogError("A request to the DICOMweb STOW-RS client must provide a JSON object "
+    OrthancPlugins::Configuration::LogError("A request to the DICOMweb client must provide a JSON object "
                                             "with the field \"Uri\" containing the URI of interest");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
   }
 
   std::map<std::string, std::string> getArguments;
@@ -412,6 +422,7 @@ void GetFromServer(OrthancPluginRestOutput* output,
 static void RetrieveFromServerInternal(std::set<std::string>& instances,
                                        const Orthanc::WebServiceParameters& server,
                                        const std::map<std::string, std::string>& httpHeaders,
+                                       const std::map<std::string, std::string>& getArguments,
                                        const Json::Value& resource)
 {
   static const std::string STUDY = "Study";
@@ -426,7 +437,7 @@ static void RetrieveFromServerInternal(std::set<std::string>& instances,
   {
     OrthancPlugins::Configuration::LogError("Resources of interest for the DICOMweb WADO-RS Retrieve client "
                                             "must be provided as a JSON object");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
   }
 
   std::string study, series, instance;
@@ -435,7 +446,7 @@ static void RetrieveFromServerInternal(std::set<std::string>& instances,
   {
     OrthancPlugins::Configuration::LogError("A non-empty \"" + STUDY + "\" field is mandatory for the "
                                             "DICOMweb WADO-RS Retrieve client");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
   }
 
   GetStringValue(series, resource, SERIES);
@@ -446,19 +457,22 @@ static void RetrieveFromServerInternal(std::set<std::string>& instances,
   {
     OrthancPlugins::Configuration::LogError("When specifying a \"" + INSTANCE + "\" field in a call to DICOMweb "
                                             "WADO-RS Retrieve client, the \"" + SERIES + "\" field is mandatory");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
   }
 
-  std::string uri = "studies/" + study;
+  std::string tmpUri = "studies/" + study;
   if (!series.empty())
   {
-    uri += "/series/" + series;
+    tmpUri += "/series/" + series;
     if (!instance.empty())
     {
-      uri += "/instances/" + instance;
+      tmpUri += "/instances/" + instance;
     }
   }
 
+  std::string uri;
+  OrthancPlugins::UriEncode(uri, tmpUri, getArguments);
+
   OrthancPlugins::MemoryBuffer answerBody(context);
   std::map<std::string, std::string> answerHeaders;
   OrthancPlugins::CallServer(answerBody, answerHeaders, server, OrthancPluginHttpMethod_Get, httpHeaders, uri, "");
@@ -479,7 +493,7 @@ static void RetrieveFromServerInternal(std::set<std::string>& instances,
   if (contentType.empty())
   {
     OrthancPlugins::Configuration::LogError("No Content-Type provided by the remote WADO-RS server");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
   }
 
   Orthanc::Toolbox::ToLowerCase(contentType[0]);
@@ -487,7 +501,7 @@ static void RetrieveFromServerInternal(std::set<std::string>& instances,
   {
     OrthancPlugins::Configuration::LogError("The remote WADO-RS server answers with a \"" + contentType[0] +
                                             "\" Content-Type, but \"" + MULTIPART_RELATED + "\" is expected");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
   }
 
   std::string type, boundary;
@@ -504,6 +518,18 @@ static void RetrieveFromServerInternal(std::set<std::string>& instances,
       if (s == "type")
       {
         type = Orthanc::Toolbox::StripSpaces(tokens[1]);
+
+        // This covers the case where the content-type is quoted,
+        // which COULD be the case 
+        // cf. https://tools.ietf.org/html/rfc7231#section-3.1.1.1
+        size_t len = type.length();
+        if (len >= 2 &&
+            type[0] == '"' &&
+            type[len - 1] == '"')
+        {
+          type = type.substr(1, len - 2);
+        }
+        
         Orthanc::Toolbox::ToLowerCase(type);
       }
       else if (s == "boundary")
@@ -517,13 +543,13 @@ static void RetrieveFromServerInternal(std::set<std::string>& instances,
   {
     OrthancPlugins::Configuration::LogError("The remote WADO-RS server answers with a \"" + type +
                                             "\" multipart Content-Type, but \"" + APPLICATION_DICOM + "\" is expected");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
   }
 
   if (boundary.empty())
   {
     OrthancPlugins::Configuration::LogError("The remote WADO-RS server does not provide a boundary for its multipart answer");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
   }
 
   std::vector<OrthancPlugins::MultipartItem> parts;
@@ -540,7 +566,7 @@ static void RetrieveFromServerInternal(std::set<std::string>& instances,
     if (parts[i].contentType_ != APPLICATION_DICOM)
     {
       OrthancPlugins::Configuration::LogError("The remote WADO-RS server has provided a non-DICOM file in its multipart answer");
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);      
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);      
     }
 
     OrthancPlugins::MemoryBuffer tmp(context);
@@ -553,7 +579,7 @@ static void RetrieveFromServerInternal(std::set<std::string>& instances,
         !result.isMember("ID") ||
         result["ID"].type() != Json::stringValue)
     {
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);      
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);      
     }
     else
     {
@@ -570,6 +596,7 @@ void RetrieveFromServer(OrthancPluginRestOutput* output,
 {
   static const std::string RESOURCES("Resources");
   static const char* HTTP_HEADERS = "HttpHeaders";
+  static const std::string GET_ARGUMENTS = "Arguments";
 
   OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
 
@@ -590,16 +617,20 @@ void RetrieveFromServer(OrthancPluginRestOutput* output,
   {
     OrthancPlugins::Configuration::LogError("A request to the DICOMweb WADO-RS Retrieve client must provide a JSON object "
                                             "with the field \"" + RESOURCES + "\" containing an array of resources");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
   }
 
   std::map<std::string, std::string> httpHeaders;
   OrthancPlugins::ParseAssociativeArray(httpHeaders, body, HTTP_HEADERS);
 
+  std::map<std::string, std::string> getArguments;
+  OrthancPlugins::ParseAssociativeArray(getArguments, body, GET_ARGUMENTS);
+
+
   std::set<std::string> instances;
   for (Json::Value::ArrayIndex i = 0; i < body[RESOURCES].size(); i++)
   {
-    RetrieveFromServerInternal(instances, server, httpHeaders, body[RESOURCES][i]);
+    RetrieveFromServerInternal(instances, server, httpHeaders, getArguments, body[RESOURCES][i]);
   }
 
   Json::Value status = Json::objectValue;
diff --git a/Plugin/DicomWebClient.h b/Plugin/DicomWebClient.h
index 79922db..83b7f4d 100644
--- a/Plugin/DicomWebClient.h
+++ b/Plugin/DicomWebClient.h
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
diff --git a/Plugin/DicomWebServers.cpp b/Plugin/DicomWebServers.cpp
index bbc8112..4df24ee 100644
--- a/Plugin/DicomWebServers.cpp
+++ b/Plugin/DicomWebServers.cpp
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -71,7 +72,7 @@ namespace OrthancPlugins
     if (!ok)
     {
       OrthancPlugins::Configuration::LogError("Cannot parse the \"DicomWeb.Servers\" section of the configuration file");
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
     }
   }
 
@@ -92,7 +93,7 @@ namespace OrthancPlugins
         server->second == NULL)
     {
       OrthancPlugins::Configuration::LogError("Inexistent server: " + name);
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InexistentItem);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem);
     }
     else
     {
@@ -206,7 +207,7 @@ namespace OrthancPlugins
     {
       OrthancPlugins::Configuration::LogError("Cannot issue an HTTP query to " + url + 
                                               " (HTTP status: " + boost::lexical_cast<std::string>(status) + ")");
-      throw PluginException(code);
+      throw Orthanc::OrthancException(static_cast<Orthanc::ErrorCode>(code));
     }
 
     Json::Value json;
@@ -215,7 +216,7 @@ namespace OrthancPlugins
 
     if (json.type() != Json::objectValue)
     {
-      throw PluginException(OrthancPluginErrorCode_InternalError);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
     }
 
     Json::Value::Members members = json.getMemberNames();
@@ -225,7 +226,7 @@ namespace OrthancPlugins
 
       if (json[key].type() != Json::stringValue)
       {
-        throw PluginException(OrthancPluginErrorCode_InternalError);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
       }
       else
       {
@@ -243,7 +244,7 @@ namespace OrthancPlugins
     {
       OrthancPlugins::Configuration::LogError("The GET arguments must be provided in a separate field "
                                               "(explicit \"?\" is disallowed): " + resource);
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
     }
 
     uri = resource;
diff --git a/Plugin/DicomWebServers.h b/Plugin/DicomWebServers.h
index df3aded..744e40f 100644
--- a/Plugin/DicomWebServers.h
+++ b/Plugin/DicomWebServers.h
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
diff --git a/Plugin/Plugin.cpp b/Plugin/Plugin.cpp
index 0bf076e..e1832f8 100644
--- a/Plugin/Plugin.cpp
+++ b/Plugin/Plugin.cpp
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -140,10 +141,21 @@ void ListServerOperations(OrthancPluginRestOutput* output,
 }
 
 
+static bool DisplayPerformanceWarning(OrthancPluginContext* context)
+{
+  (void) DisplayPerformanceWarning;   // Disable warning about unused function
+  OrthancPluginLogWarning(context, "Performance warning in DICOMweb: "
+                          "Non-release build, runtime debug assertions are turned on");
+  return true;
+}
+
+
 extern "C"
 {
   ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
   {
+    assert(DisplayPerformanceWarning(context));
+
     /* Check the version of the Orthanc core */
     if (OrthancPluginCheckVersion(context) == 0)
     {
@@ -215,12 +227,6 @@ extern "C"
         OrthancPlugins::Configuration::LogWarning("WADO-URI support is disabled");
       }
     }
-    catch (OrthancPlugins::PluginException& e)
-    {
-      OrthancPlugins::Configuration::LogError("Exception while initializing the DICOMweb plugin: " + 
-                                              std::string(e.GetErrorDescription(context)));
-      return -1;
-    }
     catch (Orthanc::OrthancException& e)
     {
       OrthancPlugins::Configuration::LogError("Exception while initializing the DICOMweb plugin: " + 
diff --git a/Plugin/Plugin.h b/Plugin/Plugin.h
index e3a7721..b678331 100644
--- a/Plugin/Plugin.h
+++ b/Plugin/Plugin.h
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
diff --git a/Plugin/QidoRs.cpp b/Plugin/QidoRs.cpp
index 7a69482..312aa9e 100644
--- a/Plugin/QidoRs.cpp
+++ b/Plugin/QidoRs.cpp
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -150,7 +151,7 @@ namespace
           break;
 
         default:
-          throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
       }
     }
 
@@ -162,10 +163,13 @@ namespace
     limit_(0),
     includeAllFields_(false)
     {
+      std::string args;
+      
       for (uint32_t i = 0; i < request->getCount; i++)
       {
         std::string key(request->getKeys[i]);
         std::string value(request->getValues[i]);
+        args += " [" + key + "=" + value + "]";
 
         if (key == "limit")
         {
@@ -188,7 +192,7 @@ namespace
           else
           {
             OrthancPlugins::Configuration::LogError("Not a proper value for fuzzy matching (true or false): " + value);
-            throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadRequest);
+            throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest);
           }
         }
         else if (key == "includefield")
@@ -213,6 +217,8 @@ namespace
           filters_[OrthancPlugins::ParseTag(*dictionary_, key)] = value;
         }
       }
+
+      OrthancPlugins::Configuration::LogInfo("Arguments of QIDO-RS request:" + args);
     }
 
     unsigned int GetLimit() const
@@ -260,13 +266,24 @@ namespace
           break;
 
         default:
-          throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
       }
 
       result["Expand"] = false;
       result["CaseSensitive"] = true;
       result["Query"] = Json::objectValue;
+      result["Limit"] = limit_;
+      result["Since"] = offset_;
 
+      if (offset_ != 0 &&
+          !OrthancPlugins::CheckMinimalOrthancVersion(
+            OrthancPlugins::Configuration::GetContext(), 1, 2, 1))
+      {
+        OrthancPlugins::Configuration::LogError(
+          "QIDO-RS request with \"offset\" argument: "
+          "Only available if the Orthanc core version is >= 1.2.1");
+      }
+      
       for (Filters::const_iterator it = filters_.begin(); 
            it != filters_.end(); ++it)
       {
@@ -288,8 +305,8 @@ namespace
         case QueryLevel_Study:
         {
           Json::Value series, instances;
-          if (OrthancPlugins::RestApiGetJson(series, context, "/studies/" + resource + "/series?expand", false) &&
-              OrthancPlugins::RestApiGetJson(instances, context, "/studies/" + resource + "/instances", false))
+          if (OrthancPlugins::RestApiGet(series, context, "/studies/" + resource + "/series?expand", false) &&
+              OrthancPlugins::RestApiGet(instances, context, "/studies/" + resource + "/instances", false))
           {
             // Number of Study Related Series
             target[gdcm::Tag(0x0020, 0x1206)] = boost::lexical_cast<std::string>(series.size());
@@ -335,7 +352,7 @@ namespace
         case QueryLevel_Series:
         {
           Json::Value instances;
-          if (OrthancPlugins::RestApiGetJson(instances, context, "/series/" + resource + "/instances", false))
+          if (OrthancPlugins::RestApiGet(instances, context, "/series/" + resource + "/instances", false))
           {
             // Number of Series Related Instances
             target[gdcm::Tag(0x0020, 0x1209)] = boost::lexical_cast<std::string>(instances.size());
@@ -507,10 +524,10 @@ static void ApplyMatcher(OrthancPluginRestOutput* output,
   std::string body = writer.write(find);
   
   Json::Value resources;
-  if (!OrthancPlugins::RestApiPostJson(resources, context, "/tools/find", body, false) ||
+  if (!OrthancPlugins::RestApiPost(resources, context, "/tools/find", body, false) ||
       resources.type() != Json::arrayValue)
   {
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
   }
 
   typedef std::list< std::pair<std::string, std::string> > ResourcesAndInstances;
@@ -527,7 +544,7 @@ static void ApplyMatcher(OrthancPluginRestOutput* output,
     {
       // Find one child instance of this resource
       Json::Value tmp;
-      if (OrthancPlugins::RestApiGetJson(tmp, context, root + resource + "/instances", false) &&
+      if (OrthancPlugins::RestApiGet(tmp, context, root + resource + "/instances", false) &&
           tmp.type() == Json::arrayValue &&
           tmp.size() > 0)
       {
@@ -585,7 +602,7 @@ static void ApplyMatcher(OrthancPluginRestOutput* output,
          it = resourcesAndInstances.begin(); it != resourcesAndInstances.end(); ++it)
   {
     Json::Value tags;
-    if (OrthancPlugins::RestApiGetJson(tags, context, "/instances/" + it->second + "/tags", false))
+    if (OrthancPlugins::RestApiGet(tags, context, "/instances/" + it->second + "/tags", false))
     {
       std::string wadoUrl = OrthancPlugins::Configuration::GetWadoUrl(
         wadoBase, 
diff --git a/Plugin/QidoRs.h b/Plugin/QidoRs.h
index 77a5c80..490d356 100644
--- a/Plugin/QidoRs.h
+++ b/Plugin/QidoRs.h
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
diff --git a/Plugin/StowRs.cpp b/Plugin/StowRs.cpp
index a8b1c55..a4a5e66 100644
--- a/Plugin/StowRs.cpp
+++ b/Plugin/StowRs.cpp
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -153,6 +154,12 @@ void StowCallback(OrthancPluginRestOutput* output,
   std::vector<OrthancPlugins::MultipartItem> items;
   OrthancPlugins::ParseMultipartBody(items, context, request->body, request->bodySize, boundary);
 
+  for (size_t i = 0; i < items.size(); i++)
+  {
+    OrthancPlugins::Configuration::LogInfo("Detected multipart item with content type \"" + 
+                                           items[i].contentType_ + "\" of size " + 
+                                           boost::lexical_cast<std::string>(items[i].size_));
+  }  
 
   for (size_t i = 0; i < items.size(); i++)
   {
diff --git a/Plugin/StowRs.h b/Plugin/StowRs.h
index 9e57321..7899c6c 100644
--- a/Plugin/StowRs.h
+++ b/Plugin/StowRs.h
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
diff --git a/Plugin/WadoRs.cpp b/Plugin/WadoRs.cpp
index 173071a..2bd619f 100644
--- a/Plugin/WadoRs.cpp
+++ b/Plugin/WadoRs.cpp
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -174,7 +175,7 @@ static void AnswerListOfDicomInstances(OrthancPluginRestOutput* output,
   OrthancPluginContext* context = OrthancPlugins::Configuration::GetContext();
 
   Json::Value instances;
-  if (!OrthancPlugins::RestApiGetJson(instances, context, resource + "/instances", false))
+  if (!OrthancPlugins::RestApiGet(instances, context, resource + "/instances", false))
   {
     // Internal error
     OrthancPluginSendHttpStatusCode(context, output, 400);
@@ -183,7 +184,7 @@ static void AnswerListOfDicomInstances(OrthancPluginRestOutput* output,
 
   if (OrthancPluginStartMultipartAnswer(context, output, "related", "application/dicom"))
   {
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
   }
   
   for (Json::Value::ArrayIndex i = 0; i < instances.size(); i++)
@@ -194,7 +195,7 @@ static void AnswerListOfDicomInstances(OrthancPluginRestOutput* output,
     if (dicom.RestApiGet(uri, false) &&
         OrthancPluginSendMultipartItem(context, output, dicom.GetData(), dicom.GetSize()) != 0)
     {
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
     }
   }
 }
@@ -217,7 +218,7 @@ static void AnswerMetadata(OrthancPluginRestOutput* output,
   else
   {
     Json::Value instances;
-    if (!OrthancPlugins::RestApiGetJson(instances, context, resource + "/instances", false))
+    if (!OrthancPlugins::RestApiGet(instances, context, resource + "/instances", false))
     {
       // Internal error
       OrthancPluginSendHttpStatusCode(context, output, 400);
@@ -310,7 +311,7 @@ static bool LocateSeries(OrthancPluginRestOutput* output,
   }
   
   Json::Value study;
-  if (!OrthancPlugins::RestApiGetJson(study, context, "/series/" + id + "/study", false))
+  if (!OrthancPlugins::RestApiGet(study, context, "/series/" + id + "/study", false))
   {
     OrthancPluginSendHttpStatusCode(context, output, 404);
     return false;
@@ -358,8 +359,8 @@ bool LocateInstance(OrthancPluginRestOutput* output,
   }
   
   Json::Value study, series;
-  if (!OrthancPlugins::RestApiGetJson(series, context, "/instances/" + id + "/series", false) ||
-      !OrthancPlugins::RestApiGetJson(study, context, "/instances/" + id + "/study", false))
+  if (!OrthancPlugins::RestApiGet(series, context, "/instances/" + id + "/series", false) ||
+      !OrthancPlugins::RestApiGet(study, context, "/instances/" + id + "/study", false))
   {
     OrthancPluginSendHttpStatusCode(context, output, 404);
     return false;
@@ -436,14 +437,14 @@ void RetrieveDicomInstance(OrthancPluginRestOutput* output,
     {
       if (OrthancPluginStartMultipartAnswer(context, output, "related", "application/dicom"))
       {
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
       }
 
       OrthancPlugins::MemoryBuffer dicom(context);
       if (dicom.RestApiGet(uri + "/file", false) &&
           OrthancPluginSendMultipartItem(context, output, dicom.GetData(), dicom.GetSize()) != 0)
       {
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
       }
     }
   }
@@ -612,7 +613,7 @@ void RetrieveBulkData(OrthancPluginRestOutput* output,
       if (OrthancPluginStartMultipartAnswer(context, output, "related", "application/octet-stream") != 0 ||
           OrthancPluginSendMultipartItem(context, output, result.c_str(), result.size()) != 0)
       {
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_Plugin);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
       }
     }
     else
diff --git a/Plugin/WadoRs.h b/Plugin/WadoRs.h
index bbbd302..e48a9fd 100644
--- a/Plugin/WadoRs.h
+++ b/Plugin/WadoRs.h
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
diff --git a/Plugin/WadoRsRetrieveFrames.cpp b/Plugin/WadoRsRetrieveFrames.cpp
index 7ad13fa..4eeeb70 100644
--- a/Plugin/WadoRsRetrieveFrames.cpp
+++ b/Plugin/WadoRsRetrieveFrames.cpp
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -68,7 +69,7 @@ static gdcm::TransferSyntax ParseTransferSyntax(const OrthancPluginHttpRequest*
 
       if (tokens[0] != "multipart/related")
       {
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
       }
 
       std::string type("application/octet-stream");
@@ -81,7 +82,7 @@ static gdcm::TransferSyntax ParseTransferSyntax(const OrthancPluginHttpRequest*
 
         if (parsed.size() != 2)
         {
-          throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadRequest);
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest);
         }
 
         if (parsed[0] == "type")
@@ -105,12 +106,70 @@ static gdcm::TransferSyntax ParseTransferSyntax(const OrthancPluginHttpRequest*
         {
           OrthancPlugins::Configuration::LogError("DICOMweb RetrieveFrames: Cannot specify a transfer syntax (" + 
                                                   transferSyntax + ") for default Little Endian uncompressed pixel data");
-          throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadRequest);
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest);
         }
       }
       else
       {
-        // http://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_6.5-1
+        /**
+         * DICOM 2017c
+         * http://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_6.1.1.8-3b
+         **/
+        if (type == "image/jpeg" && (transferSyntax.empty() ||  // Default
+                                     transferSyntax == "1.2.840.10008.1.2.4.70"))
+        {
+          return gdcm::TransferSyntax::JPEGLosslessProcess14_1;
+        }
+        else if (type == "image/jpeg" && transferSyntax == "1.2.840.10008.1.2.4.50")
+        {
+          return gdcm::TransferSyntax::JPEGBaselineProcess1;
+        }
+        else if (type == "image/jpeg" && transferSyntax == "1.2.840.10008.1.2.4.51")
+        {
+          return gdcm::TransferSyntax::JPEGExtendedProcess2_4;
+        }
+        else if (type == "image/jpeg" && transferSyntax == "1.2.840.10008.1.2.4.57")
+        {
+          return gdcm::TransferSyntax::JPEGLosslessProcess14;
+        }
+        else if (type == "image/x-dicom-rle" && (transferSyntax.empty() ||  // Default
+                                                 transferSyntax == "1.2.840.10008.1.2.5"))
+        {
+          return gdcm::TransferSyntax::RLELossless;
+        }
+        else if (type == "image/x-jls" && (transferSyntax.empty() ||  // Default
+                                           transferSyntax == "1.2.840.10008.1.2.4.80"))
+        {
+          return gdcm::TransferSyntax::JPEGLSLossless;
+        }
+        else if (type == "image/x-jls" && transferSyntax == "1.2.840.10008.1.2.4.81")
+        {
+          return gdcm::TransferSyntax::JPEGLSNearLossless;
+        }
+        else if (type == "image/jp2" && (transferSyntax.empty() ||  // Default
+                                         transferSyntax == "1.2.840.10008.1.2.4.90"))
+        {
+          return gdcm::TransferSyntax::JPEG2000Lossless;
+        }
+        else if (type == "image/jp2" && transferSyntax == "1.2.840.10008.1.2.4.91")
+        {
+          return gdcm::TransferSyntax::JPEG2000;
+        }
+        else if (type == "image/jpx" && (transferSyntax.empty() ||  // Default
+                                         transferSyntax == "1.2.840.10008.1.2.4.92"))
+        {
+          return gdcm::TransferSyntax::JPEG2000Part2Lossless;
+        }
+        else if (type == "image/jpx" && transferSyntax == "1.2.840.10008.1.2.4.93")
+        {
+          return gdcm::TransferSyntax::JPEG2000Part2;
+        }
+
+
+        /**
+         * Backward compatibility with DICOM 2014a
+         * http://dicom.nema.org/medical/dicom/2014a/output/html/part18.html#table_6.5-1
+         **/
         if (type == "image/dicom+jpeg" && transferSyntax == "1.2.840.10008.1.2.4.50")
         {
           return gdcm::TransferSyntax::JPEGBaselineProcess1;
@@ -160,12 +219,11 @@ static gdcm::TransferSyntax ParseTransferSyntax(const OrthancPluginHttpRequest*
         {
           return gdcm::TransferSyntax::JPEG2000Part2;
         }
-        else
-        {
-          OrthancPlugins::Configuration::LogError("DICOMweb RetrieveFrames: Transfer syntax \"" + 
+
+
+        OrthancPlugins::Configuration::LogError("DICOMweb RetrieveFrames: Transfer syntax \"" + 
                                                   transferSyntax + "\" is incompatible with media type \"" + type + "\"");
-          throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadRequest);
-        }
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest);
       }
     }
   }
@@ -199,7 +257,7 @@ static void ParseFrameList(std::list<unsigned int>& frames,
     if (frame <= 0)
     {
       OrthancPlugins::Configuration::LogError("Invalid frame number (must be > 0): " + tokens[i]);
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
     }
 
     frames.push_back(static_cast<unsigned int>(frame - 1));
@@ -210,46 +268,48 @@ static void ParseFrameList(std::list<unsigned int>& frames,
 
 static const char* GetMimeType(const gdcm::TransferSyntax& syntax)
 {
+  // http://dicom.nema.org/medical/dicom/current/output/html/part18.html#table_6.1.1.8-3b
+  
   switch (syntax)
   {
     case gdcm::TransferSyntax::ImplicitVRLittleEndian:
       return "application/octet-stream";
 
     case gdcm::TransferSyntax::JPEGBaselineProcess1:
-      return "image/dicom+jpeg; transfer-syntax=1.2.840.10008.1.2.4.50";
+      return "image/jpeg; transfer-syntax=1.2.840.10008.1.2.4.50";
 
     case gdcm::TransferSyntax::JPEGExtendedProcess2_4:
-      return "image/dicom+jpeg; transfer-syntax=1.2.840.10008.1.2.4.51";
+      return "image/jpeg; transfer-syntax=1.2.840.10008.1.2.4.51";
     
     case gdcm::TransferSyntax::JPEGLosslessProcess14:
-      return "image/dicom+jpeg; transfer-syntax=1.2.840.10008.1.2.4.57";
+      return "image/jpeg; transfer-syntax=1.2.840.10008.1.2.4.57";
 
     case gdcm::TransferSyntax::JPEGLosslessProcess14_1:
-      return "image/dicom+jpeg; transferSyntax=1.2.840.10008.1.2.4.70";
+      return "image/jpeg; transferSyntax=1.2.840.10008.1.2.4.70";
     
     case gdcm::TransferSyntax::RLELossless:
-      return "image/dicom+rle; transferSyntax=1.2.840.10008.1.2.5";
+      return "image/x-dicom-rle; transferSyntax=1.2.840.10008.1.2.5";
 
     case gdcm::TransferSyntax::JPEGLSLossless:
-      return "image/dicom+jpeg-ls; transferSyntax=1.2.840.10008.1.2.4.80";
+      return "image/x-jls; transferSyntax=1.2.840.10008.1.2.4.80";
 
     case gdcm::TransferSyntax::JPEGLSNearLossless:
-      return "image/dicom+jpeg-ls; transfer-syntax=1.2.840.10008.1.2.4.81";
+      return "image/x-jls; transfer-syntax=1.2.840.10008.1.2.4.81";
 
     case gdcm::TransferSyntax::JPEG2000Lossless:
-      return "image/dicom+jp2; transferSyntax=1.2.840.10008.1.2.4.90";
+      return "image/jp2; transferSyntax=1.2.840.10008.1.2.4.90";
 
     case gdcm::TransferSyntax::JPEG2000:
-      return "image/dicom+jp2; transfer-syntax=1.2.840.10008.1.2.4.91";
+      return "image/jp2; transfer-syntax=1.2.840.10008.1.2.4.91";
 
     case gdcm::TransferSyntax::JPEG2000Part2Lossless:
-      return "image/dicom+jpx; transferSyntax=1.2.840.10008.1.2.4.92";
+      return "image/jpx; transferSyntax=1.2.840.10008.1.2.4.92";
 
     case gdcm::TransferSyntax::JPEG2000Part2:
-      return "image/dicom+jpx; transfer-syntax=1.2.840.10008.1.2.4.93";
+      return "image/jpx; transfer-syntax=1.2.840.10008.1.2.4.93";
 
     default:
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
   }
 }
 
@@ -275,7 +335,7 @@ static void AnswerSingleFrame(OrthancPluginRestOutput* output,
 
   if (error != OrthancPluginErrorCode_Success)
   {
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NetworkProtocol);      
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);      
   }
 }
 
@@ -289,7 +349,7 @@ static bool AnswerFrames(OrthancPluginRestOutput* output,
 {
   if (!dicom.GetDataSet().FindDataElement(OrthancPlugins::DICOM_TAG_PIXEL_DATA))
   {
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_IncompatibleImageFormat);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_IncompatibleImageFormat);
   }
 
   const gdcm::DataElement& pixelData = dicom.GetDataSet().GetDataElement(OrthancPlugins::DICOM_TAG_PIXEL_DATA);
@@ -308,23 +368,24 @@ static bool AnswerFrames(OrthancPluginRestOutput* output,
     if (pixelData.GetByteValue() == NULL)
     {
       OrthancPlugins::Configuration::LogError("Image was not properly decoded");
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);      
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);      
     }
 
-    int width, height, bits;
+    int width, height, bits, samplesPerPixel;
 
     if (!dicom.GetIntegerTag(height, *dictionary_, OrthancPlugins::DICOM_TAG_ROWS) ||
         !dicom.GetIntegerTag(width, *dictionary_, OrthancPlugins::DICOM_TAG_COLUMNS) ||
-        !dicom.GetIntegerTag(bits, *dictionary_, OrthancPlugins::DICOM_TAG_BITS_ALLOCATED))
+        !dicom.GetIntegerTag(bits, *dictionary_, OrthancPlugins::DICOM_TAG_BITS_ALLOCATED) || 
+        !dicom.GetIntegerTag(samplesPerPixel, *dictionary_, OrthancPlugins::DICOM_TAG_SAMPLES_PER_PIXEL))
     {
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
     }
 
-    size_t frameSize = height * width * bits / 8;
+    size_t frameSize = height * width * bits * samplesPerPixel / 8;
     
     if (pixelData.GetByteValue()->GetLength() % frameSize != 0)
     {
-      throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);      
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);      
     }
 
     size_t framesCount = pixelData.GetByteValue()->GetLength() / frameSize;
@@ -348,7 +409,7 @@ static bool AnswerFrames(OrthancPluginRestOutput* output,
       {
         OrthancPlugins::Configuration::LogError("Trying to access frame number " + boost::lexical_cast<std::string>(*frame + 1) + 
                                                 " of an image with " + boost::lexical_cast<std::string>(framesCount) + " frames");
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
       }
       else
       {
@@ -381,7 +442,7 @@ static bool AnswerFrames(OrthancPluginRestOutput* output,
                                                 " of an image with " + 
                                                 boost::lexical_cast<std::string>(fragments->GetNumberOfFragments()) + 
                                                 " frames");
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_ParameterOutOfRange);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
       }
       else
       {
@@ -413,7 +474,7 @@ void RetrieveFrames(OrthancPluginRestOutput* output,
   OrthancPlugins::MemoryBuffer content(context);
   if (LocateInstance(output, uri, request) &&
       content.RestApiGet(uri + "/file", false) &&
-      OrthancPlugins::RestApiGetJson(header, context, uri + "/header?simplify", false))
+      OrthancPlugins::RestApiGet(header, context, uri + "/header?simplify", false))
   {
     {
       std::string s = "DICOMweb RetrieveFrames on " + uri + ", frames: ";
@@ -476,14 +537,14 @@ void RetrieveFrames(OrthancPluginRestOutput* output,
       if (!reader.Read())
       {
         OrthancPlugins::Configuration::LogError("Cannot decode the image");
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadFileFormat);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
       }
 
       change.SetInput(reader.GetImage());
       if (!change.Change())
       {
         OrthancPlugins::Configuration::LogError("Cannot change the transfer syntax of the image");
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_InternalError);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
       }
 
       gdcm::ImageWriter writer;
@@ -494,7 +555,7 @@ void RetrieveFrames(OrthancPluginRestOutput* output,
       writer.SetStream(ss);
       if (!writer.Write())
       {
-        throw OrthancPlugins::PluginException(OrthancPluginErrorCode_NotEnoughMemory);
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotEnoughMemory);
       }
 
       OrthancPlugins::ParsedDicomFile transcoded(ss.str());
diff --git a/Plugin/WadoUri.cpp b/Plugin/WadoUri.cpp
index 3eb13d4..af3f1b6 100644
--- a/Plugin/WadoUri.cpp
+++ b/Plugin/WadoUri.cpp
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -116,7 +117,7 @@ static bool LocateInstance(std::string& instance,
     else
     {
       Json::Value info;
-      if (!OrthancPlugins::RestApiGetJson(info, context, "/instances/" + instance + "/series", false) ||
+      if (!OrthancPlugins::RestApiGet(info, context, "/instances/" + instance + "/series", false) ||
           info["MainDicomTags"]["SeriesInstanceUID"] != seriesUid)
       {
         OrthancPlugins::Configuration::LogError("WADO-URI: Instance " + objectUid + " does not belong to series " + seriesUid);
@@ -136,7 +137,7 @@ static bool LocateInstance(std::string& instance,
     else
     {
       Json::Value info;
-      if (!OrthancPlugins::RestApiGetJson(info, context, "/instances/" + instance + "/study", false) ||
+      if (!OrthancPlugins::RestApiGet(info, context, "/instances/" + instance + "/study", false) ||
           info["MainDicomTags"]["StudyInstanceUID"] != studyUid)
       {
         OrthancPlugins::Configuration::LogError("WADO-URI: Instance " + objectUid + " does not belong to study " + studyUid);
@@ -165,7 +166,7 @@ static void AnswerDicom(OrthancPluginRestOutput* output,
   else
   {
     OrthancPlugins::Configuration::LogError("WADO-URI: Unable to retrieve DICOM file from " + uri);
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_Plugin);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
   }
 }
 
@@ -200,7 +201,7 @@ static void AnswerPngPreview(OrthancPluginRestOutput* output,
   }
   else
   {
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_Plugin);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
   }
 }
 
@@ -214,7 +215,7 @@ static void AnswerJpegPreview(OrthancPluginRestOutput* output,
   OrthancPlugins::MemoryBuffer png(context);
   if (!RetrievePngPreview(png, instance))
   {
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_Plugin);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
   }
   
   OrthancPlugins::OrthancImage image(context);
@@ -237,7 +238,7 @@ void WadoUriCallback(OrthancPluginRestOutput* output,
   std::string contentType = "image/jpg";  // By default, JPEG image will be returned
   if (!LocateInstance(instance, contentType, request))
   {
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_UnknownResource);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
   }
 
   if (contentType == "application/dicom")
@@ -256,6 +257,6 @@ void WadoUriCallback(OrthancPluginRestOutput* output,
   else
   {
     OrthancPlugins::Configuration::LogError("WADO-URI: Unsupported content type: \"" + contentType + "\"");
-    throw OrthancPlugins::PluginException(OrthancPluginErrorCode_BadRequest);
+    throw Orthanc::OrthancException(Orthanc::ErrorCode_BadRequest);
   }
 }
diff --git a/Plugin/WadoUri.h b/Plugin/WadoUri.h
index 1784723..53b3d05 100644
--- a/Plugin/WadoUri.h
+++ b/Plugin/WadoUri.h
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
diff --git a/README b/README
index 347fb6d..fea03a7 100644
--- a/README
+++ b/README
@@ -35,9 +35,8 @@ the "./Status.txt" file.
 Installation and usage
 ----------------------
 
-Build instructions can be found in "./Resources/BuildInstructions.txt".
-
-Usage instructions can be found in "./Usage.txt".
+Build and usage instructions are available in the Orthanc Book:
+https://orthanc.chu.ulg.ac.be/book/plugins/dicomweb.html
 
 
 Licensing: AGPL
diff --git a/Resources/BuildInstructions.txt b/Resources/BuildInstructions.txt
index b02a77f..b63195c 100644
--- a/Resources/BuildInstructions.txt
+++ b/Resources/BuildInstructions.txt
@@ -1,5 +1,5 @@
-Generic Linux (static linking)
-==============================
+Generic GNU/Linux (static linking)
+==================================
 
 # mkdir Build
 # cd Build
@@ -29,8 +29,8 @@ Dynamic linking for Ubuntu 12.10
 # make
 
 
-Cross-compiling for Windows from Linux using MinGW
-==================================================
+Cross-compiling for Windows from GNU/Linux using MinGW
+======================================================
 
 # mkdir Build
 # cd Build
@@ -41,6 +41,7 @@ Cross-compiling for Windows from Linux using MinGW
 Notes
 =====
 
-List the public symbols exported by the shared library under Linux:
+List the public symbols exported by the shared library under
+GNU/Linux:
 
 # nm -C -D --defined-only ./libOrthancDicomWeb.so
diff --git a/Resources/CMake/GdcmConfiguration.cmake b/Resources/CMake/GdcmConfiguration.cmake
index 402e470..7f37199 100644
--- a/Resources/CMake/GdcmConfiguration.cmake
+++ b/Resources/CMake/GdcmConfiguration.cmake
@@ -1,6 +1,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017 Osimis, Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU Affero General Public License
@@ -44,8 +45,9 @@ if (STATIC_BUILD OR NOT USE_SYSTEM_GDCM)
 
   include(ExternalProject)
   externalproject_add(GDCM
-    URL "http://www.montefiore.ulg.ac.be/~jodogne/Orthanc/ThirdPartyDownloads/gdcm-2.6.0.tar.gz"
+    URL "http://www.orthanc-server.com/downloads/third-party/gdcm-2.6.0.tar.gz"
     URL_MD5 "978afe57af448b1c97c9f116790aca9c"
+    TIMEOUT 60
     CMAKE_ARGS -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} ${Flags}
     #-DLIBRARY_OUTPUT_PATH=${CMAKE_CURRENT_BINARY_DIR}
     INSTALL_COMMAND ""  # Skip the install step
diff --git a/Resources/Samples/JavaScript/qido-rs.js b/Resources/Samples/JavaScript/qido-rs.js
index ab7e387..b954d23 100644
--- a/Resources/Samples/JavaScript/qido-rs.js
+++ b/Resources/Samples/JavaScript/qido-rs.js
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
diff --git a/Resources/Samples/JavaScript/stow-rs.js b/Resources/Samples/JavaScript/stow-rs.js
index cc37f84..9a3fe3b 100644
--- a/Resources/Samples/JavaScript/stow-rs.js
+++ b/Resources/Samples/JavaScript/stow-rs.js
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
diff --git a/Resources/Samples/Python/SendStow.py b/Resources/Samples/Python/SendStow.py
index 98c5b18..2547fe0 100755
--- a/Resources/Samples/Python/SendStow.py
+++ b/Resources/Samples/Python/SendStow.py
@@ -3,6 +3,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017 Osimis, Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU Affero General Public License
@@ -30,7 +31,8 @@ import sys
 import json
 import uuid
 
-if len(sys.argv) < 2:
+#if len(sys.argv) < 2:
+if len(sys.argv) < 1:
     print('Usage: %s <StowUri> <file>...' % sys.argv[0])
     print('')
     print('Example: %s http://localhost:8042/dicom-web/studies hello.dcm world.dcm' % sys.argv[0])
@@ -45,9 +47,11 @@ body = bytearray()
 for i in range(2, len(sys.argv)):
     try:
         with open(sys.argv[i], 'rb') as f:
+            content = f.read()
             body += bytearray('--%s\r\n' % boundary, 'ascii')
+            body += bytearray('Content-Length: %d\r\n' % len(content), 'ascii')
             body += bytearray('Content-Type: application/dicom\r\n\r\n', 'ascii')
-            body += f.read()
+            body += content
             body += bytearray('\r\n', 'ascii')
     except:
         print('Ignoring directory %s' % sys.argv[i])
diff --git a/Resources/Samples/Python/WadoRetrieveStudy.py b/Resources/Samples/Python/WadoRetrieveStudy.py
index 25ed702..3e280d6 100755
--- a/Resources/Samples/Python/WadoRetrieveStudy.py
+++ b/Resources/Samples/Python/WadoRetrieveStudy.py
@@ -3,6 +3,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017 Osimis, Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU Affero General Public License
diff --git a/Resources/SyncOrthancFolder.py b/Resources/SyncOrthancFolder.py
index 5ed3021..9756cef 100755
--- a/Resources/SyncOrthancFolder.py
+++ b/Resources/SyncOrthancFolder.py
@@ -25,9 +25,12 @@ FILES = [
     'Core/PrecompiledHeaders.h',
     'Core/Toolbox.cpp',
     'Core/Toolbox.h',
+    'Core/SystemToolbox.h',
+    'Core/SystemToolbox.cpp',
     'Core/WebServiceParameters.cpp',
     'Core/WebServiceParameters.h',
     'Plugins/Samples/Common/ExportedSymbols.list',
+    'Plugins/Samples/Common/OrthancPluginException.h',
     'Plugins/Samples/Common/OrthancPluginCppWrapper.h',
     'Plugins/Samples/Common/OrthancPluginCppWrapper.cpp',
     'Plugins/Samples/Common/VersionScript.map',
diff --git a/Status.txt b/Status.txt
index 3d4fdae..946bea7 100644
--- a/Status.txt
+++ b/Status.txt
@@ -120,3 +120,19 @@ Ignored
 
 * Flag "fuzzymatching"
 * Header "Cache-control"
+
+
+
+==========================================================
+CP 1509 - Refactor media type description for web services
+==========================================================
+
+Not supported.
+
+"There are some significant changes described in CP 1509 to various
+parts of the PS3.18 standard that defines DICOMweb services. [...] The
+most important changes are cleaning up the bulk data media types,
+adding a rendered component to the URL for rendered resources,
+clarifying that compressed bulk data never contains the encapsulation
+item tags, and making JSON support required on the server side and the
+default for query responses." [David Clunie]
diff --git a/UnitTestsSources/UnitTestsMain.cpp b/UnitTestsSources/UnitTestsMain.cpp
index bd26c3e..10434ca 100644
--- a/UnitTestsSources/UnitTestsMain.cpp
+++ b/UnitTestsSources/UnitTestsMain.cpp
@@ -2,6 +2,7 @@
  * Orthanc - A Lightweight, RESTful DICOM Store
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 Osimis, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -40,6 +41,15 @@ TEST(ContentType, Parse)
   ASSERT_EQ(a["type"], "Application/Dicom");
   ASSERT_EQ(a["boundary"], "heLLO");
 
+  // The WADO-RS client must support the case where the WADO-RS server
+  // escapes the "type" subfield in the Content-Type header
+  // cf. https://tools.ietf.org/html/rfc7231#section-3.1.1.1
+  ParseContentType(c, a, "Multipart/Related; TYPE=\"Application/Dicom\"  ;  Boundary=heLLO");
+  ASSERT_EQ(c, "multipart/related");
+  ASSERT_EQ(2u, a.size());
+  ASSERT_EQ(a["type"], "Application/Dicom");
+  ASSERT_EQ(a["boundary"], "heLLO");
+  
   ParseContentType(c, a, "");
   ASSERT_TRUE(c.empty());
   ASSERT_EQ(0u, a.size());  
diff --git a/Usage.txt b/Usage.txt
deleted file mode 100644
index 0f78fe8..0000000
--- a/Usage.txt
+++ /dev/null
@@ -1,284 +0,0 @@
-=============
-Configuration
-=============
-
-(1) You must change the Orthanc configuration file to tell Orthanc
-    where it can find the DICOMweb plugin. This is done by properly
-    modifying the "Plugins" configuration option of Orthanc. For
-    instance, in Linux:
-
-{
-  ...
-  "Plugins" : [
-    "/home/user/OrthancDicomWeb/Build/libOrthancDicomWeb.so"
-  ]
-  ...
-}
-
-    Or in Windows:
-
-{
-  ...
-  "Plugins" : [
-    "c:/Temp/OrthancDicomWeb.dll"
-  ]
-  ...
-}
-
-    Note that the DICOMweb server will share all the parameters of the
-    Orthanc HTTP server, notably wrt. authentication and HTTPS
-    encryption. For this reason, you will most probably have to enable
-    the remote access to the Orthanc HTTP server:
-
-{
-  ...
-  "RemoteAccessEnabled" : true
-  ...
-}
-
-
-(2) There are several configuration options that can be set to
-    fine-tune the Orthanc DICOMweb server. Here is the full list of
-    the available options, all of them must be grouped inside the
-    "DicomWeb" section of the Orthanc configuration file:
-
-{
-  ...
-  "DicomWeb" : {
-    "Enable" : true,          // Whether DICOMweb support is enabled
-    "Root" : "/dicom-web/",   // Root URI of the DICOMweb API (for QIDO-RS, STOW-RS and WADO-RS)
-    "EnableWado" : true,      // Whether WADO-URI (previously known as WADO) support is enabled
-    "WadoRoot" : "/wado",     // Root URI of the WADO-URI (aka. WADO) API
-    "Host" : "localhost",     // Hard-codes the name of the host for subsequent WADO-RS requests
-    "Ssl" : false,            // Whether HTTPS should be used for subsequent WADO-RS requests
-    "StowMaxInstances" : 10,  // For STOW-RS client, the maximum number of instances in one single HTTP query (0 = no limit)
-    "StowMaxSize" : 10        // For STOW-RS client, the maximum size of the body in one single HTTP query (in MB, 0 = no limit)
-  }
-  ...
-}
-
-
-(3) If you want to connect Orthanc as a client to remote DICOMweb
-    servers (cf. below), you need to modify the configuration file so
-    as to define each of them in the option "DicomWeb.Servers".  The
-    syntax is identical to the "OrthancPeers" parameters.
-
-    In the most simple case, here is how to instruct Orthanc about the
-    existence of a password-less DICOMweb server that will be refered
-    to as "sample" in Orthanc:
-
-{
-  ...
-  "DicomWeb" : {
-    "Servers" : {
-      "sample" : [ "http://192.168.1.1/dicom-web/" ]
-    }
-  }
-  ...
-}
-
-    You are of course free to add as many DICOMweb servers as you
-    need. If the DICOMweb server is protected by a password (with HTTP
-    Basic access authentication):
-
-{
-  ...
-  "DicomWeb" : {
-    "Servers" : {
-      "sample" : [ "http://192.168.1.1/dicom-web/", "username", "password" ]
-    }
-  }
-  ...
-}
-
-    If the DICOMweb server is protected with HTTPS client
-    authentication, you must provide your client certificate (in the
-    PEM format), your client private key (in the PEM format), together
-    with the password protecting the private key:
-
-{
-  ...
-  "DicomWeb" : {
-    "Servers" : {
-      "sample" : {
-        "Url" : "http://192.168.1.1/dicom-web/", 
-        "CertificateFile" : "client.crt",
-        "CertificateKeyFile" : "client.key",
-        "CertificateKeyPassword" : "password"
-      }
-    }
-  }
-  ...
-}
-
-    Finally, it is also possible to use client authentication with
-    hardware security modules and smart cards through PKCS#11 (this
-    feature is only available is the core of Orthanc was compiled with
-    the "-DENABLE_PKCS11=ON" option in CMake, and if the Orthanc
-    configuration file has a proper "Pkcs11" section):
-
-{
-  ...
-  "DicomWeb" : {
-    "Servers" : {
-      "sample" : {
-        "Url" : "http://192.168.1.1/dicom-web/", 
-        "Pkcs11" : true
-      }
-    }
-  }
-  ...
-}
-
-    Important remark: When querying a DICOMweb server, Orthanc will
-    automatically use the global configuration options "HttpProxy",
-    "HttpTimeout", "HttpsVerifyPeers", "HttpsCACertificates", and
-    "Pkcs11". Make sure to adapt them if need be.
-
-
-
-=================================
-Querying a remote DICOMweb server
-=================================
-
-Listing the available servers
------------------------------
-
-The list of the remote DICOMweb servers that are known to the DICOMweb
-plugin can be obtained as follows:
-
-# curl http://localhost:8042/dicom-web/servers/
-[ "sample" ]
-
-Here, a single server called "sample" is configured.
-
-
-Making a call to QIDO-RS or WADO-RS
------------------------------------
-
-In Orthanc, the URI "/{dicom-web}/servers/{name}/get" allows to make a
-HTTP GET call against a DICOMweb server. This can be used to issue a
-QIDO-RS or WADO-RS command. Orthanc will take care of properly
-encoding the URL and authenticating the client.
-
-For instance, here is a sample QIDO-RS search to query all the
-studies (using a bash command-line):
-
-# curl http://localhost:8042/dicom-web/servers/sample/get -d @- << EOF
-{
-  "Uri" : "/studies"
-}
-EOF
-
-You do not have to specify the base URL of the remote DICOMweb server,
-as it is encoded in the configuration file.
-
-The result of the command above is a multipart "application/dicom+xml"
-document.  It is possible to request a more human-friendly JSON answer
-by adding the "Accept" HTTP header. Here is how to search for a given
-patient name, while requesting a JSON answer and pretty-printing
-through the "json_pp" command-line tool:
-
-# curl http://localhost:8042/dicom-web/servers/sample/get -d @- << EOF | json_pp 
-{
-  "Uri" : "/studies",
-  "HttpHeaders" : {
-    "Accept" : "application/json"
-  },
-  "Arguments" : {
-    "00100010" : "*JODOGNE*"
-  }
-}
-EOF
-
-Note how all the GET arguments must be specified in the "Arguments"
-field. Orthanc will take care of properly encoding it to a URL.
-
-An user-friendly reference of the features available in QIDO-RS and
-WADO-RS can be found at http://dicomweb.hcintegrations.ca/#/home
-
-
-Sending DICOM resources to a STOW-RS server
--------------------------------------------
-
-STOW-RS allows to send local DICOM resources to a remote DICOMweb
-server. In Orthanc, the STOW-RS client primitive is available at URI
-"/{dicom-web}/servers/{name}/stow". Here is a sample call:
-
-# curl http://localhost:8042/dicom-web/servers/sample/stow -X POST -d @- << EOF
-{
-  "Resources" : [
-    "6ca4c9f3-5e895cb3-4d82c6da-09e060fe-9c59f228"
-  ]
-}
-EOF
-
-Note that this primitive takes as its input a list of Orthanc
-identifiers corresponding to the resources (patients, studies, series
-and/or instances) to be exported:
-https://orthanc.chu.ulg.ac.be/book/faq/orthanc-ids.html
-
-Remark 1: Additional HTTP headers can be added with an optional
-"HttpHeaders" argument, as for QIDO-RS and WADO-RS. This might be
-useful e.g. for cookie-based session management.
-
-Remark 2: One call to this "/stow" primitive will possibly result in
-several HTTP requests to the DICOMweb server, in order to limit the
-size of the HTTP messages. The configuration options
-"DicomWeb.StowMaxInstances" and "DicomWeb.StowMaxSize" can be used to
-tune this behavior (set both options to 0 to send one single request).
-
-
-Retrieving DICOM resources from a WADO-RS server
-------------------------------------------------
-
-Once DICOM resources of interest have been identified through a
-QIDO-RS call to a remote DICOMweb server (cf. above), it is
-interesting to download them locally with a WADO-RS call. You could do
-it manually with a second call to the
-"/{dicom-web}/servers/{name}/get" URI, but Orthanc provides another
-primitive "/retrieve" to automate this process.
-
-Here is how you would download one study, one series and one instance
-whose StudyInstanceUID (0020,000d), SeriesInstanceUID (0020,000e) are
-SOPInstanceUID (0008,0018) have been identified through a former
-QIDO-RS call:
-
-# curl http://localhost:8042/dicom-web/servers/sample/retrieve -X POST -d @- << EOF
-{
-  "Resources" : [
-    {
-      "Study" : "1.3.51.0.1.1.192.168.29.133.1688840.1688819"
-    },
-    {
-      "Study" : "1.3.51.0.1.1.192.168.29.133.1681753.1681732",
-      "Series" : "1.3.12.2.1107.5.2.33.37097.2012041613040617636372171.0.0.0"
-    },
-    {
-      "Study" : "1.3.51.0.1.1.192.168.29.133.1681753.1681732",
-      "Series" : "1.3.12.2.1107.5.2.33.37097.2012041612474981424569674.0.0.0",
-      "Instance" : "1.3.12.2.1107.5.2.33.37097.2012041612485540185869716"
-    }
-  ]
-}
-EOF
-
-Orthanc will reply with the list of the Orthanc identifiers of all the
-DICOM instances that were downloaded from the remote server.
-
-Remark 1: Contrarily to the "/stow" URI that uses Orthanc identifiers,
-the "/retrieve" URI uses DICOM identifiers.
-
-Remark 2: The "HttpArguments" is also available.
-
-
-
-=======
-Samples
-=======
-
-Samples of how to call DICOMweb services from standalone applications
-can be found in the following folders:
-
-- In Python: see ./Resources/Samples/Python/
-- In JavaScript: see ./Resources/Samples/Python/

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/orthanc-dicomweb.git



More information about the debian-med-commit mailing list