changeset 1:d5d3cb00556a

initial release
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 22 Mar 2017 16:13:52 +0100
parents decac5df19c4
children 7ee79efd8b3f
files AUTHORS CMakeLists.txt COPYING NEWS NOTES.txt Plugin/AccessedResource.cpp Plugin/AccessedResource.h Plugin/AssociativeArray.cpp Plugin/AssociativeArray.h Plugin/AuthorizationParserBase.cpp Plugin/AuthorizationParserBase.h Plugin/AuthorizationWebService.cpp Plugin/AuthorizationWebService.h Plugin/CachedAuthorizationService.cpp Plugin/CachedAuthorizationService.h Plugin/DefaultAuthorizationParser.cpp Plugin/DefaultAuthorizationParser.h Plugin/Enumerations.cpp Plugin/Enumerations.h Plugin/IAuthorizationParser.h Plugin/IAuthorizationService.h Plugin/ICache.h Plugin/ICacheFactory.h Plugin/MemoryCache.cpp Plugin/MemoryCache.h Plugin/OrthancResource.cpp Plugin/OrthancResource.h Plugin/Plugin.cpp Plugin/ResourceHierarchyCache.cpp Plugin/ResourceHierarchyCache.h Plugin/Token.cpp Plugin/Token.h README README.md Resources/Orthanc/Core/Cache/LeastRecentlyUsedIndex.h Resources/Orthanc/Core/Endianness.h Resources/Orthanc/Core/Enumerations.cpp Resources/Orthanc/Core/Enumerations.h Resources/Orthanc/Core/Logging.cpp Resources/Orthanc/Core/Logging.h Resources/Orthanc/Core/OrthancException.h Resources/Orthanc/Core/PrecompiledHeaders.cpp Resources/Orthanc/Core/PrecompiledHeaders.h Resources/Orthanc/Core/Toolbox.cpp Resources/Orthanc/Core/Toolbox.h Resources/Orthanc/Plugins/Samples/Common/ExportedSymbols.list Resources/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp Resources/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h Resources/Orthanc/Plugins/Samples/Common/OrthancPluginException.h Resources/Orthanc/Plugins/Samples/Common/VersionScript.map Resources/Orthanc/Resources/CMake/AutoGeneratedCode.cmake Resources/Orthanc/Resources/CMake/BoostConfiguration.cmake Resources/Orthanc/Resources/CMake/Compiler.cmake Resources/Orthanc/Resources/CMake/DownloadPackage.cmake Resources/Orthanc/Resources/CMake/JsonCppConfiguration.cmake Resources/Orthanc/Resources/EmbedResources.py Resources/Orthanc/Resources/MinGW-W64-Toolchain32.cmake Resources/Orthanc/Resources/MinGW-W64-Toolchain64.cmake Resources/Orthanc/Resources/MinGWToolchain.cmake Resources/Orthanc/Resources/ThirdParty/VisualStudio/stdint.h Resources/Orthanc/Resources/ThirdParty/base64/base64.cpp Resources/Orthanc/Resources/ThirdParty/base64/base64.h Resources/Orthanc/Resources/WindowsResources.py Resources/Orthanc/Resources/WindowsResources.rc Resources/Orthanc/Sdk-1.1.0/orthanc/OrthancCPlugin.h Resources/SyncOrthancFolder.py Resources/TestService.js
diffstat 67 files changed, 17472 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/AUTHORS	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,11 @@
+Orthanc for Whole-Slide Imaging
+===============================
+
+
+Authors
+-------
+
+* Osimis <info@osimis.io>
+  Rue des Chasseurs Ardennais 3
+  4031 Liege 
+  Belgium
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CMakeLists.txt	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,132 @@
+# Advanced authorization plugin for Orthanc
+# 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
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Affero General Public License for more details.
+# 
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+cmake_minimum_required(VERSION 2.8)
+
+project(OrthancAuthorization)
+
+set(ORTHANC_AUTHORIZATION_VERSION "mainline")
+
+# Parameters of the build
+set(STATIC_BUILD OFF CACHE BOOL "Static build of the third-party libraries (necessary for Windows)")
+SET(STANDALONE_BUILD ON CACHE BOOL "Standalone build (all the resources are embedded, necessary for releases)")
+set(ALLOW_DOWNLOADS OFF CACHE BOOL "Allow CMake to download packages")
+
+# Advanced parameters to fine-tune linking against system libraries
+set(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost")
+set(USE_SYSTEM_JSONCPP ON CACHE BOOL "Use the system version of JsonCpp")
+set(USE_SYSTEM_ORTHANC_SDK ON CACHE BOOL "Use the system version of the Orthanc plugin SDK")
+
+set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}/Resources/Orthanc)
+set(ORTHANC_DISABLE_PATCH ON)  # No need for the "patch" command-line tool
+include(CheckIncludeFiles)
+include(CheckIncludeFileCXX)
+include(CheckLibraryExists)
+include(FindPythonInterp)
+include(${ORTHANC_ROOT}/Resources/CMake/Compiler.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/AutoGeneratedCode.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake)
+
+include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/JsonCppConfiguration.cmake)
+
+
+# Check that the Orthanc SDK headers are available
+if (STATIC_BUILD OR NOT USE_SYSTEM_ORTHANC_SDK)
+  include_directories(${ORTHANC_ROOT}/Sdk-1.1.0)
+else ()
+  CHECK_INCLUDE_FILE_CXX(orthanc/OrthancCPlugin.h HAVE_ORTHANC_H)
+  if (NOT HAVE_ORTHANC_H)
+    message(FATAL_ERROR "Please install the headers of the Orthanc plugins SDK")
+  endif()
+endif()
+
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
+  link_libraries(rt)
+elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  SET(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -lws2_32")
+
+  execute_process(
+    COMMAND 
+    ${PYTHON_EXECUTABLE} ${ORTHANC_ROOT}/Resources/WindowsResources.py
+    ${ORTHANC_AUTHORIZATION_VERSION} "OrthancAuthorization" OrthancAuthorization.dll "Advanced authorization plugin for Orthanc"
+    ERROR_VARIABLE Failure
+    OUTPUT_FILE ${AUTOGENERATED_DIR}/Version.rc
+    )
+
+  if (Failure)
+    message(FATAL_ERROR "Error while computing the version information: ${Failure}")
+  endif()
+
+  list(APPEND AUTOGENERATED_SOURCES  ${AUTOGENERATED_DIR}/Version.rc)
+endif()
+
+if (APPLE)
+  SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework CoreFoundation")
+  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -framework CoreFoundation")
+endif()
+
+add_definitions(
+  -DHAS_ORTHANC_EXCEPTION=1
+  -DORTHANC_ENABLE_MD5=0
+  -DORTHANC_ENABLE_BASE64=0
+  -DORTHANC_ENABLE_LOGGING=1
+  -DORTHANC_ENABLE_LOGGING_PLUGIN=1
+  -DORTHANC_ENABLE_PUGIXML=0
+  -DORTHANC_SANDBOXED=0
+  )
+
+add_library(OrthancAuthorization SHARED
+  ${CMAKE_SOURCE_DIR}/Plugin/AccessedResource.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/AssociativeArray.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/AuthorizationParserBase.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/AuthorizationWebService.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/CachedAuthorizationService.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/DefaultAuthorizationParser.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/Enumerations.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/MemoryCache.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/OrthancResource.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/Plugin.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/ResourceHierarchyCache.cpp
+  ${CMAKE_SOURCE_DIR}/Plugin/Token.cpp
+
+  ${ORTHANC_ROOT}/Core/Logging.cpp
+  ${ORTHANC_ROOT}/Core/Enumerations.cpp
+  ${ORTHANC_ROOT}/Core/Toolbox.cpp
+  ${ORTHANC_ROOT}/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
+
+  ${BOOST_SOURCES}
+  ${SQLITE_SOURCES}
+  ${JSONCPP_SOURCES}
+  )
+
+
+message("Setting the version of the plugin to ${ORTHANC_AUTHORIZATION_VERSION}")
+add_definitions(-DORTHANC_AUTHORIZATION_VERSION="${ORTHANC_AUTHORIZATION_VERSION}")
+
+set_target_properties(OrthancAuthorization PROPERTIES 
+  VERSION ${ORTHANC_AUTHORIZATION_VERSION} 
+  SOVERSION ${ORTHANC_AUTHORIZATION_VERSION})
+
+install(
+  TARGETS OrthancAuthorization
+  RUNTIME DESTINATION lib    # Destination for Windows
+  LIBRARY DESTINATION share/orthanc/plugins    # Destination for Linux
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/COPYING	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,661 @@
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/NEWS	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,9 @@
+Pending changes in the mainline
+===============================
+
+
+
+2017-03-22
+==========
+
+* Initial release
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/NOTES.txt	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,29 @@
+TODO
+====
+
+- map Orthanc/DICOM ids
+- GET tokens
+
+
+
+
+
+
+  "Authorization" : {
+    "UncheckedResources" : [
+      "/series",
+      "/instances",
+      "/patients",
+      "/studies",
+      "/plugins/explorer.js",
+      "/system"
+    ],
+    "UncheckedFolders" : [
+      "/app/",
+      "/web-viewer/app/",
+      "/web-viewer/libs/",
+      "/wsi/app/"
+    ],
+    "WebService" : "http://localhost:8000/"
+  }
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/AccessedResource.cpp	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,82 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "AccessedResource.h"
+
+#include "../Resources/Orthanc/Core/OrthancException.h"
+
+namespace OrthancPlugins
+{
+  AccessedResource::AccessedResource(AccessLevel level,
+                                     const std::string& orthancId,
+                                     const std::string& dicomUid) :
+    level_(level),
+    orthancId_(orthancId),
+    dicomUid_(dicomUid)
+  {
+    if (level_ == AccessLevel_System &&
+        !dicomUid.empty())
+    {
+      // The "DICOM UID" makes no sense for custom Orthanc URIs
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);        
+    }
+  }
+
+
+  AccessedResource::AccessedResource(Orthanc::ResourceType level,
+                                     const std::string& orthancId,
+                                     const std::string& dicomUid) :
+    orthancId_(orthancId),
+    dicomUid_(dicomUid)
+  {
+    switch (level)
+    {
+      case Orthanc::ResourceType_Patient:
+        level_ = AccessLevel_Patient;
+        break;
+
+      case Orthanc::ResourceType_Study:
+        level_ = AccessLevel_Study;
+        break;
+
+      case Orthanc::ResourceType_Series:
+        level_ = AccessLevel_Series;
+        break;
+
+      case Orthanc::ResourceType_Instance:
+        level_ = AccessLevel_Instance;
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const std::string& AccessedResource::GetDicomUid() const
+  {
+    if (level_ == AccessLevel_System)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);        
+    }
+    else
+    {
+      return dicomUid_;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/AccessedResource.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,53 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "Enumerations.h"
+
+namespace OrthancPlugins
+{
+  class AccessedResource
+  {
+  private:
+    AccessLevel    level_;
+    std::string    orthancId_;
+    std::string    dicomUid_;
+
+  public:
+    AccessedResource(AccessLevel level,
+                     const std::string& orthancId,
+                     const std::string& dicomUid);
+
+    AccessedResource(Orthanc::ResourceType level,
+                     const std::string& orthancId,
+                     const std::string& dicomUid);
+
+    AccessLevel GetLevel() const
+    {
+      return level_;
+    }
+
+    const std::string& GetOrthancId() const
+    {
+      return orthancId_;
+    }
+
+    const std::string& GetDicomUid() const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/AssociativeArray.cpp	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,80 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "AssociativeArray.h"
+
+#include "../Resources/Orthanc/Core/Toolbox.h"
+
+namespace OrthancPlugins
+{
+  AssociativeArray::AssociativeArray(uint32_t headersCount,
+                                     const char *const *headersKeys,
+                                     const char *const *headersValues,
+                                     bool caseSensitive) :
+    caseSensitive_(caseSensitive)
+  {
+    for (uint32_t i = 0; i < headersCount; i++)
+    {
+      std::string value;
+
+      if (caseSensitive)
+      {
+        Orthanc::Toolbox::ToLowerCase(value, headersValues[i]);
+      }
+      else
+      {
+        value = headersValues[i];
+      }
+        
+      map_[headersKeys[i]] = value;
+    }
+  }
+
+    
+  bool AssociativeArray::GetValue(std::string& value,
+                                  const std::string& key) const
+  {
+    if (key.empty())
+    {
+      return false;
+    }
+
+    Map::const_iterator found;
+
+    if (caseSensitive_)
+    {
+      found = map_.find(key);
+    }
+    else
+    {
+      std::string lower;
+      Orthanc::Toolbox::ToLowerCase(lower, key);
+      found = map_.find(lower);
+    }       
+
+    if (found == map_.end())
+    {
+      return false;
+    }
+    else
+    {
+      value = found->second;
+      return true;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/AssociativeArray.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,45 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include <stdint.h>
+#include <boost/noncopyable.hpp>
+#include <map>
+#include <string>
+
+namespace OrthancPlugins
+{
+  class AssociativeArray : public boost::noncopyable
+  {
+  private:
+    typedef std::map<std::string, std::string>  Map;
+
+    Map  map_;
+    bool caseSensitive_;
+
+  public:
+    AssociativeArray(uint32_t headersCount,
+                     const char *const *headersKeys,
+                     const char *const *headersValues,
+                     bool caseSensitive);
+    
+    bool GetValue(std::string& value,
+                  const std::string& key) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/AuthorizationParserBase.cpp	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,151 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "AuthorizationParserBase.h"
+
+#include "../Resources/Orthanc/Core/OrthancException.h"
+
+namespace OrthancPlugins
+{
+  void AuthorizationParserBase::AddResourceInternal(AccessedResources& target,
+                                                    Orthanc::ResourceType level,
+                                                    const std::string& orthancId)
+  {
+    std::string dicomUid;
+
+    if (resourceHierarchy_->LookupDicomUid(dicomUid, level, orthancId))
+    {
+      target.push_back(AccessedResource(level, orthancId, dicomUid));
+    }
+  }
+    
+
+  void AuthorizationParserBase::AddOrthancInstance(AccessedResources& target,
+                                                   const std::string& orthancId)
+  {
+    std::string patient, study, series;
+    if (!resourceHierarchy_->LookupInstance(patient, study, series, orthancId))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }
+
+    AddResourceInternal(target, Orthanc::ResourceType_Patient, patient);
+    AddResourceInternal(target, Orthanc::ResourceType_Study, study);
+    AddResourceInternal(target, Orthanc::ResourceType_Series, series);
+    AddResourceInternal(target, Orthanc::ResourceType_Instance, orthancId);
+  }
+
+  
+  void AuthorizationParserBase::AddOrthancSeries(AccessedResources& target,
+                                                 const std::string& orthancId)
+  {
+    std::string patient, study;
+    if (!resourceHierarchy_->LookupSeries(patient, study, orthancId))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }
+
+    AddResourceInternal(target, Orthanc::ResourceType_Patient, patient);
+    AddResourceInternal(target, Orthanc::ResourceType_Study, study);
+    AddResourceInternal(target, Orthanc::ResourceType_Series, orthancId);
+  }
+
+  
+  void AuthorizationParserBase::AddOrthancStudy(AccessedResources& target,
+                                                const std::string& orthancId)
+  {
+    std::string patient;
+    if (!resourceHierarchy_->LookupStudy(patient, orthancId))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }
+
+    AddResourceInternal(target, Orthanc::ResourceType_Patient, patient);
+    AddResourceInternal(target, Orthanc::ResourceType_Study, orthancId);
+  }
+
+  
+  void AuthorizationParserBase::AddOrthancPatient(AccessedResources& target,
+                                                  const std::string& orthancId)
+  {
+    AddResourceInternal(target, Orthanc::ResourceType_Patient, orthancId);
+  }
+
+  
+  void AuthorizationParserBase::AddDicomStudy(AccessedResources& target,
+                                              const std::string& studyDicomUid)
+  {
+    std::string patient, study;
+
+    if (!resourceHierarchy_->LookupOrthancId(study, Orthanc::ResourceType_Study, studyDicomUid) ||
+        !resourceHierarchy_->LookupStudy(patient, study))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }
+
+    AddResourceInternal(target, Orthanc::ResourceType_Patient, patient);
+    target.push_back(AccessedResource(Orthanc::ResourceType_Study, study, studyDicomUid));
+  }
+
+  
+  void AuthorizationParserBase::AddDicomSeries(AccessedResources& target,
+                                               const std::string& studyDicomUid,
+                                               const std::string& seriesDicomUid)
+  {
+    std::string series;
+
+    if (!resourceHierarchy_->LookupOrthancId(series, Orthanc::ResourceType_Series, seriesDicomUid))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }
+
+    AddDicomStudy(target, studyDicomUid);
+    target.push_back(AccessedResource(Orthanc::ResourceType_Series, series, seriesDicomUid));
+  }
+
+  
+  void AuthorizationParserBase::AddDicomInstance(AccessedResources& target,
+                                                 const std::string& studyDicomUid,
+                                                 const std::string& seriesDicomUid,
+                                                 const std::string& instanceDicomUid)
+  {
+    std::string instance;
+
+    if (!resourceHierarchy_->LookupOrthancId
+        (instance, Orthanc::ResourceType_Instance, instanceDicomUid))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
+    }
+
+    AddDicomSeries(target, studyDicomUid, seriesDicomUid);
+    target.push_back(AccessedResource(Orthanc::ResourceType_Instance, instance, instanceDicomUid));
+  }
+
+  
+  AuthorizationParserBase::AuthorizationParserBase(OrthancPluginContext* context,
+                                                   ICacheFactory& factory)
+  {
+    resourceHierarchy_.reset(new ResourceHierarchyCache(context, factory));
+
+    if (resourceHierarchy_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+}
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/AuthorizationParserBase.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,70 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "IAuthorizationParser.h"
+#include "ResourceHierarchyCache.h"
+
+namespace OrthancPlugins
+{
+  class AuthorizationParserBase : public IAuthorizationParser
+  {
+  private:
+    std::auto_ptr<ResourceHierarchyCache>  resourceHierarchy_;
+
+    void AddResourceInternal(AccessedResources& target,
+                             Orthanc::ResourceType level,
+                             const std::string& orthancId);
+    
+  protected:
+    void AddOrthancInstance(AccessedResources& target,
+                            const std::string& orthancId);
+
+    void AddOrthancSeries(AccessedResources& target,
+                          const std::string& orthancId);
+
+    void AddOrthancStudy(AccessedResources& target,
+                         const std::string& orthancId);
+
+    void AddOrthancPatient(AccessedResources& target,
+                           const std::string& orthancId);
+
+    void AddDicomStudy(AccessedResources& target,
+                       const std::string& studyDicomUid);
+    
+    void AddDicomSeries(AccessedResources& target,
+                        const std::string& studyDicomUid,
+                        const std::string& seriesDicomUid);
+
+    void AddDicomInstance(AccessedResources& target,
+                          const std::string& studyDicomUid,
+                          const std::string& seriesDicomUid,
+                          const std::string& instanceDicomUid);
+
+  public:
+    AuthorizationParserBase(OrthancPluginContext* context,
+                            ICacheFactory& factory);
+
+    virtual void Invalidate(Orthanc::ResourceType level,
+                            const std::string& id)
+    {
+      resourceHierarchy_->Invalidate(level, id);
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/AuthorizationWebService.cpp	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,157 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "AuthorizationWebService.h"
+
+#include "../Resources/Orthanc/Core/Logging.h"
+#include "../Resources/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h"
+
+namespace OrthancPlugins
+{
+  bool AuthorizationWebService::IsGrantedInternal(unsigned int& validity,
+                                                  OrthancPluginHttpMethod method,
+                                                  const AccessedResource& access,
+                                                  const Token* token,
+                                                  const std::string& tokenValue)
+  {
+    Json::Value body = Json::objectValue;
+      
+    switch (method)
+    {
+      case OrthancPluginHttpMethod_Get:
+        body["method"] ="get";
+        break;
+          
+      case OrthancPluginHttpMethod_Post:
+        body["method"] ="post";
+        break;
+          
+      case OrthancPluginHttpMethod_Put:
+        body["method"] ="put";
+        break;
+          
+      case OrthancPluginHttpMethod_Delete:
+        body["method"] ="delete";
+        break;
+          
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    body["level"] = EnumerationToString(access.GetLevel());
+
+    if (access.GetLevel() == AccessLevel_System)
+    {
+      body["uri"] = access.GetOrthancId();
+    }
+    else
+    {
+      body["orthanc-id"] = access.GetOrthancId();
+      body["dicom-uid"] = access.GetDicomUid();
+    }
+
+    if (token != NULL)
+    {
+      body["token-key"] = token->GetKey();
+      body["token-value"] = tokenValue;
+    }
+
+    MemoryBuffer answerBody(context_);
+    MemoryBuffer answerHeaders(context_);
+    uint16_t httpStatus = 0;
+
+    uint32_t headersCount = 0;
+    const char* headersKeys[1];
+    const char* headersValues[1];
+      
+    if (token != NULL &&
+        token->GetType() == TokenType_HttpHeader)
+    {
+      // If the token source is a HTTP header, forward it also as a
+      // HTTP header
+      headersCount = 1;
+      headersKeys[0] = token->GetKey().c_str();
+      headersValues[0] = tokenValue.c_str();
+    }
+
+    std::string flatBody = body.toStyledString();
+      
+    if (OrthancPluginHttpClient(context_, *answerBody, *answerHeaders,
+                                &httpStatus, OrthancPluginHttpMethod_Post,
+                                url_.c_str(), headersCount, headersKeys, headersValues,
+                                flatBody.c_str(), flatBody.size(),
+                                username_.empty() ? NULL : username_.c_str(),
+                                password_.empty() ? NULL : password_.c_str(),
+                                10 /* timeout */, NULL, NULL, NULL, 0)
+        != OrthancPluginErrorCode_Success)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);        
+    }
+
+    Json::Value answer;
+    answerBody.ToJson(answer);
+
+    static const char* GRANTED = "granted";
+    static const char* VALIDITY = "validity";
+      
+    if (answer.type() != Json::objectValue ||
+        !answer.isMember(GRANTED) ||
+        answer[GRANTED].type() != Json::booleanValue ||
+        (answer.isMember(VALIDITY) &&
+         answer[VALIDITY].type() != Json::intValue))
+    {
+      LOG(ERROR) << "Syntax error in the result of the Web service";
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);
+    }
+
+    validity = 0;
+    if (answer.isMember(VALIDITY))
+    {
+      int tmp = answer[VALIDITY].asInt();
+      if (tmp < 0)
+      {
+        LOG(ERROR) << "A validity duration cannot be negative";
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_NetworkProtocol);          
+      }
+
+      validity = static_cast<unsigned int>(tmp);
+    }
+
+    return answer[GRANTED].asBool();
+  }
+    
+
+  AuthorizationWebService::AuthorizationWebService(OrthancPluginContext* context,
+                                                   const std::string& url) :
+    context_(context),
+    url_(url)
+  {
+    if (context_ == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+    
+  void AuthorizationWebService::SetCredentials(const std::string& username,
+                                               const std::string& password)
+  {
+    username_ = username;
+    password_ = password;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/AuthorizationWebService.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,62 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "IAuthorizationService.h"
+
+namespace OrthancPlugins
+{
+  class AuthorizationWebService : public IAuthorizationService
+  {
+  private:
+    OrthancPluginContext* context_;
+    std::string url_;
+    std::string username_;
+    std::string password_;
+
+    bool IsGrantedInternal(unsigned int& validity,
+                           OrthancPluginHttpMethod method,
+                           const AccessedResource& access,
+                           const Token* token,
+                           const std::string& tokenValue);
+    
+  public:
+    AuthorizationWebService(OrthancPluginContext* context,
+                            const std::string& url);
+
+    void SetCredentials(const std::string& username,
+                        const std::string& password);
+
+    virtual bool IsGranted(unsigned int& validity,
+                           OrthancPluginHttpMethod method,
+                           const AccessedResource& access,
+                           const Token& token,
+                           const std::string& tokenValue)
+    {
+      return IsGrantedInternal(validity, method, access, &token, tokenValue);
+    }
+    
+    virtual bool IsGranted(unsigned int& validity,
+                           OrthancPluginHttpMethod method,
+                           const AccessedResource& access)
+    {
+      return IsGrantedInternal(validity, method, access, NULL, "");
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/CachedAuthorizationService.cpp	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,99 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "CachedAuthorizationService.h"
+
+#include "../Resources/Orthanc/Core/OrthancException.h"
+
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancPlugins
+{
+  std::string CachedAuthorizationService::ComputeKey(OrthancPluginHttpMethod method,
+                                                     const AccessedResource& access,
+                                                     const Token& token,
+                                                     const std::string& tokenValue) const
+  {
+    return (boost::lexical_cast<std::string>(method) + "|" +
+            boost::lexical_cast<std::string>(access.GetLevel()) + "|" +
+            access.GetOrthancId() + "|" + token.GetKey() + "|" + tokenValue);
+  }
+    
+
+  CachedAuthorizationService::CachedAuthorizationService(IAuthorizationService* decorated /* takes ownership */,
+                                                         ICacheFactory& factory) :
+    decorated_(decorated),
+    cache_(factory.Create())
+  {
+    if (decorated_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+
+  bool CachedAuthorizationService::IsGranted(unsigned int& validity,
+                                             OrthancPluginHttpMethod method,
+                                             const AccessedResource& access,
+                                             const Token& token,
+                                             const std::string& tokenValue)
+  {
+    assert(decorated_.get() != NULL);
+
+    std::string key = ComputeKey(method, access, token, tokenValue);
+    std::string value;
+
+    if (cache_->Retrieve(value, key))
+    {
+      // Return the previously cached value
+      return (value == "1");
+    }        
+        
+    bool granted = decorated_->IsGranted(validity, method, access, token, tokenValue);
+
+    if (granted)
+    {
+      if (validity > 0)
+      {
+        cache_->Store(key, "1", validity);
+      }
+        
+      return true;
+    }
+    else
+    {
+      if (validity > 0)
+      {
+        cache_->Store(key, "0", validity);
+      }
+        
+      return false;
+    }
+  }
+
+  
+  bool CachedAuthorizationService::IsGranted(unsigned int& validity,
+                                             OrthancPluginHttpMethod method,
+                                             const AccessedResource& access)
+  {
+    assert(decorated_.get() != NULL);
+
+    // The cache is not used if no token is available
+    return decorated_->IsGranted(validity, method, access);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/CachedAuthorizationService.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,56 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "IAuthorizationService.h"
+#include "ICacheFactory.h"
+
+#include <memory>
+
+namespace OrthancPlugins
+{
+  /**
+   * Decorator design pattern to add a cache around an IAuthorizationService
+   **/
+  class CachedAuthorizationService : public IAuthorizationService
+  {
+  private:
+    std::auto_ptr<IAuthorizationService>  decorated_;
+    std::auto_ptr<ICache>   cache_;
+
+    std::string ComputeKey(OrthancPluginHttpMethod method,
+                           const AccessedResource& access,
+                           const Token& token,
+                           const std::string& tokenValue) const;
+    
+  public:
+    CachedAuthorizationService(IAuthorizationService* decorated /* takes ownership */,
+                               ICacheFactory& factory);
+
+    virtual bool IsGranted(unsigned int& validity,
+                           OrthancPluginHttpMethod method,
+                           const AccessedResource& access,
+                           const Token& token,
+                           const std::string& tokenValue);
+    
+    virtual bool IsGranted(unsigned int& validity,
+                           OrthancPluginHttpMethod method,
+                           const AccessedResource& access);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/DefaultAuthorizationParser.cpp	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,129 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "DefaultAuthorizationParser.h"
+
+#include "../Resources/Orthanc/Core/OrthancException.h"
+
+namespace OrthancPlugins
+{
+  DefaultAuthorizationParser::DefaultAuthorizationParser(OrthancPluginContext* context,
+                                                         ICacheFactory& factory,
+                                                         const std::string& dicomWebRoot) :
+    AuthorizationParserBase(context, factory),
+    resourcesPattern_("^/(patients|studies|series|instances)/([a-f0-9-]+)(|/.*)$"),
+    seriesPattern_("^/(web-viewer/series|web-viewer/is-stable-series|wsi/pyramids|wsi/tiles)/([a-f0-9-]+)(|/.*)$"),
+    instancesPattern_("^/web-viewer/instances/[a-z0-9]+-([a-f0-9-]+)_[0-9]+$")
+  {
+    std::string tmp = dicomWebRoot;
+    while (!tmp.empty() &&
+           tmp[tmp.size() - 1] == '/')
+    {
+      tmp = tmp.substr(0, tmp.size() - 1);
+    }
+
+    dicomWebStudies_ = boost::regex(
+      "^" + tmp + "/studies/([.0-9]+)(|/series)(|/)$");
+      
+    dicomWebSeries_ = boost::regex(
+      "^" + tmp + "/studies/([.0-9]+)/series/([.0-9]+)(|/instances)(|/)$");
+      
+    dicomWebInstances_ = boost::regex(
+      "^" + tmp + "/studies/([.0-9]+)/series/([.0-9]+)/instances/([.0-9]+)(|/|/frames/.*)$");
+  }
+
+
+  bool DefaultAuthorizationParser::Parse(AccessedResources& target,
+                                         const std::string& uri)
+  {
+    // The mutex below should not be necessary, but we prefer to
+    // ensure thread safety in boost::regex
+    boost::mutex::scoped_lock lock(mutex_);
+
+    boost::smatch what;
+
+    if (boost::regex_match(uri, what, resourcesPattern_))
+    {
+      AccessLevel level = StringToAccessLevel(what[1]);
+
+      switch (level)
+      {
+        case AccessLevel_Instance:
+          AddOrthancInstance(target, what[2]);
+          break;
+
+        case AccessLevel_Series:
+          AddOrthancSeries(target, what[2]);
+          break;
+
+        case AccessLevel_Study:
+          AddOrthancStudy(target, what[2]);
+          break;
+
+        case AccessLevel_Patient:
+          AddOrthancPatient(target, what[2]);
+          break;
+
+        default:
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+      }
+
+      return true;
+    }
+    else if (boost::regex_match(uri, what, seriesPattern_))
+    {
+      AddOrthancSeries(target, what[2]);
+      return true;
+    }
+    else if (boost::regex_match(uri, what, instancesPattern_))
+    {
+      AddOrthancInstance(target, what[1]);
+      return true;
+    }
+    else if (boost::regex_match(uri, what, dicomWebStudies_))
+    {
+      AddDicomStudy(target, what[1]);
+      return true;
+    }
+    else if (boost::regex_match(uri, what, dicomWebSeries_))
+    {
+      AddDicomSeries(target, what[1], what[2]);
+      return true;
+    }
+    else if (boost::regex_match(uri, what, dicomWebInstances_))
+    {
+      AddDicomInstance(target, what[1], what[2], what[3]);
+      return true;
+    }
+    else
+    {
+      // Unknown type of resource: Consider it as a system access
+
+      // Remove the trailing slashes if need be
+      std::string s = uri;
+      while (!s.empty() &&
+             s[s.length() - 1] == '/')
+      {
+        s = s.substr(0, s.length() - 1);
+      }
+          
+      target.push_back(AccessedResource(AccessLevel_System, s, ""));
+      return true;
+    }        
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/DefaultAuthorizationParser.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,47 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "AuthorizationParserBase.h"
+
+#include <boost/regex.hpp>
+#include <boost/thread/mutex.hpp>
+
+namespace OrthancPlugins
+{
+  class DefaultAuthorizationParser : public AuthorizationParserBase
+  { 
+  private:
+    boost::mutex mutex_; 
+    boost::regex resourcesPattern_;
+    boost::regex seriesPattern_;
+    boost::regex instancesPattern_;
+    boost::regex dicomWebStudies_;
+    boost::regex dicomWebSeries_;
+    boost::regex dicomWebInstances_;
+
+  public:
+    DefaultAuthorizationParser(OrthancPluginContext* context,
+                               ICacheFactory& factory,
+                               const std::string& dicomWebRoot);
+
+    virtual bool Parse(AccessedResources& target,
+                       const std::string& uri);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/Enumerations.cpp	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,85 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "Enumerations.h"
+
+#include "../Resources/Orthanc/Core/OrthancException.h"
+#include "../Resources/Orthanc/Core/Toolbox.h"
+
+namespace OrthancPlugins
+{
+  std::string EnumerationToString(AccessLevel level)
+  {
+    switch (level)
+    {
+      case AccessLevel_Patient:
+        return "patient";
+        
+      case AccessLevel_Study:
+        return "study";
+        
+      case AccessLevel_Series:
+        return "series";
+        
+      case AccessLevel_Instance:
+        return "instance";
+        
+      case AccessLevel_System:
+        return "system";
+        
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  AccessLevel StringToAccessLevel(const std::string& level)
+  {
+    std::string tmp;
+    Orthanc::Toolbox::ToLowerCase(tmp, level);
+
+    if (tmp == "patient" ||
+        tmp == "patients")
+    {
+      return AccessLevel_Patient;
+    }
+    else if (tmp == "study" ||
+             tmp == "studies")
+    {
+      return AccessLevel_Study;
+    }
+    else if (tmp == "series")
+    {
+      return AccessLevel_Series;
+    }
+    else if (tmp == "instance" ||
+             tmp == "instances")
+    {
+      return AccessLevel_Instance;
+    }
+    else if (tmp == "system")
+    {
+      return AccessLevel_System;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/Enumerations.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,45 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "../Resources/Orthanc/Core/Enumerations.h"
+
+namespace OrthancPlugins
+{
+  enum TokenType
+  {
+    TokenType_HttpHeader,
+    TokenType_GetArgument
+  };
+
+
+  enum AccessLevel
+  {
+    AccessLevel_Patient,
+    AccessLevel_Study,
+    AccessLevel_Series,
+    AccessLevel_Instance,
+    AccessLevel_System
+  };
+
+
+  std::string EnumerationToString(AccessLevel level);
+
+  AccessLevel StringToAccessLevel(const std::string& level);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/IAuthorizationParser.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,44 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "AccessedResource.h"
+
+#include <boost/noncopyable.hpp>
+#include <list>
+
+namespace OrthancPlugins
+{
+  // NOTE: This interface must be thread-safe 
+  class IAuthorizationParser : public boost::noncopyable
+  {
+  public:
+    typedef std::list<AccessedResource>  AccessedResources;
+    
+    virtual ~IAuthorizationParser()
+    {
+    }
+
+    virtual void Invalidate(Orthanc::ResourceType level,
+                            const std::string& id) = 0;
+
+    virtual bool Parse(AccessedResources& target,
+                       const std::string& uri) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/IAuthorizationService.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,47 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "AccessedResource.h"
+#include "Token.h"
+
+#include <orthanc/OrthancCPlugin.h>
+#include <boost/noncopyable.hpp>
+
+namespace OrthancPlugins
+{
+  // NOTE: This interface must be thread-safe
+  class IAuthorizationService : public boost::noncopyable
+  {
+  public:
+    virtual ~IAuthorizationService()
+    {
+    }
+    
+    virtual bool IsGranted(unsigned int& validity /* out */,
+                           OrthancPluginHttpMethod method,
+                           const AccessedResource& access,
+                           const Token& token,
+                           const std::string& tokenValue) = 0;
+    
+    virtual bool IsGranted(unsigned int& validity /* out */,
+                           OrthancPluginHttpMethod method,
+                           const AccessedResource& access) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/ICache.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,42 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include <boost/noncopyable.hpp>
+#include <string>
+
+namespace OrthancPlugins
+{
+  class ICache : public boost::noncopyable
+  {
+  public:
+    virtual ~ICache()
+    {
+    }
+
+    virtual void Store(const std::string& key,
+                       const std::string& value,
+                       unsigned int validity /* in seconds */) = 0;
+
+    virtual void Invalidate(const std::string& key) = 0;
+
+    virtual bool Retrieve(std::string& value,
+                          const std::string& key) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/ICacheFactory.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,34 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "ICache.h"
+
+namespace OrthancPlugins
+{
+  class ICacheFactory : public boost::noncopyable
+  {
+  public:
+    virtual ~ICacheFactory()
+    {
+    }
+
+    virtual ICache *Create() = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/MemoryCache.cpp	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,161 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "MemoryCache.h"
+
+namespace OrthancPlugins
+{
+  class MemoryCache::Payload : public boost::noncopyable
+  {
+  private:
+    bool                     hasExpiration_;
+    boost::posix_time::ptime expiration_;
+    std::string              value_;
+
+  public:
+    Payload(const std::string& value,
+            unsigned int validity) :
+      value_(value)
+    {
+      if (validity == 0)
+      {
+        hasExpiration_ = false;
+      }
+      else
+      {
+        hasExpiration_ = true;
+        expiration_ = (boost::posix_time::second_clock::local_time() +
+                       boost::posix_time::seconds(validity));
+      }
+    }
+
+    bool HasExpired() const
+    {
+      return (hasExpiration_ &&
+              boost::posix_time::second_clock::local_time() >= expiration_);
+    }
+      
+    bool GetValue(std::string& target) const
+    {
+      if (HasExpired())
+      {
+        return false;
+      }
+      else
+      {
+        target = value_;
+        return true;          
+      }
+    }
+  };
+    
+   
+  void MemoryCache::RemoveOldest()
+  {
+    Payload* payload = NULL;
+    index_.RemoveOldest(payload);
+    assert(payload != NULL);
+    delete payload;
+  }
+
+  
+  void MemoryCache::InvalidateInternal(const std::string& key)
+  {
+    Payload* payload = NULL;
+
+    if (index_.Contains(key, payload))
+    {
+      assert(payload != NULL);
+      delete payload;
+      index_.Invalidate(key);
+    }
+  }
+    
+
+  MemoryCache::MemoryCache(unsigned int maxSize) :
+    maxSize_(maxSize)
+  {
+    if (maxSize_ == 0)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+    
+  MemoryCache::~MemoryCache()
+  {
+    while (!index_.IsEmpty())
+    {
+      RemoveOldest();
+    }
+  }
+    
+
+  void MemoryCache::Invalidate(const std::string& key)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    InvalidateInternal(key);
+  }
+
+
+  void MemoryCache::Store(const std::string& key,
+                          const std::string& value,
+                          unsigned int expiration /* in seconds */)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    InvalidateInternal(key);
+
+    if (index_.GetSize() == maxSize_)
+    {
+      // The cache is full: Make some room
+      RemoveOldest();
+    }
+        
+    index_.Add(key, new Payload(value, expiration));
+  }
+
+
+  bool MemoryCache::Retrieve(std::string& value,
+                             const std::string& key)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    Payload* payload = NULL;
+      
+    if (!index_.Contains(key, payload))
+    {
+      return false;
+    }
+      
+    assert(payload != NULL);
+      
+    if (payload->GetValue(value))
+    {
+      index_.MakeMostRecent(key);
+      return true;
+    }
+    else
+    {
+      // The value has expired in the cache: Invalidate it
+      delete payload;
+      index_.Invalidate(key);
+      return false;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/MemoryCache.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,75 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "ICacheFactory.h"
+#include "../Resources/Orthanc/Core/Cache/LeastRecentlyUsedIndex.h"
+
+#include <boost/thread/mutex.hpp>
+
+namespace OrthancPlugins
+{
+  class MemoryCache : public ICache
+  {
+  public:
+    class Factory : public ICacheFactory
+    {
+    private:
+      unsigned int   maxSize_;
+      
+    public:
+      Factory(unsigned int maxSize) :
+        maxSize_(maxSize)
+      {
+      }
+      
+      virtual ICache *Create()
+      {
+        return new MemoryCache(maxSize_);
+      }
+    };
+    
+  private:
+    class Payload;
+    
+    typedef Orthanc::LeastRecentlyUsedIndex<std::string, Payload*>  Index;
+
+    boost::mutex  mutex_;
+    unsigned int  maxSize_;
+    Index         index_;
+   
+    void RemoveOldest();
+
+    void InvalidateInternal(const std::string& key);
+    
+  public:
+    MemoryCache(unsigned int maxSize);
+    
+    ~MemoryCache();
+
+    virtual void Invalidate(const std::string& key);
+
+    virtual void Store(const std::string& key,
+                       const std::string& value,
+                       unsigned int expiration /* in seconds */);
+
+    virtual bool Retrieve(std::string& value,
+                          const std::string& key);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/OrthancResource.cpp	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,286 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "OrthancResource.h"
+
+#include "../Resources/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h"
+
+namespace OrthancPlugins
+{
+  void OrthancResource::GetDicomUidInternal(std::string& result,
+                                            Orthanc::ResourceType level,
+                                            const Json::Value& content)
+  {
+    std::string uidTag;
+        
+    switch (level)
+    {
+      case Orthanc::ResourceType_Patient:
+        uidTag = "PatientID";
+        break;
+
+      case Orthanc::ResourceType_Study:
+        uidTag = "StudyInstanceUID";
+        break;
+
+      case Orthanc::ResourceType_Series:
+        uidTag = "SeriesInstanceUID";
+        break;
+
+      case Orthanc::ResourceType_Instance:
+        uidTag = "SOPInstanceUID";
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    static const char* MAIN_DICOM_TAGS = "MainDicomTags";
+      
+    if (content.type() != Json::objectValue ||
+        !content.isMember(MAIN_DICOM_TAGS))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+    }
+
+    const Json::Value& mainDicomTags = content[MAIN_DICOM_TAGS];
+    if (mainDicomTags.type() != Json::objectValue ||
+        (mainDicomTags.isMember(uidTag) &&
+         mainDicomTags[uidTag].type() != Json::stringValue))
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+    }
+
+    if (!mainDicomTags.isMember(uidTag))
+    {
+      result.clear();
+    }
+    else
+    {
+      result = mainDicomTags[uidTag].asString();
+    }
+  }
+
+
+  Orthanc::ResourceType OrthancResource::GetLevel() const
+  {
+    if (IsValid())
+    {
+      return level_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+  
+
+  const std::string& OrthancResource::GetIdentifier() const
+  {
+    if (IsValid())
+    {
+      return id_;
+    }
+    else
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+  }
+
+  
+  bool OrthancResource::GetContent(Json::Value& content,
+                                   OrthancPluginContext* context) const
+  {
+    if (!IsValid())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
+    }
+
+    std::string uri;
+    switch (level_)
+    {
+      case Orthanc::ResourceType_Patient:
+        uri = "patients";
+        break;
+
+      case Orthanc::ResourceType_Study:
+        uri = "studies";
+        break;
+
+      case Orthanc::ResourceType_Series:
+        uri = "series";
+        break;
+
+      case Orthanc::ResourceType_Instance:
+        uri = "instances";
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    uri = "/" + uri + "/" + id_;
+        
+    return RestApiGet(content, context, uri, false /* ignore plugins */);
+  }
+  
+
+  bool OrthancResource::GetDicomUid(std::string& dicomUid /* out */,
+                                    OrthancPluginContext* context) const
+  {
+    Json::Value content;
+        
+    if (!GetContent(content, context))
+    {
+      return false;
+    }
+    else
+    {
+      GetDicomUidInternal(dicomUid, level_, content);
+      return true;
+    }
+  }
+  
+    
+  bool OrthancResource::GetHierarchy(std::string& dicomUid /* out */,
+                                     OrthancResource& parent /* out */,
+                                     std::list<OrthancResource>& children /* out */,
+                                     OrthancPluginContext* context) const
+  {
+    Json::Value content;
+        
+    if (!GetContent(content, context))
+    {
+      return false;
+    }
+
+    std::string parentKey, childrenKey;
+        
+    switch (level_)
+    {
+      case Orthanc::ResourceType_Patient:
+        childrenKey = "Studies";
+        break;
+
+      case Orthanc::ResourceType_Study:
+        parentKey = "ParentPatient";
+        childrenKey = "Series";
+        break;
+
+      case Orthanc::ResourceType_Series:
+        parentKey = "ParentStudy";
+        childrenKey = "Instances";
+        break;
+
+      case Orthanc::ResourceType_Instance:
+        parentKey = "ParentSeries";
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    GetDicomUidInternal(dicomUid, level_, content);
+      
+    if (content.type() != Json::objectValue)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+    }
+
+    if (!parentKey.empty())
+    {
+      if (!content.isMember(parentKey) ||
+          content[parentKey].type() != Json::stringValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+      }
+
+      parent = OrthancResource(Orthanc::GetParentResourceType(level_),
+                               content[parentKey].asString());
+    }
+
+    children.clear();
+        
+    if (!childrenKey.empty())
+    {
+      if (!content.isMember(childrenKey) ||
+          content[childrenKey].type() != Json::arrayValue)
+      {
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+      }
+
+      Orthanc::ResourceType childrenType = Orthanc::GetChildResourceType(level_);
+
+      for (Json::Value::ArrayIndex i = 0; i < content[childrenKey].size(); i++)
+      {
+        const Json::Value& child = content[childrenKey][i];
+
+        if (child.type() != Json::stringValue)
+        {
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+        }
+
+        children.push_back(OrthancResource(childrenType, child.asString()));
+      }
+    }
+        
+    return true;
+  }
+
+
+  bool OrthancResource::LookupOrthancId(std::string& result,
+                                        OrthancPluginContext* context,
+                                        Orthanc::ResourceType level,
+                                        const std::string& dicomUid)
+  {
+    OrthancString s(context);
+
+    switch (level)
+    {
+      case Orthanc::ResourceType_Patient:
+        s.Assign(OrthancPluginLookupPatient(context, dicomUid.c_str()));
+        break;
+
+      case Orthanc::ResourceType_Study:
+        s.Assign(OrthancPluginLookupStudy(context, dicomUid.c_str()));
+        break;
+
+      case Orthanc::ResourceType_Series:
+        s.Assign(OrthancPluginLookupSeries(context, dicomUid.c_str()));
+        break;
+
+      case Orthanc::ResourceType_Instance:
+        s.Assign(OrthancPluginLookupInstance(context, dicomUid.c_str()));
+        break;
+
+      default:
+        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+
+    if (s.GetContent() == NULL)
+    {
+      // Inexisting resource
+      return false;
+    }
+    else
+    {
+      result.assign(s.GetContent());
+      return true;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/OrthancResource.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,79 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "Enumerations.h"
+
+#include <orthanc/OrthancCPlugin.h>
+#include <json/value.h>
+#include <list>
+
+namespace OrthancPlugins
+{
+  class OrthancResource
+  {
+  private:
+    bool                  isValid_;
+    Orthanc::ResourceType level_;
+    std::string           id_;
+
+    static void GetDicomUidInternal(std::string& result,
+                                    Orthanc::ResourceType level,
+                                    const Json::Value& content);
+
+  public:
+    OrthancResource() :
+      isValid_(false)
+    {
+    }
+    
+    OrthancResource(Orthanc::ResourceType level,
+                    const std::string& id) :
+      isValid_(true),
+      level_(level),
+      id_(id)
+    {
+    }
+
+    bool IsValid() const
+    {
+      return isValid_;
+    }
+
+    Orthanc::ResourceType GetLevel() const;
+
+    const std::string& GetIdentifier() const;
+
+    bool GetContent(Json::Value& content,
+                    OrthancPluginContext* context) const;
+
+    bool GetDicomUid(std::string& dicomUid /* out */,
+                     OrthancPluginContext* context) const;
+    
+    bool GetHierarchy(std::string& dicomUid /* out */,
+                      OrthancResource& parent /* out */,
+                      std::list<OrthancResource>& children /* out */,
+                      OrthancPluginContext* context) const;
+
+    static bool LookupOrthancId(std::string& result,
+                                OrthancPluginContext* context,
+                                Orthanc::ResourceType level,
+                                const std::string& dicomUid);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/Plugin.cpp	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,389 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "AssociativeArray.h"
+#include "DefaultAuthorizationParser.h"
+#include "CachedAuthorizationService.h"
+#include "AuthorizationWebService.h"
+#include "MemoryCache.h"
+
+#include "../Resources/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h"
+#include "../Resources/Orthanc/Core/Logging.h"
+#include "../Resources/Orthanc/Core/Toolbox.h"
+
+static OrthancPluginContext* context_ = NULL;
+
+
+// Configuration of the authorization plugin
+static std::auto_ptr<OrthancPlugins::IAuthorizationParser> authorizationParser_;
+static std::auto_ptr<OrthancPlugins::IAuthorizationService> authorizationService_;
+static std::set<std::string> uncheckedResources_;
+static std::list<std::string> uncheckedFolders_;
+static std::list<OrthancPlugins::Token> tokens_;
+static std::set<OrthancPlugins::AccessLevel> uncheckedLevels_;
+
+
+static int32_t FilterHttpRequests(OrthancPluginHttpMethod method,
+                                  const char *uri,
+                                  const char *ip,
+                                  uint32_t headersCount,
+                                  const char *const *headersKeys,
+                                  const char *const *headersValues,
+                                  uint32_t getArgumentsCount,
+                                  const char *const *getArgumentsKeys,
+                                  const char *const *getArgumentsValues)
+{
+  try
+  {
+    if (method == OrthancPluginHttpMethod_Get)
+    {
+      // Allow GET accesses to static resources
+      if (uncheckedResources_.find(uri) != uncheckedResources_.end())
+      {
+        return 1;
+      }
+
+      for (std::list<std::string>::const_iterator
+             it = uncheckedFolders_.begin(); it != uncheckedFolders_.end(); ++it)
+      {
+        if (Orthanc::Toolbox::StartsWith(uri, *it))
+        {
+          return 1;
+        }
+      }
+    }
+
+    if (authorizationParser_.get() != NULL &&
+        authorizationService_.get() != NULL)
+    {
+      // Parse the resources that are accessed through this URI
+      OrthancPlugins::IAuthorizationParser::AccessedResources accesses;
+      if (!authorizationParser_->Parse(accesses, uri))
+      {
+        return 0;  // Unable to parse this URI
+      }
+
+      // Loop over all the accessed resources to ensure access is
+      // granted to each of them
+      for (OrthancPlugins::IAuthorizationParser::AccessedResources::const_iterator
+             access = accesses.begin(); access != accesses.end(); ++access)
+      {
+        // Ignored the access levels that are unchecked
+        // (cf. "UncheckedLevels" option)
+        if (uncheckedLevels_.find(access->GetLevel()) == uncheckedLevels_.end())
+        {
+          LOG(INFO) << "Testing whether access to "
+                    << OrthancPlugins::EnumerationToString(access->GetLevel())
+                    << " \"" << access->GetOrthancId() << "\" is allowed";
+
+          bool granted = false;
+          unsigned int validity;  // ignored
+
+          if (tokens_.empty())
+          {
+            granted = authorizationService_->IsGranted(validity, method, *access);
+          }
+          else
+          {
+            OrthancPlugins::AssociativeArray headers
+              (headersCount, headersKeys, headersValues, false);
+
+            OrthancPlugins::AssociativeArray getArguments
+              (getArgumentsCount, getArgumentsKeys, getArgumentsValues, true);
+
+            // Loop over all the authorization tokens stored in the HTTP
+            // headers, until finding one that is granted
+            for (std::list<OrthancPlugins::Token>::const_iterator
+                   token = tokens_.begin(); token != tokens_.end(); ++token)
+            {
+              std::string value;
+
+              bool hasValue = false;
+              switch (token->GetType())
+              {
+                case OrthancPlugins::TokenType_HttpHeader:
+                  hasValue = headers.GetValue(value, token->GetKey());
+                  break;
+
+                case OrthancPlugins::TokenType_GetArgument:
+                  hasValue = getArguments.GetValue(value, token->GetKey());
+                  break;
+
+                default:
+                  throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+              }
+              
+              if (hasValue &&
+                  authorizationService_->IsGranted(validity, method, *access, *token, value))
+              {
+                granted = true;
+                break;
+              }
+            }
+          }
+
+          if (!granted)
+          {
+            return 0;
+          }
+        }
+      }
+
+      // Access is granted to all the resources
+      return 1;
+    }
+      
+    // By default, forbid access to all the resources
+    return 0;
+  }
+  catch (std::runtime_error& e)
+  {
+    LOG(ERROR) << e.what();
+    return OrthancPluginErrorCode_Success;  // Ignore error
+  }
+  catch (Orthanc::OrthancException& e)
+  {
+    LOG(ERROR) << e.What();
+    return OrthancPluginErrorCode_Success;  // Ignore error
+  }
+  catch (...)
+  {
+    LOG(ERROR) << "Unhandled internal exception";
+    return OrthancPluginErrorCode_Success;  // Ignore error
+  }
+}
+
+  
+#if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 1)
+static int32_t FilterHttpRequestsFallback(OrthancPluginHttpMethod method,
+                                          const char *uri,
+                                          const char *ip,
+                                          uint32_t headersCount,
+                                          const char *const *headersKeys,
+                                          const char *const *headersValues)
+{
+  // Fallback wrapper function for Orthanc <= 1.2.0, where the GET
+  // arguments were not available in the HTTP filters
+  return FilterHttpRequests(method, uri, ip,
+                            headersCount, headersKeys, headersValues,
+                            0, NULL, NULL);
+}
+#endif
+
+
+static OrthancPluginErrorCode OnChangeCallback(OrthancPluginChangeType changeType,
+                                               OrthancPluginResourceType resourceType,
+                                               const char* resourceId)
+{
+  try
+  {
+    if (authorizationParser_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+    
+    if (changeType == OrthancPluginChangeType_Deleted)
+    {
+      switch (resourceType)
+      {
+        case OrthancPluginResourceType_Patient:
+          authorizationParser_->Invalidate(Orthanc::ResourceType_Patient, resourceId);
+          break;
+
+        case OrthancPluginResourceType_Study:
+          authorizationParser_->Invalidate(Orthanc::ResourceType_Study, resourceId);
+          break;
+
+        case OrthancPluginResourceType_Series:
+          authorizationParser_->Invalidate(Orthanc::ResourceType_Series, resourceId);
+          break;
+
+        case OrthancPluginResourceType_Instance:
+          authorizationParser_->Invalidate(Orthanc::ResourceType_Instance, resourceId);
+          break;
+
+        default:
+          break;
+      }
+    }
+       
+    return OrthancPluginErrorCode_Success;
+  }
+  catch (std::runtime_error& e)
+  {
+    LOG(ERROR) << e.what();
+    return OrthancPluginErrorCode_Success;  // Ignore error
+  }
+  catch (Orthanc::OrthancException& e)
+  {
+    LOG(ERROR) << e.What();
+    return OrthancPluginErrorCode_Success;  // Ignore error
+  }
+  catch (...)
+  {
+    LOG(ERROR) << "Unhandled internal exception";
+    return OrthancPluginErrorCode_Success;  // Ignore error
+  }
+}
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
+  {
+    context_ = context;
+    OrthancPluginLogWarning(context_, "Initializing the authorization plugin");
+
+    /* Check the version of the Orthanc core */
+    if (OrthancPluginCheckVersion(context_) == 0)
+    {
+      OrthancPlugins::ReportMinimalOrthancVersion(context_, 
+                                                  ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER,
+                                                  ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER,
+                                                  ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER);
+      return -1;
+    }
+
+    Orthanc::Logging::Initialize(context_);
+    OrthancPluginSetDescription(context_, "Advanced authorization plugin for Orthanc.");
+
+    try
+    {
+      OrthancPlugins::OrthancConfiguration general(context_);
+
+      static const char* SECTION = "Authorization";
+      if (general.IsSection(SECTION))
+      {
+        OrthancPlugins::OrthancConfiguration configuration;
+        general.GetSection(configuration, "Authorization");
+
+        // TODO - The size of the caches is set to 10,000 items. Maybe add a configuration option?
+        OrthancPlugins::MemoryCache::Factory factory(10000);
+
+        {
+          std::string root;
+
+          if (configuration.IsSection("DicomWeb"))
+          {
+            OrthancPlugins::OrthancConfiguration dicomWeb;
+            dicomWeb.GetSection(configuration, "DicomWeb");
+            root = dicomWeb.GetStringValue("Root", "");
+          }
+
+          if (root.empty())
+          {
+            root = "/dicom-web/";
+          } 
+
+          authorizationParser_.reset
+            (new OrthancPlugins::DefaultAuthorizationParser(context_, factory, root));
+        }
+
+        std::list<std::string> tmp;
+
+        configuration.LookupListOfStrings(tmp, "TokenHttpHeaders", true);
+        for (std::list<std::string>::const_iterator
+               it = tmp.begin(); it != tmp.end(); ++it)
+        {
+          tokens_.push_back(OrthancPlugins::Token(OrthancPlugins::TokenType_HttpHeader, *it));
+        }
+
+        configuration.LookupListOfStrings(tmp, "TokenGetArguments", true);
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 1)  
+        for (std::list<std::string>::const_iterator
+               it = tmp.begin(); it != tmp.end(); ++it)
+        {
+          tokens_.push_back(OrthancPlugins::Token(OrthancPlugins::TokenType_GetArgument, *it));
+        }
+#else
+        if (!tmp.empty())
+        {
+          LOG(ERROR) << "The option \"TokenGetArguments\" of the authorization plugin "
+                     << "is only valid if compiled against Orthanc >= 1.2.1";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
+        }
+#endif
+
+        configuration.LookupSetOfStrings(uncheckedResources_, "UncheckedResources", false);
+        configuration.LookupListOfStrings(uncheckedFolders_, "UncheckedFolders", false);
+
+        std::string url;
+
+        static const char* WEB_SERVICE = "WebService";
+        if (!configuration.LookupStringValue(url, WEB_SERVICE))
+        {
+          LOG(ERROR) << "Missing mandatory option \"" << WEB_SERVICE
+                     << "\" for the authorization plugin";
+          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);          
+        }
+
+        if (configuration.LookupListOfStrings(tmp, "UncheckedLevels", false))
+        {
+          for (std::list<std::string>::const_iterator
+                 it = tmp.begin(); it != tmp.end(); ++it)
+          {
+            uncheckedLevels_.insert(OrthancPlugins::StringToAccessLevel(*it));
+          }
+        }
+
+        authorizationService_.reset
+          (new OrthancPlugins::CachedAuthorizationService
+           (new OrthancPlugins::AuthorizationWebService(context_, url), factory));
+
+        OrthancPluginRegisterOnChangeCallback(context_, OnChangeCallback);
+        
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 2, 1)
+        OrthancPluginRegisterIncomingHttpRequestFilter2(context_, FilterHttpRequests);
+#else
+        OrthancPluginRegisterIncomingHttpRequestFilter(context_, FilterHttpRequestsFallback);
+#endif
+      }
+      else
+      {
+        LOG(WARNING) << "No section \"" << SECTION << "\" in the configuration file, "
+                     << "the authorization plugin is disabled";
+      }
+    }
+    catch (Orthanc::OrthancException& e)
+    {
+      LOG(ERROR) << e.What();
+      return -1;
+    }
+    
+    return 0;
+  }
+
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+    authorizationParser_.reset(NULL);
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return "authorization";
+  }
+
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return ORTHANC_AUTHORIZATION_VERSION;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/ResourceHierarchyCache.cpp	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,200 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "ResourceHierarchyCache.h"
+
+#include "../Resources/Orthanc/Core/Logging.h"
+#include "../Resources/Orthanc/Core/OrthancException.h"
+
+#include <boost/lexical_cast.hpp>
+
+namespace OrthancPlugins
+{
+  std::string ResourceHierarchyCache::ComputeKey(Orthanc::ResourceType level,
+                                                 const std::string identifier) const
+  {
+    return boost::lexical_cast<std::string>(level) + "|" + identifier;
+  }
+    
+
+  void ResourceHierarchyCache::LinkParent(const OrthancResource& child,
+                                          const OrthancResource& parent)
+  {
+    LOG(INFO) << "Linking " << Orthanc::EnumerationToString(child.GetLevel())
+              << " \"" << child.GetIdentifier() << "\" to its parent "
+              << Orthanc::EnumerationToString(parent.GetLevel())
+              << " \"" << parent.GetIdentifier() << "\"";
+        
+    cache_->Store(ComputeKey(child), parent.GetIdentifier(), 0 /* no expiration */);
+  }
+
+
+  bool ResourceHierarchyCache::LookupParent(std::string& target,
+                                            const OrthancResource& resource)
+  {
+    std::string key = ComputeKey(resource);
+      
+    if (cache_->Retrieve(target, key))
+    {
+      // The parent was already stored in the cache
+      return true;
+    }
+
+    // Not in the cache, reading the resource from the Orthanc store
+    std::string dicomUid;
+    OrthancResource parent;
+    std::list<OrthancResource> children;
+
+    if (!resource.GetHierarchy(dicomUid, parent, children, context_))
+    {
+      // The resource is non-existing (*)
+      return false;
+    }
+
+    orthancToDicom_->Store(key, dicomUid, 0 /* no expiration */);
+    dicomToOrthanc_->Store(ComputeKey(resource.GetLevel(), dicomUid),
+                           resource.GetIdentifier(), 0 /* no expiration */);
+
+    for (std::list<OrthancResource>::const_iterator
+           it = children.begin(); it != children.end(); ++it)
+    {
+      // Cache the relation of the resource with its children
+      LinkParent(*it, resource);
+    }
+
+    if (parent.IsValid())
+    {
+      LinkParent(resource, parent);
+      target = parent.GetIdentifier();
+      return true;
+    }
+    else
+    {
+      // We reached the patient level, or the resource was removed
+      // from Orthanc since (*)
+      return false;
+    }
+  }
+
+
+  ResourceHierarchyCache::ResourceHierarchyCache(OrthancPluginContext* context,
+                                                 ICacheFactory& factory) :
+    context_(context),
+    cache_(factory.Create()),
+    orthancToDicom_(factory.Create()),
+    dicomToOrthanc_(factory.Create())
+  {
+    if (cache_.get() == NULL)
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
+    }
+  }
+
+  
+  void ResourceHierarchyCache::Invalidate(Orthanc::ResourceType level,
+                                          const std::string& identifier)
+  {
+    LOG(INFO) << "Invalidating " << Orthanc::EnumerationToString(level)
+              << " resource with ID: " << identifier;
+
+    std::string key = ComputeKey(level, identifier);
+    cache_->Invalidate(key);
+    orthancToDicom_->Invalidate(key);
+  }
+
+
+  bool ResourceHierarchyCache::LookupSeries(std::string& patient,
+                                            std::string& study,
+                                            const std::string& series)
+  {
+    if (LookupParent(study, Orthanc::ResourceType_Series, series))
+    {
+      return LookupStudy(patient, study);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  
+  bool ResourceHierarchyCache::LookupInstance(std::string& patient,
+                                              std::string& study,
+                                              std::string& series,
+                                              const std::string& instance)
+  {
+    if (LookupParent(series, Orthanc::ResourceType_Instance, instance))
+    {
+      return LookupSeries(patient, study, series);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool ResourceHierarchyCache::LookupDicomUid(std::string& target,
+                                              Orthanc::ResourceType level,
+                                              const std::string& orthancId)
+  {
+    std::string key = ComputeKey(level, orthancId);
+
+    if (orthancToDicom_->Retrieve(target, key))
+    {
+      return true;
+    }
+
+    OrthancResource resource(level, orthancId);
+
+    if (resource.GetDicomUid(target, context_))
+    {
+      orthancToDicom_->Store(key, target, 0 /* no expiration */);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  
+  bool ResourceHierarchyCache::LookupOrthancId(std::string& target,
+                                               Orthanc::ResourceType level,
+                                               const std::string& dicomUid)
+  {
+    std::string key = ComputeKey(level, dicomUid);
+
+    if (dicomToOrthanc_->Retrieve(target, key))
+    {
+      return true;
+    }
+
+    OrthancResource resource(level, dicomUid);
+
+    if (OrthancResource::LookupOrthancId(target, context_, level, dicomUid))
+    {
+      dicomToOrthanc_->Store(key, target, 0 /* no expiration */);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/ResourceHierarchyCache.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,89 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "ICacheFactory.h"
+#include "Enumerations.h"
+#include "OrthancResource.h"
+
+#include <orthanc/OrthancCPlugin.h>
+#include <memory>
+
+namespace OrthancPlugins
+{
+  class ResourceHierarchyCache : public boost::noncopyable
+  {
+  private:
+    OrthancPluginContext   *context_;
+    std::auto_ptr<ICache>   cache_;   // Maps resources to their parents
+    std::auto_ptr<ICache>   orthancToDicom_;
+    std::auto_ptr<ICache>   dicomToOrthanc_;
+
+    std::string ComputeKey(Orthanc::ResourceType level,
+                           const std::string identifier) const;
+    
+    std::string ComputeKey(const OrthancResource& resource) const
+    {
+      return ComputeKey(resource.GetLevel(), resource.GetIdentifier());
+    }
+
+    void LinkParent(const OrthancResource& child,
+                    const OrthancResource& parent);
+
+    bool LookupParent(std::string& target,
+                      const OrthancResource& resource);
+
+    bool LookupParent(std::string& target,
+                      Orthanc::ResourceType level,
+                      const std::string& identifier)
+    {
+      return LookupParent(target, OrthancResource(level, identifier));
+    }
+
+  public:
+    ResourceHierarchyCache(OrthancPluginContext* context,
+                           ICacheFactory& factory);
+
+    void Invalidate(Orthanc::ResourceType level,
+                    const std::string& identifier);
+
+    bool LookupStudy(std::string& patient,
+                     const std::string& study)
+    {
+      return LookupParent(patient, Orthanc::ResourceType_Study, study);
+    }
+
+    bool LookupSeries(std::string& patient,
+                      std::string& study,
+                      const std::string& series);
+
+    bool LookupInstance(std::string& patient,
+                        std::string& study,
+                        std::string& series,
+                        const std::string& instance);
+
+    bool LookupDicomUid(std::string& target,
+                        Orthanc::ResourceType level,
+                        const std::string& orthancId);
+
+    bool LookupOrthancId(std::string& target,
+                         Orthanc::ResourceType level,
+                         const std::string& dicomUid);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/Token.cpp	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,35 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#include "Token.h"
+
+#include "../Resources/Orthanc/Core/OrthancException.h"
+
+namespace OrthancPlugins
+{ 
+  Token::Token(TokenType type,
+               const std::string& key) :
+    type_(type),
+    key_(key)
+  {
+    if (key.empty())
+    {
+      throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugin/Token.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,45 @@
+/**
+ * Advanced authorization plugin for Orthanc
+ * 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
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Affero General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+#pragma once
+
+#include "Enumerations.h"
+
+namespace OrthancPlugins
+{ 
+  class Token
+  {
+  private:
+    TokenType   type_;
+    std::string key_;
+
+  public:
+    Token(TokenType type,
+          const std::string& key);
+
+    TokenType GetType() const
+    {
+      return type_;
+    }
+
+    const std::string& GetKey() const
+    {
+      return key_;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,48 @@
+Advanced authorization for Orthanc
+==================================
+
+
+General Information
+-------------------
+
+This repository contains the source code of an advanced authorization
+plugin for Orthanc.
+
+For each incoming REST request to some URI, the plugin will query a
+Web service to know whether the access is granted to the
+user. Authorization credentials can be retrieved either from a GET
+argument, or from a HTTP header.
+
+
+Installation and usage
+----------------------
+
+Build instructions are similar to that of Orthanc:
+https://orthanc.chu.ulg.ac.be/book/faq/compiling.html
+
+Usage details are available as part of the Orthanc Book:
+http://book.orthanc-server.com/plugins/authorization.html
+
+
+Licensing
+---------
+
+The authorization plugin for Orthanc is licensed under the AGPL license.
+
+We also kindly ask scientific works and clinical studies that make
+use of Orthanc to cite Orthanc in their associated publications.
+Similarly, we ask open-source and closed-source products that make
+use of Orthanc to warn us about this use. You can cite our work
+using the following BibTeX entry:
+
+@inproceedings{Jodogne:ISBI2013,
+  author = {Jodogne, S. and Bernard, C. and Devillers, M. and Lenaerts, E. and Coucke, P.},
+  title = {Orthanc -- {A} Lightweight, {REST}ful {DICOM} Server for Healthcare and Medical Research},
+  booktitle={Biomedical Imaging ({ISBI}), {IEEE} 10th International Symposium on}, 
+  year={2013}, 
+  pages={190-193}, 
+  ISSN={1945-7928},
+  month=apr,
+  url={http://ieeexplore.ieee.org/xpl/articleDetails.jsp?tp=&arnumber=6556444},
+  address={San Francisco, {CA}, {USA}}
+}
--- a/README.md	Tue Feb 14 12:55:35 2017 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-authorization plugin ...
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Cache/LeastRecentlyUsedIndex.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,347 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 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
+
+#include <list>
+#include <map>
+#include <boost/noncopyable.hpp>
+#include <cassert>
+
+#include "../OrthancException.h"
+#include "../Toolbox.h"
+
+namespace Orthanc
+{
+  /**
+   * This class implements the index of a cache with least recently
+   * used (LRU) recycling policy. All the items of the cache index
+   * can be associated with a payload.
+   * Reference: http://stackoverflow.com/a/2504317
+   **/
+  template <typename T, typename Payload = NullType>
+  class LeastRecentlyUsedIndex : public boost::noncopyable
+  {
+  private:
+    typedef std::list< std::pair<T, Payload> >  Queue;
+    typedef std::map<T, typename Queue::iterator>  Index;
+
+    Index  index_;
+    Queue  queue_;
+
+    /**
+     * Internal method for debug builds to check whether the internal
+     * data structures are not corrupted.
+     **/
+    void CheckInvariants() const;
+
+  public:
+    /**
+     * Add a new element to the cache index, and make it the most
+     * recent element.
+     * \param id The ID of the element.
+     * \param payload The payload of the element.
+     **/
+    void Add(T id, Payload payload = Payload());
+
+    void AddOrMakeMostRecent(T id, Payload payload = Payload());
+
+    /**
+     * When accessing an element of the cache, this method tags the
+     * element as the most recently used.
+     * \param id The most recently accessed item.
+     **/
+    void MakeMostRecent(T id);
+
+    void MakeMostRecent(T id, Payload updatedPayload);
+
+    /**
+     * Remove an element from the cache index.
+     * \param id The item to remove.
+     **/
+    Payload Invalidate(T id);
+
+    /**
+     * Get the oldest element in the cache and remove it.
+     * \return The oldest item.
+     **/
+    T RemoveOldest();
+
+    /**
+     * Get the oldest element in the cache, remove it and return the
+     * associated payload.
+     * \param payload Where to store the associated payload.
+     * \return The oldest item.
+     **/
+    T RemoveOldest(Payload& payload);
+
+    /**
+     * Check whether an element is contained in the cache.
+     * \param id The item.
+     * \return \c true iff the item is indexed by the cache.
+     **/
+    bool Contains(T id) const
+    {
+      return index_.find(id) != index_.end();
+    }
+
+    bool Contains(T id, Payload& payload) const
+    {
+      typename Index::const_iterator it = index_.find(id);
+      if (it == index_.end())
+      {
+        return false;
+      }
+      else
+      {
+        payload = it->second->second;
+        return true;
+      }
+    }
+
+    /**
+     * Return the number of elements in the cache.
+     * \return The number of elements.
+     **/
+    size_t GetSize() const
+    {
+      assert(index_.size() == queue_.size());
+      return queue_.size();
+    }
+
+    /**
+     * Check whether the cache index is empty.
+     * \return \c true iff the cache is empty.
+     **/
+    bool IsEmpty() const
+    {
+      return index_.empty();
+    }
+
+    const T& GetOldest() const;
+    
+    const Payload& GetOldestPayload() const;
+  };
+
+
+
+
+  /******************************************************************
+   ** Implementation of the template
+   ******************************************************************/
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::CheckInvariants() const
+  {
+#ifndef NDEBUG
+    assert(index_.size() == queue_.size());
+
+    for (typename Index::const_iterator 
+           it = index_.begin(); it != index_.end(); it++)
+    {
+      assert(it->second != queue_.end());
+      assert(it->second->first == it->first);
+    }
+#endif
+  }
+
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::Add(T id, Payload payload)
+  {
+    if (Contains(id))
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    queue_.push_front(std::make_pair(id, payload));
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::MakeMostRecent(T id)
+  {
+    if (!Contains(id))
+    {
+      throw OrthancException(ErrorCode_InexistentItem);
+    }
+
+    typename Index::iterator it = index_.find(id);
+    assert(it != index_.end());
+
+    std::pair<T, Payload> item = *(it->second);
+    
+    queue_.erase(it->second);
+    queue_.push_front(item);
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::AddOrMakeMostRecent(T id, Payload payload)
+  {
+    typename Index::iterator it = index_.find(id);
+
+    if (it != index_.end())
+    {
+      // Already existing. Make it most recent.
+      std::pair<T, Payload> item = *(it->second);
+      item.second = payload;
+      queue_.erase(it->second);
+      queue_.push_front(item);
+    }
+    else
+    {
+      // New item
+      queue_.push_front(std::make_pair(id, payload));
+    }
+
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  void LeastRecentlyUsedIndex<T, Payload>::MakeMostRecent(T id, Payload updatedPayload)
+  {
+    if (!Contains(id))
+    {
+      throw OrthancException(ErrorCode_InexistentItem);
+    }
+
+    typename Index::iterator it = index_.find(id);
+    assert(it != index_.end());
+
+    std::pair<T, Payload> item = *(it->second);
+    item.second = updatedPayload;
+    
+    queue_.erase(it->second);
+    queue_.push_front(item);
+    index_[id] = queue_.begin();
+
+    CheckInvariants();
+  }
+
+
+  template <typename T, typename Payload>
+  Payload LeastRecentlyUsedIndex<T, Payload>::Invalidate(T id)
+  {
+    if (!Contains(id))
+    {
+      throw OrthancException(ErrorCode_InexistentItem);
+    }
+
+    typename Index::iterator it = index_.find(id);
+    assert(it != index_.end());
+
+    Payload payload = it->second->second;
+    queue_.erase(it->second);
+    index_.erase(it);
+
+    CheckInvariants();
+    return payload;
+  }
+
+
+  template <typename T, typename Payload>
+  T LeastRecentlyUsedIndex<T, Payload>::RemoveOldest(Payload& payload)
+  {
+    if (IsEmpty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    std::pair<T, Payload> item = queue_.back();
+    T oldest = item.first;
+    payload = item.second;
+
+    queue_.pop_back();
+    assert(index_.find(oldest) != index_.end());
+    index_.erase(oldest);
+
+    CheckInvariants();
+
+    return oldest;
+  }
+
+
+  template <typename T, typename Payload>
+  T LeastRecentlyUsedIndex<T, Payload>::RemoveOldest()
+  {
+    if (IsEmpty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    std::pair<T, Payload> item = queue_.back();
+    T oldest = item.first;
+
+    queue_.pop_back();
+    assert(index_.find(oldest) != index_.end());
+    index_.erase(oldest);
+
+    CheckInvariants();
+
+    return oldest;
+  }
+
+
+  template <typename T, typename Payload>
+  const T& LeastRecentlyUsedIndex<T, Payload>::GetOldest() const
+  {
+    if (IsEmpty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    return queue_.back().first;
+  }
+
+
+  template <typename T, typename Payload>
+  const Payload& LeastRecentlyUsedIndex<T, Payload>::GetOldestPayload() const
+  {
+    if (IsEmpty())
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    return queue_.back().second;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Endianness.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,158 @@
+/**
+ * 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
+
+
+/********************************************************************
+ ** LINUX ARCHITECTURES
+ ********************************************************************/
+
+#if defined(__linux__)
+#  define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1
+#  include <endian.h>
+#endif
+
+
+/********************************************************************
+ ** WINDOWS ARCHITECTURES
+ **
+ ** On Windows x86, "host" will always be little-endian ("le").
+ ********************************************************************/
+
+#if defined(_WIN32)
+#  if defined(_MSC_VER)
+//   Visual Studio - http://msdn.microsoft.com/en-us/library/a3140177.aspx
+#    define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1
+#    define be16toh(x) _byteswap_ushort(x)
+#    define be32toh(x) _byteswap_ulong(x)
+#    define be64toh(x) _byteswap_uint64(x)
+#  elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))
+//   MinGW >= 4.3 - Use builtin intrinsic for byte swapping
+#    define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1
+#    define be16toh(x) __builtin_bswap16(x)
+#    define be32toh(x) __builtin_bswap32(x)
+#    define be64toh(x) __builtin_bswap64(x)
+#  else
+//   MinGW <= 4.2, we must manually implement the byte swapping
+#    define ORTHANC_HAS_BUILTIN_BYTE_SWAP 0
+#    define be16toh(x) __orthanc_bswap16(x)
+#    define be32toh(x) __orthanc_bswap32(x)
+#    define be64toh(x) __orthanc_bswap64(x)
+#  endif
+
+#  define htobe16(x) be16toh(x)
+#  define htobe32(x) be32toh(x)
+#  define htobe64(x) be64toh(x)
+
+#  define htole16(x) x
+#  define htole32(x) x
+#  define htole64(x) x
+
+#  define le16toh(x) x
+#  define le32toh(x) x
+#  define le64toh(x) x
+#endif
+
+
+/********************************************************************
+ ** FREEBSD ARCHITECTURES
+ ********************************************************************/
+
+#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+#  define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1
+#  include <arpa/inet.h>
+#endif
+
+
+/********************************************************************
+ ** APPLE ARCHITECTURES (including OS X)
+ ********************************************************************/
+
+#if defined(__APPLE__)
+#  define ORTHANC_HAS_BUILTIN_BYTE_SWAP 1
+#  include <libkern/OSByteOrder.h>
+#  define be16toh(x) OSSwapBigToHostInt16(x)
+#  define be32toh(x) OSSwapBigToHostInt32(x)
+#  define be64toh(x) OSSwapBigToHostInt64(x)
+
+#  define htobe16(x) OSSwapHostToBigInt16(x)
+#  define htobe32(x) OSSwapHostToBigInt32(x)
+#  define htobe64(x) OSSwapHostToBigInt64(x)
+
+#  define htole16(x) OSSwapHostToLittleInt16(x)
+#  define htole32(x) OSSwapHostToLittleInt32(x)
+#  define htole64(x) OSSwapHostToLittleInt64(x)
+
+#  define le16toh(x) OSSwapLittleToHostInt16(x)
+#  define le32toh(x) OSSwapLittleToHostInt32(x)
+#  define le64toh(x) OSSwapLittleToHostInt64(x)
+#endif
+
+
+/********************************************************************
+ ** PORTABLE (BUT SLOW) IMPLEMENTATION OF BYTE-SWAPPING
+ ********************************************************************/
+
+#if ORTHANC_HAS_BUILTIN_BYTE_SWAP != 1
+
+#include <stdint.h>
+
+static inline uint16_t __orthanc_bswap16(uint16_t a)
+{
+  return (a << 8) | (a >> 8);
+}
+
+static inline uint32_t __orthanc_bswap32(uint32_t a)
+{
+  const uint8_t* p = reinterpret_cast<const uint8_t*>(&a);
+  return (static_cast<uint32_t>(p[0]) << 24 |
+          static_cast<uint32_t>(p[1]) << 16 |
+          static_cast<uint32_t>(p[2]) << 8 |
+          static_cast<uint32_t>(p[3]));
+}
+
+static inline uint64_t __orthanc_bswap64(uint64_t a)
+{
+  const uint8_t* p = reinterpret_cast<const uint8_t*>(&a);
+  return (static_cast<uint64_t>(p[0]) << 56 |
+          static_cast<uint64_t>(p[1]) << 48 |
+          static_cast<uint64_t>(p[2]) << 40 |
+          static_cast<uint64_t>(p[3]) << 32 |
+          static_cast<uint64_t>(p[4]) << 24 |
+          static_cast<uint64_t>(p[5]) << 16 |
+          static_cast<uint64_t>(p[6]) << 8 |
+          static_cast<uint64_t>(p[7]));
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Enumerations.cpp	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,1405 @@
+/**
+ * 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 "Enumerations.h"
+
+#include "OrthancException.h"
+#include "Toolbox.h"
+#include "Logging.h"
+
+#include <string.h>
+#include <cassert>
+
+namespace Orthanc
+{
+  // This function is autogenerated by the script
+  // "Resources/GenerateErrorCodes.py"
+  const char* EnumerationToString(ErrorCode error)
+  {
+    switch (error)
+    {
+      case ErrorCode_InternalError:
+        return "Internal error";
+
+      case ErrorCode_Success:
+        return "Success";
+
+      case ErrorCode_Plugin:
+        return "Error encountered within the plugin engine";
+
+      case ErrorCode_NotImplemented:
+        return "Not implemented yet";
+
+      case ErrorCode_ParameterOutOfRange:
+        return "Parameter out of range";
+
+      case ErrorCode_NotEnoughMemory:
+        return "The server hosting Orthanc is running out of memory";
+
+      case ErrorCode_BadParameterType:
+        return "Bad type for a parameter";
+
+      case ErrorCode_BadSequenceOfCalls:
+        return "Bad sequence of calls";
+
+      case ErrorCode_InexistentItem:
+        return "Accessing an inexistent item";
+
+      case ErrorCode_BadRequest:
+        return "Bad request";
+
+      case ErrorCode_NetworkProtocol:
+        return "Error in the network protocol";
+
+      case ErrorCode_SystemCommand:
+        return "Error while calling a system command";
+
+      case ErrorCode_Database:
+        return "Error with the database engine";
+
+      case ErrorCode_UriSyntax:
+        return "Badly formatted URI";
+
+      case ErrorCode_InexistentFile:
+        return "Inexistent file";
+
+      case ErrorCode_CannotWriteFile:
+        return "Cannot write to file";
+
+      case ErrorCode_BadFileFormat:
+        return "Bad file format";
+
+      case ErrorCode_Timeout:
+        return "Timeout";
+
+      case ErrorCode_UnknownResource:
+        return "Unknown resource";
+
+      case ErrorCode_IncompatibleDatabaseVersion:
+        return "Incompatible version of the database";
+
+      case ErrorCode_FullStorage:
+        return "The file storage is full";
+
+      case ErrorCode_CorruptedFile:
+        return "Corrupted file (e.g. inconsistent MD5 hash)";
+
+      case ErrorCode_InexistentTag:
+        return "Inexistent tag";
+
+      case ErrorCode_ReadOnly:
+        return "Cannot modify a read-only data structure";
+
+      case ErrorCode_IncompatibleImageFormat:
+        return "Incompatible format of the images";
+
+      case ErrorCode_IncompatibleImageSize:
+        return "Incompatible size of the images";
+
+      case ErrorCode_SharedLibrary:
+        return "Error while using a shared library (plugin)";
+
+      case ErrorCode_UnknownPluginService:
+        return "Plugin invoking an unknown service";
+
+      case ErrorCode_UnknownDicomTag:
+        return "Unknown DICOM tag";
+
+      case ErrorCode_BadJson:
+        return "Cannot parse a JSON document";
+
+      case ErrorCode_Unauthorized:
+        return "Bad credentials were provided to an HTTP request";
+
+      case ErrorCode_BadFont:
+        return "Badly formatted font file";
+
+      case ErrorCode_DatabasePlugin:
+        return "The plugin implementing a custom database back-end does not fulfill the proper interface";
+
+      case ErrorCode_StorageAreaPlugin:
+        return "Error in the plugin implementing a custom storage area";
+
+      case ErrorCode_EmptyRequest:
+        return "The request is empty";
+
+      case ErrorCode_NotAcceptable:
+        return "Cannot send a response which is acceptable according to the Accept HTTP header";
+
+      case ErrorCode_NullPointer:
+        return "Cannot handle a NULL pointer";
+
+      case ErrorCode_SQLiteNotOpened:
+        return "SQLite: The database is not opened";
+
+      case ErrorCode_SQLiteAlreadyOpened:
+        return "SQLite: Connection is already open";
+
+      case ErrorCode_SQLiteCannotOpen:
+        return "SQLite: Unable to open the database";
+
+      case ErrorCode_SQLiteStatementAlreadyUsed:
+        return "SQLite: This cached statement is already being referred to";
+
+      case ErrorCode_SQLiteExecute:
+        return "SQLite: Cannot execute a command";
+
+      case ErrorCode_SQLiteRollbackWithoutTransaction:
+        return "SQLite: Rolling back a nonexistent transaction (have you called Begin()?)";
+
+      case ErrorCode_SQLiteCommitWithoutTransaction:
+        return "SQLite: Committing a nonexistent transaction";
+
+      case ErrorCode_SQLiteRegisterFunction:
+        return "SQLite: Unable to register a function";
+
+      case ErrorCode_SQLiteFlush:
+        return "SQLite: Unable to flush the database";
+
+      case ErrorCode_SQLiteCannotRun:
+        return "SQLite: Cannot run a cached statement";
+
+      case ErrorCode_SQLiteCannotStep:
+        return "SQLite: Cannot step over a cached statement";
+
+      case ErrorCode_SQLiteBindOutOfRange:
+        return "SQLite: Bing a value while out of range (serious error)";
+
+      case ErrorCode_SQLitePrepareStatement:
+        return "SQLite: Cannot prepare a cached statement";
+
+      case ErrorCode_SQLiteTransactionAlreadyStarted:
+        return "SQLite: Beginning the same transaction twice";
+
+      case ErrorCode_SQLiteTransactionCommit:
+        return "SQLite: Failure when committing the transaction";
+
+      case ErrorCode_SQLiteTransactionBegin:
+        return "SQLite: Cannot start a transaction";
+
+      case ErrorCode_DirectoryOverFile:
+        return "The directory to be created is already occupied by a regular file";
+
+      case ErrorCode_FileStorageCannotWrite:
+        return "Unable to create a subdirectory or a file in the file storage";
+
+      case ErrorCode_DirectoryExpected:
+        return "The specified path does not point to a directory";
+
+      case ErrorCode_HttpPortInUse:
+        return "The TCP port of the HTTP server is privileged or already in use";
+
+      case ErrorCode_DicomPortInUse:
+        return "The TCP port of the DICOM server is privileged or already in use";
+
+      case ErrorCode_BadHttpStatusInRest:
+        return "This HTTP status is not allowed in a REST API";
+
+      case ErrorCode_RegularFileExpected:
+        return "The specified path does not point to a regular file";
+
+      case ErrorCode_PathToExecutable:
+        return "Unable to get the path to the executable";
+
+      case ErrorCode_MakeDirectory:
+        return "Cannot create a directory";
+
+      case ErrorCode_BadApplicationEntityTitle:
+        return "An application entity title (AET) cannot be empty or be longer than 16 characters";
+
+      case ErrorCode_NoCFindHandler:
+        return "No request handler factory for DICOM C-FIND SCP";
+
+      case ErrorCode_NoCMoveHandler:
+        return "No request handler factory for DICOM C-MOVE SCP";
+
+      case ErrorCode_NoCStoreHandler:
+        return "No request handler factory for DICOM C-STORE SCP";
+
+      case ErrorCode_NoApplicationEntityFilter:
+        return "No application entity filter";
+
+      case ErrorCode_NoSopClassOrInstance:
+        return "DicomUserConnection: Unable to find the SOP class and instance";
+
+      case ErrorCode_NoPresentationContext:
+        return "DicomUserConnection: No acceptable presentation context for modality";
+
+      case ErrorCode_DicomFindUnavailable:
+        return "DicomUserConnection: The C-FIND command is not supported by the remote SCP";
+
+      case ErrorCode_DicomMoveUnavailable:
+        return "DicomUserConnection: The C-MOVE command is not supported by the remote SCP";
+
+      case ErrorCode_CannotStoreInstance:
+        return "Cannot store an instance";
+
+      case ErrorCode_CreateDicomNotString:
+        return "Only string values are supported when creating DICOM instances";
+
+      case ErrorCode_CreateDicomOverrideTag:
+        return "Trying to override a value inherited from a parent module";
+
+      case ErrorCode_CreateDicomUseContent:
+        return "Use \"Content\" to inject an image into a new DICOM instance";
+
+      case ErrorCode_CreateDicomNoPayload:
+        return "No payload is present for one instance in the series";
+
+      case ErrorCode_CreateDicomUseDataUriScheme:
+        return "The payload of the DICOM instance must be specified according to Data URI scheme";
+
+      case ErrorCode_CreateDicomBadParent:
+        return "Trying to attach a new DICOM instance to an inexistent resource";
+
+      case ErrorCode_CreateDicomParentIsInstance:
+        return "Trying to attach a new DICOM instance to an instance (must be a series, study or patient)";
+
+      case ErrorCode_CreateDicomParentEncoding:
+        return "Unable to get the encoding of the parent resource";
+
+      case ErrorCode_UnknownModality:
+        return "Unknown modality";
+
+      case ErrorCode_BadJobOrdering:
+        return "Bad ordering of filters in a job";
+
+      case ErrorCode_JsonToLuaTable:
+        return "Cannot convert the given JSON object to a Lua table";
+
+      case ErrorCode_CannotCreateLua:
+        return "Cannot create the Lua context";
+
+      case ErrorCode_CannotExecuteLua:
+        return "Cannot execute a Lua command";
+
+      case ErrorCode_LuaAlreadyExecuted:
+        return "Arguments cannot be pushed after the Lua function is executed";
+
+      case ErrorCode_LuaBadOutput:
+        return "The Lua function does not give the expected number of outputs";
+
+      case ErrorCode_NotLuaPredicate:
+        return "The Lua function is not a predicate (only true/false outputs allowed)";
+
+      case ErrorCode_LuaReturnsNoString:
+        return "The Lua function does not return a string";
+
+      case ErrorCode_StorageAreaAlreadyRegistered:
+        return "Another plugin has already registered a custom storage area";
+
+      case ErrorCode_DatabaseBackendAlreadyRegistered:
+        return "Another plugin has already registered a custom database back-end";
+
+      case ErrorCode_DatabaseNotInitialized:
+        return "Plugin trying to call the database during its initialization";
+
+      case ErrorCode_SslDisabled:
+        return "Orthanc has been built without SSL support";
+
+      case ErrorCode_CannotOrderSlices:
+        return "Unable to order the slices of the series";
+
+      case ErrorCode_NoWorklistHandler:
+        return "No request handler factory for DICOM C-Find Modality SCP";
+
+      case ErrorCode_AlreadyExistingTag:
+        return "Cannot override the value of a tag that already exists";
+
+      default:
+        if (error >= ErrorCode_START_PLUGINS)
+        {
+          return "Error encountered within some plugin";
+        }
+        else
+        {
+          return "Unknown error code";
+        }
+    }
+  }
+
+
+  const char* EnumerationToString(HttpMethod method)
+  {
+    switch (method)
+    {
+      case HttpMethod_Get:
+        return "GET";
+
+      case HttpMethod_Post:
+        return "POST";
+
+      case HttpMethod_Delete:
+        return "DELETE";
+
+      case HttpMethod_Put:
+        return "PUT";
+
+      default:
+        return "?";
+    }
+  }
+
+
+  const char* EnumerationToString(HttpStatus status)
+  {
+    switch (status)
+    {
+    case HttpStatus_100_Continue:
+      return "Continue";
+
+    case HttpStatus_101_SwitchingProtocols:
+      return "Switching Protocols";
+
+    case HttpStatus_102_Processing:
+      return "Processing";
+
+    case HttpStatus_200_Ok:
+      return "OK";
+
+    case HttpStatus_201_Created:
+      return "Created";
+
+    case HttpStatus_202_Accepted:
+      return "Accepted";
+
+    case HttpStatus_203_NonAuthoritativeInformation:
+      return "Non-Authoritative Information";
+
+    case HttpStatus_204_NoContent:
+      return "No Content";
+
+    case HttpStatus_205_ResetContent:
+      return "Reset Content";
+
+    case HttpStatus_206_PartialContent:
+      return "Partial Content";
+
+    case HttpStatus_207_MultiStatus:
+      return "Multi-Status";
+
+    case HttpStatus_208_AlreadyReported:
+      return "Already Reported";
+
+    case HttpStatus_226_IMUsed:
+      return "IM Used";
+
+    case HttpStatus_300_MultipleChoices:
+      return "Multiple Choices";
+
+    case HttpStatus_301_MovedPermanently:
+      return "Moved Permanently";
+
+    case HttpStatus_302_Found:
+      return "Found";
+
+    case HttpStatus_303_SeeOther:
+      return "See Other";
+
+    case HttpStatus_304_NotModified:
+      return "Not Modified";
+
+    case HttpStatus_305_UseProxy:
+      return "Use Proxy";
+
+    case HttpStatus_307_TemporaryRedirect:
+      return "Temporary Redirect";
+
+    case HttpStatus_400_BadRequest:
+      return "Bad Request";
+
+    case HttpStatus_401_Unauthorized:
+      return "Unauthorized";
+
+    case HttpStatus_402_PaymentRequired:
+      return "Payment Required";
+
+    case HttpStatus_403_Forbidden:
+      return "Forbidden";
+
+    case HttpStatus_404_NotFound:
+      return "Not Found";
+
+    case HttpStatus_405_MethodNotAllowed:
+      return "Method Not Allowed";
+
+    case HttpStatus_406_NotAcceptable:
+      return "Not Acceptable";
+
+    case HttpStatus_407_ProxyAuthenticationRequired:
+      return "Proxy Authentication Required";
+
+    case HttpStatus_408_RequestTimeout:
+      return "Request Timeout";
+
+    case HttpStatus_409_Conflict:
+      return "Conflict";
+
+    case HttpStatus_410_Gone:
+      return "Gone";
+
+    case HttpStatus_411_LengthRequired:
+      return "Length Required";
+
+    case HttpStatus_412_PreconditionFailed:
+      return "Precondition Failed";
+
+    case HttpStatus_413_RequestEntityTooLarge:
+      return "Request Entity Too Large";
+
+    case HttpStatus_414_RequestUriTooLong:
+      return "Request-URI Too Long";
+
+    case HttpStatus_415_UnsupportedMediaType:
+      return "Unsupported Media Type";
+
+    case HttpStatus_416_RequestedRangeNotSatisfiable:
+      return "Requested Range Not Satisfiable";
+
+    case HttpStatus_417_ExpectationFailed:
+      return "Expectation Failed";
+
+    case HttpStatus_422_UnprocessableEntity:
+      return "Unprocessable Entity";
+
+    case HttpStatus_423_Locked:
+      return "Locked";
+
+    case HttpStatus_424_FailedDependency:
+      return "Failed Dependency";
+
+    case HttpStatus_426_UpgradeRequired:
+      return "Upgrade Required";
+
+    case HttpStatus_500_InternalServerError:
+      return "Internal Server Error";
+
+    case HttpStatus_501_NotImplemented:
+      return "Not Implemented";
+
+    case HttpStatus_502_BadGateway:
+      return "Bad Gateway";
+
+    case HttpStatus_503_ServiceUnavailable:
+      return "Service Unavailable";
+
+    case HttpStatus_504_GatewayTimeout:
+      return "Gateway Timeout";
+
+    case HttpStatus_505_HttpVersionNotSupported:
+      return "HTTP Version Not Supported";
+
+    case HttpStatus_506_VariantAlsoNegotiates:
+      return "Variant Also Negotiates";
+
+    case HttpStatus_507_InsufficientStorage:
+      return "Insufficient Storage";
+
+    case HttpStatus_509_BandwidthLimitExceeded:
+      return "Bandwidth Limit Exceeded";
+
+    case HttpStatus_510_NotExtended:
+      return "Not Extended";
+
+    default:
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(ResourceType type)
+  {
+    switch (type)
+    {
+      case ResourceType_Patient:
+        return "Patient";
+
+      case ResourceType_Study:
+        return "Study";
+
+      case ResourceType_Series:
+        return "Series";
+
+      case ResourceType_Instance:
+        return "Instance";
+      
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(ImageFormat format)
+  {
+    switch (format)
+    {
+      case ImageFormat_Png:
+        return "Png";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(Encoding encoding)
+  {
+    switch (encoding)
+    {
+      case Encoding_Ascii:
+        return "Ascii";
+
+      case Encoding_Utf8:
+        return "Utf8";
+
+      case Encoding_Latin1:
+        return "Latin1";
+
+      case Encoding_Latin2:
+        return "Latin2";
+
+      case Encoding_Latin3:
+        return "Latin3";
+
+      case Encoding_Latin4:
+        return "Latin4";
+
+      case Encoding_Latin5:
+        return "Latin5";
+
+      case Encoding_Cyrillic:
+        return "Cyrillic";
+
+      case Encoding_Windows1251:
+        return "Windows1251";
+
+      case Encoding_Arabic:
+        return "Arabic";
+
+      case Encoding_Greek:
+        return "Greek";
+
+      case Encoding_Hebrew:
+        return "Hebrew";
+
+      case Encoding_Thai:
+        return "Thai";
+
+      case Encoding_Japanese:
+        return "Japanese";
+
+      case Encoding_Chinese:
+        return "Chinese";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(PhotometricInterpretation photometric)
+  {
+    switch (photometric)
+    {
+      case PhotometricInterpretation_RGB:
+        return "RGB";
+
+      case PhotometricInterpretation_Monochrome1:
+        return "Monochrome1";
+
+      case PhotometricInterpretation_Monochrome2:
+        return "Monochrome2";
+
+      case PhotometricInterpretation_ARGB:
+        return "ARGB";
+
+      case PhotometricInterpretation_CMYK:
+        return "CMYK";
+
+      case PhotometricInterpretation_HSV:
+        return "HSV";
+
+      case PhotometricInterpretation_Palette:
+        return "Palette color";
+
+      case PhotometricInterpretation_YBRFull:
+        return "YBR full";
+
+      case PhotometricInterpretation_YBRFull422:
+        return "YBR full 422";
+
+      case PhotometricInterpretation_YBRPartial420:
+        return "YBR partial 420"; 
+
+      case PhotometricInterpretation_YBRPartial422:
+        return "YBR partial 422"; 
+
+      case PhotometricInterpretation_YBR_ICT:
+        return "YBR ICT"; 
+
+      case PhotometricInterpretation_YBR_RCT:
+        return "YBR RCT"; 
+
+      case PhotometricInterpretation_Unknown:
+        return "Unknown";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(RequestOrigin origin)
+  {
+    switch (origin)
+    {
+      case RequestOrigin_Unknown:
+        return "Unknown";
+
+      case RequestOrigin_DicomProtocol:
+        return "DicomProtocol";
+
+      case RequestOrigin_RestApi:
+        return "RestApi";
+
+      case RequestOrigin_Plugins:
+        return "Plugins";
+
+      case RequestOrigin_Lua:
+        return "Lua";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(LogLevel level)
+  {
+    switch (level)
+    {
+      case LogLevel_Error:
+        return "ERROR";
+
+      case LogLevel_Warning:
+        return "WARNING";
+
+      case LogLevel_Info:
+        return "INFO";
+
+      case LogLevel_Trace:
+        return "TRACE";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(PixelFormat format)
+  {
+    switch (format)
+    {
+      case PixelFormat_RGB24:
+        return "RGB24";
+
+      case PixelFormat_RGBA32:
+        return "RGBA32";
+
+      case PixelFormat_BGRA32:
+        return "BGRA32";
+
+      case PixelFormat_Grayscale8:
+        return "Grayscale (unsigned 8bpp)";
+
+      case PixelFormat_Grayscale16:
+        return "Grayscale (unsigned 16bpp)";
+
+      case PixelFormat_SignedGrayscale16:
+        return "Grayscale (signed 16bpp)";
+
+      case PixelFormat_Float32:
+        return "Grayscale (float 32bpp)";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  Encoding StringToEncoding(const char* encoding)
+  {
+    std::string s(encoding);
+    Toolbox::ToUpperCase(s);
+
+    if (s == "UTF8")
+    {
+      return Encoding_Utf8;
+    }
+
+    if (s == "ASCII")
+    {
+      return Encoding_Ascii;
+    }
+
+    if (s == "LATIN1")
+    {
+      return Encoding_Latin1;
+    }
+
+    if (s == "LATIN2")
+    {
+      return Encoding_Latin2;
+    }
+
+    if (s == "LATIN3")
+    {
+      return Encoding_Latin3;
+    }
+
+    if (s == "LATIN4")
+    {
+      return Encoding_Latin4;
+    }
+
+    if (s == "LATIN5")
+    {
+      return Encoding_Latin5;
+    }
+
+    if (s == "CYRILLIC")
+    {
+      return Encoding_Cyrillic;
+    }
+
+    if (s == "WINDOWS1251")
+    {
+      return Encoding_Windows1251;
+    }
+
+    if (s == "ARABIC")
+    {
+      return Encoding_Arabic;
+    }
+
+    if (s == "GREEK")
+    {
+      return Encoding_Greek;
+    }
+
+    if (s == "HEBREW")
+    {
+      return Encoding_Hebrew;
+    }
+
+    if (s == "THAI")
+    {
+      return Encoding_Thai;
+    }
+
+    if (s == "JAPANESE")
+    {
+      return Encoding_Japanese;
+    }
+
+    if (s == "CHINESE")
+    {
+      return Encoding_Chinese;
+    }
+
+    throw OrthancException(ErrorCode_ParameterOutOfRange);
+  }
+
+
+  ResourceType StringToResourceType(const char* type)
+  {
+    std::string s(type);
+    Toolbox::ToUpperCase(s);
+
+    if (s == "PATIENT" || s == "PATIENTS")
+    {
+      return ResourceType_Patient;
+    }
+    else if (s == "STUDY" || s == "STUDIES")
+    {
+      return ResourceType_Study;
+    }
+    else if (s == "SERIES")
+    {
+      return ResourceType_Series;
+    }
+    else if (s == "INSTANCE"  || s == "IMAGE" || 
+             s == "INSTANCES" || s == "IMAGES")
+    {
+      return ResourceType_Instance;
+    }
+
+    throw OrthancException(ErrorCode_ParameterOutOfRange);
+  }
+
+
+  ImageFormat StringToImageFormat(const char* format)
+  {
+    std::string s(format);
+    Toolbox::ToUpperCase(s);
+
+    if (s == "PNG")
+    {
+      return ImageFormat_Png;
+    }
+
+    throw OrthancException(ErrorCode_ParameterOutOfRange);
+  }
+
+
+  LogLevel StringToLogLevel(const char *level)
+  {
+    if (strcmp(level, "ERROR") == 0)
+    {
+      return LogLevel_Error;
+    }
+    else if (strcmp(level, "WARNING") == 0)
+    {
+      return LogLevel_Warning;
+    }
+    else if (strcmp(level, "INFO") == 0)
+    {
+      return LogLevel_Info;
+    }
+    else if (strcmp(level, "TRACE") == 0)
+    {
+      return LogLevel_Trace;
+    }
+    else 
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+  }
+
+
+  ValueRepresentation StringToValueRepresentation(const std::string& vr,
+                                                  bool throwIfUnsupported)
+  {
+    if (vr == "AE")
+    {
+      return ValueRepresentation_ApplicationEntity;
+    }
+    else if (vr == "AS")
+    {
+      return ValueRepresentation_AgeString;
+    }
+    else if (vr == "AT")
+    {
+      return ValueRepresentation_AttributeTag;
+    }
+    else if (vr == "CS")
+    {
+      return ValueRepresentation_CodeString;
+    }
+    else if (vr == "DA")
+    {
+      return ValueRepresentation_Date;
+    }
+    else if (vr == "DS")
+    {
+      return ValueRepresentation_DecimalString;
+    }
+    else if (vr == "DT")
+    {
+      return ValueRepresentation_DateTime;
+    }
+    else if (vr == "FL")
+    {
+      return ValueRepresentation_FloatingPointSingle;
+    }
+    else if (vr == "FD")
+    {
+      return ValueRepresentation_FloatingPointDouble;
+    }
+    else if (vr == "IS")
+    {
+      return ValueRepresentation_IntegerString;
+    }
+    else if (vr == "LO")
+    {
+      return ValueRepresentation_LongString;
+    }
+    else if (vr == "LT")
+    {
+      return ValueRepresentation_LongText;
+    }
+    else if (vr == "OB")
+    {
+      return ValueRepresentation_OtherByte;
+    }
+    else if (vr == "OD")
+    {
+      return ValueRepresentation_OtherDouble;
+    }
+    else if (vr == "OF")
+    {
+      return ValueRepresentation_OtherFloat;
+    }
+    else if (vr == "OL")
+    {
+      return ValueRepresentation_OtherLong;
+    }
+    else if (vr == "OW")
+    {
+      return ValueRepresentation_OtherWord;
+    }
+    else if (vr == "PN")
+    {
+      return ValueRepresentation_PersonName;
+    }
+    else if (vr == "SH")
+    {
+      return ValueRepresentation_ShortString;
+    }
+    else if (vr == "SL")
+    {
+      return ValueRepresentation_SignedLong;
+    }
+    else if (vr == "SQ")
+    {
+      return ValueRepresentation_Sequence;
+    }
+    else if (vr == "SS")
+    {
+      return ValueRepresentation_SignedShort;
+    }
+    else if (vr == "ST")
+    {
+      return ValueRepresentation_ShortText;
+    }
+    else if (vr == "TM")
+    {
+      return ValueRepresentation_Time;
+    }
+    else if (vr == "UC")
+    {
+      return ValueRepresentation_UnlimitedCharacters;
+    }
+    else if (vr == "UI")
+    {
+      return ValueRepresentation_UniqueIdentifier;
+    }
+    else if (vr == "UL")
+    {
+      return ValueRepresentation_UnsignedLong;
+    }
+    else if (vr == "UN")
+    {
+      return ValueRepresentation_Unknown;
+    }
+    else if (vr == "UR")
+    {
+      return ValueRepresentation_UniversalResource;
+    }
+    else if (vr == "US")
+    {
+      return ValueRepresentation_UnsignedShort;
+    }
+    else if (vr == "UT")
+    {
+      return ValueRepresentation_UnlimitedText;
+    }
+    else
+    {
+      std::string s = "Unsupported value representation encountered: " + vr;
+
+      if (throwIfUnsupported)
+      {
+        LOG(ERROR) << s;
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+      }
+      else
+      {
+        LOG(INFO) << s;
+        return ValueRepresentation_NotSupported;
+      }
+    }
+  }
+
+
+  unsigned int GetBytesPerPixel(PixelFormat format)
+  {
+    switch (format)
+    {
+      case PixelFormat_Grayscale8:
+        return 1;
+
+      case PixelFormat_Grayscale16:
+      case PixelFormat_SignedGrayscale16:
+        return 2;
+
+      case PixelFormat_RGB24:
+        return 3;
+
+      case PixelFormat_RGBA32:
+      case PixelFormat_BGRA32:
+        return 4;
+
+      case PixelFormat_Float32:
+        assert(sizeof(float) == 4);
+        return 4;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  bool GetDicomEncoding(Encoding& encoding,
+                        const char* specificCharacterSet)
+  {
+    std::string s = Toolbox::StripSpaces(specificCharacterSet);
+    Toolbox::ToUpperCase(s);
+
+    // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2
+    // https://github.com/dcm4che/dcm4che/blob/master/dcm4che-core/src/main/java/org/dcm4che3/data/SpecificCharacterSet.java
+    if (s == "ISO_IR 6" ||
+        s == "ISO 2022 IR 6")
+    {
+      encoding = Encoding_Ascii;
+    }
+    else if (s == "ISO_IR 192")
+    {
+      encoding = Encoding_Utf8;
+    }
+    else if (s == "ISO_IR 100" ||
+             s == "ISO 2022 IR 100")
+    {
+      encoding = Encoding_Latin1;
+    }
+    else if (s == "ISO_IR 101" ||
+             s == "ISO 2022 IR 101")
+    {
+      encoding = Encoding_Latin2;
+    }
+    else if (s == "ISO_IR 109" ||
+             s == "ISO 2022 IR 109")
+    {
+      encoding = Encoding_Latin3;
+    }
+    else if (s == "ISO_IR 110" ||
+             s == "ISO 2022 IR 110")
+    {
+      encoding = Encoding_Latin4;
+    }
+    else if (s == "ISO_IR 148" ||
+             s == "ISO 2022 IR 148")
+    {
+      encoding = Encoding_Latin5;
+    }
+    else if (s == "ISO_IR 144" ||
+             s == "ISO 2022 IR 144")
+    {
+      encoding = Encoding_Cyrillic;
+    }
+    else if (s == "ISO_IR 127" ||
+             s == "ISO 2022 IR 127")
+    {
+      encoding = Encoding_Arabic;
+    }
+    else if (s == "ISO_IR 126" ||
+             s == "ISO 2022 IR 126")
+    {
+      encoding = Encoding_Greek;
+    }
+    else if (s == "ISO_IR 138" ||
+             s == "ISO 2022 IR 138")
+    {
+      encoding = Encoding_Hebrew;
+    }
+    else if (s == "ISO_IR 166" || s == "ISO 2022 IR 166")
+    {
+      encoding = Encoding_Thai;
+    }
+    else if (s == "ISO_IR 13" || s == "ISO 2022 IR 13")
+    {
+      encoding = Encoding_Japanese;
+    }
+    else if (s == "GB18030")
+    {
+      encoding = Encoding_Chinese;
+    }
+    /*
+      else if (s == "ISO 2022 IR 149")
+      {
+      TODO
+      }
+      else if (s == "ISO 2022 IR 159")
+      {
+      TODO
+      }
+      else if (s == "ISO 2022 IR 87")
+      {
+      TODO
+      }
+    */
+    else
+    {
+      return false;
+    }
+
+    // The encoding was properly detected
+    return true;
+  }
+
+
+  ResourceType GetChildResourceType(ResourceType type)
+  {
+    switch (type)
+    {
+      case ResourceType_Patient:
+        return ResourceType_Study;
+
+      case ResourceType_Study:
+        return ResourceType_Series;
+        
+      case ResourceType_Series:
+        return ResourceType_Instance;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  ResourceType GetParentResourceType(ResourceType type)
+  {
+    switch (type)
+    {
+      case ResourceType_Study:
+        return ResourceType_Patient;
+        
+      case ResourceType_Series:
+        return ResourceType_Study;
+
+      case ResourceType_Instance:
+        return ResourceType_Series;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  DicomModule GetModule(ResourceType type)
+  {
+    switch (type)
+    {
+      case ResourceType_Patient:
+        return DicomModule_Patient;
+
+      case ResourceType_Study:
+        return DicomModule_Study;
+        
+      case ResourceType_Series:
+        return DicomModule_Series;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+
+  const char* GetDicomSpecificCharacterSet(Encoding encoding)
+  {
+    // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2
+    switch (encoding)
+    {
+      case Encoding_Ascii:
+        return "ISO_IR 6";
+
+      case Encoding_Utf8:
+        return "ISO_IR 192";
+
+      case Encoding_Latin1:
+        return "ISO_IR 100";
+
+      case Encoding_Latin2:
+        return "ISO_IR 101";
+
+      case Encoding_Latin3:
+        return "ISO_IR 109";
+
+      case Encoding_Latin4:
+        return "ISO_IR 110";
+
+      case Encoding_Latin5:
+        return "ISO_IR 148";
+
+      case Encoding_Cyrillic:
+        return "ISO_IR 144";
+
+      case Encoding_Arabic:
+        return "ISO_IR 127";
+
+      case Encoding_Greek:
+        return "ISO_IR 126";
+
+      case Encoding_Hebrew:
+        return "ISO_IR 138";
+
+      case Encoding_Japanese:
+        return "ISO_IR 13";
+
+      case Encoding_Chinese:
+        return "GB18030";
+
+      case Encoding_Thai:
+        return "ISO_IR 166";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  // This function is autogenerated by the script
+  // "Resources/GenerateErrorCodes.py"
+  HttpStatus ConvertErrorCodeToHttpStatus(ErrorCode error)
+  {
+    switch (error)
+    {
+      case ErrorCode_Success:
+        return HttpStatus_200_Ok;
+
+      case ErrorCode_ParameterOutOfRange:
+        return HttpStatus_400_BadRequest;
+
+      case ErrorCode_BadParameterType:
+        return HttpStatus_400_BadRequest;
+
+      case ErrorCode_InexistentItem:
+        return HttpStatus_404_NotFound;
+
+      case ErrorCode_BadRequest:
+        return HttpStatus_400_BadRequest;
+
+      case ErrorCode_UriSyntax:
+        return HttpStatus_400_BadRequest;
+
+      case ErrorCode_InexistentFile:
+        return HttpStatus_404_NotFound;
+
+      case ErrorCode_BadFileFormat:
+        return HttpStatus_400_BadRequest;
+
+      case ErrorCode_UnknownResource:
+        return HttpStatus_404_NotFound;
+
+      case ErrorCode_InexistentTag:
+        return HttpStatus_404_NotFound;
+
+      case ErrorCode_BadJson:
+        return HttpStatus_400_BadRequest;
+
+      case ErrorCode_Unauthorized:
+        return HttpStatus_401_Unauthorized;
+
+      case ErrorCode_NotAcceptable:
+        return HttpStatus_406_NotAcceptable;
+
+      default:
+        return HttpStatus_500_InternalServerError;
+    }
+  }
+
+
+  bool IsUserContentType(FileContentType type)
+  {
+    return (type >= FileContentType_StartUser &&
+            type <= FileContentType_EndUser);
+  }
+
+
+  bool IsBinaryValueRepresentation(ValueRepresentation vr)
+  {
+    // http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html
+
+    switch (vr)
+    {
+      case ValueRepresentation_ApplicationEntity:     // AE
+      case ValueRepresentation_AgeString:             // AS
+      case ValueRepresentation_CodeString:            // CS
+      case ValueRepresentation_Date:                  // DA
+      case ValueRepresentation_DecimalString:         // DS
+      case ValueRepresentation_DateTime:              // DT
+      case ValueRepresentation_IntegerString:         // IS
+      case ValueRepresentation_LongString:            // LO
+      case ValueRepresentation_LongText:              // LT
+      case ValueRepresentation_PersonName:            // PN
+      case ValueRepresentation_ShortString:           // SH
+      case ValueRepresentation_ShortText:             // ST
+      case ValueRepresentation_Time:                  // TM
+      case ValueRepresentation_UnlimitedCharacters:   // UC
+      case ValueRepresentation_UniqueIdentifier:      // UI (UID)
+      case ValueRepresentation_UniversalResource:     // UR (URI or URL)
+      case ValueRepresentation_UnlimitedText:         // UT
+      {
+        return false;
+      }
+
+      /**
+       * Below are all the VR whose character repertoire is tagged as
+       * "not applicable"
+       **/
+      case ValueRepresentation_AttributeTag:          // AT (2 x uint16_t)
+      case ValueRepresentation_FloatingPointSingle:   // FL (float)
+      case ValueRepresentation_FloatingPointDouble:   // FD (double)
+      case ValueRepresentation_OtherByte:             // OB
+      case ValueRepresentation_OtherDouble:           // OD
+      case ValueRepresentation_OtherFloat:            // OF
+      case ValueRepresentation_OtherLong:             // OL
+      case ValueRepresentation_OtherWord:             // OW
+      case ValueRepresentation_SignedLong:            // SL (int32_t)
+      case ValueRepresentation_Sequence:              // SQ
+      case ValueRepresentation_SignedShort:           // SS (int16_t)
+      case ValueRepresentation_UnsignedLong:          // UL (uint32_t)
+      case ValueRepresentation_Unknown:               // UN
+      case ValueRepresentation_UnsignedShort:         // US (uint16_t)
+      {
+        return true;
+      }
+
+      case ValueRepresentation_NotSupported:
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Enumerations.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,545 @@
+/**
+ * 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
+
+#include <string>
+
+namespace Orthanc
+{
+  enum Endianness
+  {
+    Endianness_Unknown,
+    Endianness_Big,
+    Endianness_Little
+  };
+
+  // This enumeration is autogenerated by the script
+  // "Resources/GenerateErrorCodes.py"
+  enum ErrorCode
+  {
+    ErrorCode_InternalError = -1    /*!< Internal error */,
+    ErrorCode_Success = 0    /*!< Success */,
+    ErrorCode_Plugin = 1    /*!< Error encountered within the plugin engine */,
+    ErrorCode_NotImplemented = 2    /*!< Not implemented yet */,
+    ErrorCode_ParameterOutOfRange = 3    /*!< Parameter out of range */,
+    ErrorCode_NotEnoughMemory = 4    /*!< The server hosting Orthanc is running out of memory */,
+    ErrorCode_BadParameterType = 5    /*!< Bad type for a parameter */,
+    ErrorCode_BadSequenceOfCalls = 6    /*!< Bad sequence of calls */,
+    ErrorCode_InexistentItem = 7    /*!< Accessing an inexistent item */,
+    ErrorCode_BadRequest = 8    /*!< Bad request */,
+    ErrorCode_NetworkProtocol = 9    /*!< Error in the network protocol */,
+    ErrorCode_SystemCommand = 10    /*!< Error while calling a system command */,
+    ErrorCode_Database = 11    /*!< Error with the database engine */,
+    ErrorCode_UriSyntax = 12    /*!< Badly formatted URI */,
+    ErrorCode_InexistentFile = 13    /*!< Inexistent file */,
+    ErrorCode_CannotWriteFile = 14    /*!< Cannot write to file */,
+    ErrorCode_BadFileFormat = 15    /*!< Bad file format */,
+    ErrorCode_Timeout = 16    /*!< Timeout */,
+    ErrorCode_UnknownResource = 17    /*!< Unknown resource */,
+    ErrorCode_IncompatibleDatabaseVersion = 18    /*!< Incompatible version of the database */,
+    ErrorCode_FullStorage = 19    /*!< The file storage is full */,
+    ErrorCode_CorruptedFile = 20    /*!< Corrupted file (e.g. inconsistent MD5 hash) */,
+    ErrorCode_InexistentTag = 21    /*!< Inexistent tag */,
+    ErrorCode_ReadOnly = 22    /*!< Cannot modify a read-only data structure */,
+    ErrorCode_IncompatibleImageFormat = 23    /*!< Incompatible format of the images */,
+    ErrorCode_IncompatibleImageSize = 24    /*!< Incompatible size of the images */,
+    ErrorCode_SharedLibrary = 25    /*!< Error while using a shared library (plugin) */,
+    ErrorCode_UnknownPluginService = 26    /*!< Plugin invoking an unknown service */,
+    ErrorCode_UnknownDicomTag = 27    /*!< Unknown DICOM tag */,
+    ErrorCode_BadJson = 28    /*!< Cannot parse a JSON document */,
+    ErrorCode_Unauthorized = 29    /*!< Bad credentials were provided to an HTTP request */,
+    ErrorCode_BadFont = 30    /*!< Badly formatted font file */,
+    ErrorCode_DatabasePlugin = 31    /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */,
+    ErrorCode_StorageAreaPlugin = 32    /*!< Error in the plugin implementing a custom storage area */,
+    ErrorCode_EmptyRequest = 33    /*!< The request is empty */,
+    ErrorCode_NotAcceptable = 34    /*!< Cannot send a response which is acceptable according to the Accept HTTP header */,
+    ErrorCode_NullPointer = 35    /*!< Cannot handle a NULL pointer */,
+    ErrorCode_SQLiteNotOpened = 1000    /*!< SQLite: The database is not opened */,
+    ErrorCode_SQLiteAlreadyOpened = 1001    /*!< SQLite: Connection is already open */,
+    ErrorCode_SQLiteCannotOpen = 1002    /*!< SQLite: Unable to open the database */,
+    ErrorCode_SQLiteStatementAlreadyUsed = 1003    /*!< SQLite: This cached statement is already being referred to */,
+    ErrorCode_SQLiteExecute = 1004    /*!< SQLite: Cannot execute a command */,
+    ErrorCode_SQLiteRollbackWithoutTransaction = 1005    /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */,
+    ErrorCode_SQLiteCommitWithoutTransaction = 1006    /*!< SQLite: Committing a nonexistent transaction */,
+    ErrorCode_SQLiteRegisterFunction = 1007    /*!< SQLite: Unable to register a function */,
+    ErrorCode_SQLiteFlush = 1008    /*!< SQLite: Unable to flush the database */,
+    ErrorCode_SQLiteCannotRun = 1009    /*!< SQLite: Cannot run a cached statement */,
+    ErrorCode_SQLiteCannotStep = 1010    /*!< SQLite: Cannot step over a cached statement */,
+    ErrorCode_SQLiteBindOutOfRange = 1011    /*!< SQLite: Bing a value while out of range (serious error) */,
+    ErrorCode_SQLitePrepareStatement = 1012    /*!< SQLite: Cannot prepare a cached statement */,
+    ErrorCode_SQLiteTransactionAlreadyStarted = 1013    /*!< SQLite: Beginning the same transaction twice */,
+    ErrorCode_SQLiteTransactionCommit = 1014    /*!< SQLite: Failure when committing the transaction */,
+    ErrorCode_SQLiteTransactionBegin = 1015    /*!< SQLite: Cannot start a transaction */,
+    ErrorCode_DirectoryOverFile = 2000    /*!< The directory to be created is already occupied by a regular file */,
+    ErrorCode_FileStorageCannotWrite = 2001    /*!< Unable to create a subdirectory or a file in the file storage */,
+    ErrorCode_DirectoryExpected = 2002    /*!< The specified path does not point to a directory */,
+    ErrorCode_HttpPortInUse = 2003    /*!< The TCP port of the HTTP server is privileged or already in use */,
+    ErrorCode_DicomPortInUse = 2004    /*!< The TCP port of the DICOM server is privileged or already in use */,
+    ErrorCode_BadHttpStatusInRest = 2005    /*!< This HTTP status is not allowed in a REST API */,
+    ErrorCode_RegularFileExpected = 2006    /*!< The specified path does not point to a regular file */,
+    ErrorCode_PathToExecutable = 2007    /*!< Unable to get the path to the executable */,
+    ErrorCode_MakeDirectory = 2008    /*!< Cannot create a directory */,
+    ErrorCode_BadApplicationEntityTitle = 2009    /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */,
+    ErrorCode_NoCFindHandler = 2010    /*!< No request handler factory for DICOM C-FIND SCP */,
+    ErrorCode_NoCMoveHandler = 2011    /*!< No request handler factory for DICOM C-MOVE SCP */,
+    ErrorCode_NoCStoreHandler = 2012    /*!< No request handler factory for DICOM C-STORE SCP */,
+    ErrorCode_NoApplicationEntityFilter = 2013    /*!< No application entity filter */,
+    ErrorCode_NoSopClassOrInstance = 2014    /*!< DicomUserConnection: Unable to find the SOP class and instance */,
+    ErrorCode_NoPresentationContext = 2015    /*!< DicomUserConnection: No acceptable presentation context for modality */,
+    ErrorCode_DicomFindUnavailable = 2016    /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */,
+    ErrorCode_DicomMoveUnavailable = 2017    /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */,
+    ErrorCode_CannotStoreInstance = 2018    /*!< Cannot store an instance */,
+    ErrorCode_CreateDicomNotString = 2019    /*!< Only string values are supported when creating DICOM instances */,
+    ErrorCode_CreateDicomOverrideTag = 2020    /*!< Trying to override a value inherited from a parent module */,
+    ErrorCode_CreateDicomUseContent = 2021    /*!< Use \"Content\" to inject an image into a new DICOM instance */,
+    ErrorCode_CreateDicomNoPayload = 2022    /*!< No payload is present for one instance in the series */,
+    ErrorCode_CreateDicomUseDataUriScheme = 2023    /*!< The payload of the DICOM instance must be specified according to Data URI scheme */,
+    ErrorCode_CreateDicomBadParent = 2024    /*!< Trying to attach a new DICOM instance to an inexistent resource */,
+    ErrorCode_CreateDicomParentIsInstance = 2025    /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */,
+    ErrorCode_CreateDicomParentEncoding = 2026    /*!< Unable to get the encoding of the parent resource */,
+    ErrorCode_UnknownModality = 2027    /*!< Unknown modality */,
+    ErrorCode_BadJobOrdering = 2028    /*!< Bad ordering of filters in a job */,
+    ErrorCode_JsonToLuaTable = 2029    /*!< Cannot convert the given JSON object to a Lua table */,
+    ErrorCode_CannotCreateLua = 2030    /*!< Cannot create the Lua context */,
+    ErrorCode_CannotExecuteLua = 2031    /*!< Cannot execute a Lua command */,
+    ErrorCode_LuaAlreadyExecuted = 2032    /*!< Arguments cannot be pushed after the Lua function is executed */,
+    ErrorCode_LuaBadOutput = 2033    /*!< The Lua function does not give the expected number of outputs */,
+    ErrorCode_NotLuaPredicate = 2034    /*!< The Lua function is not a predicate (only true/false outputs allowed) */,
+    ErrorCode_LuaReturnsNoString = 2035    /*!< The Lua function does not return a string */,
+    ErrorCode_StorageAreaAlreadyRegistered = 2036    /*!< Another plugin has already registered a custom storage area */,
+    ErrorCode_DatabaseBackendAlreadyRegistered = 2037    /*!< Another plugin has already registered a custom database back-end */,
+    ErrorCode_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
+    ErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
+    ErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
+    ErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
+    ErrorCode_AlreadyExistingTag = 2042    /*!< Cannot override the value of a tag that already exists */,
+    ErrorCode_START_PLUGINS = 1000000
+  };
+
+  enum LogLevel
+  {
+    LogLevel_Error,
+    LogLevel_Warning,
+    LogLevel_Info,
+    LogLevel_Trace
+  };
+
+
+  /**
+   * {summary}{The memory layout of the pixels (resp. voxels) of a 2D (resp. 3D) image.}
+   **/
+  enum PixelFormat
+  {
+    /**
+     * {summary}{Color image in RGB24 format.}
+     * {description}{This format describes a color image. The pixels are stored in 3
+     * consecutive bytes. The memory layout is RGB.}
+     **/
+    PixelFormat_RGB24 = 1,
+
+    /**
+     * {summary}{Color image in RGBA32 format.}
+     * {description}{This format describes a color image. The pixels are stored in 4
+     * consecutive bytes. The memory layout is RGBA.}
+     **/
+    PixelFormat_RGBA32 = 2,
+
+    /**
+     * {summary}{Graylevel 8bpp image.}
+     * {description}{The image is graylevel. Each pixel is unsigned and stored in one byte.}
+     **/
+    PixelFormat_Grayscale8 = 3,
+      
+    /**
+     * {summary}{Graylevel, unsigned 16bpp image.}
+     * {description}{The image is graylevel. Each pixel is unsigned and stored in two bytes.}
+     **/
+    PixelFormat_Grayscale16 = 4,
+      
+    /**
+     * {summary}{Graylevel, signed 16bpp image.}
+     * {description}{The image is graylevel. Each pixel is signed and stored in two bytes.}
+     **/
+    PixelFormat_SignedGrayscale16 = 5,
+      
+    /**
+     * {summary}{Graylevel, floating-point image.}
+     * {description}{The image is graylevel. Each pixel is floating-point and stored in 4 bytes.}
+     **/
+    PixelFormat_Float32 = 6,
+
+    // This is the memory layout for Cairo
+    PixelFormat_BGRA32 = 7
+  };
+
+
+  /**
+   * {summary}{The extraction mode specifies the way the values of the pixels are scaled when downloading a 2D image.}
+   **/
+  enum ImageExtractionMode
+  {
+    /**
+     * {summary}{Rescaled to 8bpp.}
+     * {description}{The minimum value of the image is set to 0, and its maximum value is set to 255.}
+     **/
+    ImageExtractionMode_Preview = 1,
+
+    /**
+     * {summary}{Truncation to the [0, 255] range.}
+     **/
+    ImageExtractionMode_UInt8 = 2,
+
+    /**
+     * {summary}{Truncation to the [0, 65535] range.}
+     **/
+    ImageExtractionMode_UInt16 = 3,
+
+    /**
+     * {summary}{Truncation to the [-32768, 32767] range.}
+     **/
+    ImageExtractionMode_Int16 = 4
+  };
+
+
+  /**
+   * Most common, non-joke and non-experimental HTTP status codes
+   * http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
+   **/
+  enum HttpStatus
+  {
+    HttpStatus_None = -1,
+
+    // 1xx Informational
+    HttpStatus_100_Continue = 100,
+    HttpStatus_101_SwitchingProtocols = 101,
+    HttpStatus_102_Processing = 102,
+
+    // 2xx Success
+    HttpStatus_200_Ok = 200,
+    HttpStatus_201_Created = 201,
+    HttpStatus_202_Accepted = 202,
+    HttpStatus_203_NonAuthoritativeInformation = 203,
+    HttpStatus_204_NoContent = 204,
+    HttpStatus_205_ResetContent = 205,
+    HttpStatus_206_PartialContent = 206,
+    HttpStatus_207_MultiStatus = 207,
+    HttpStatus_208_AlreadyReported = 208,
+    HttpStatus_226_IMUsed = 226,
+
+    // 3xx Redirection
+    HttpStatus_300_MultipleChoices = 300,
+    HttpStatus_301_MovedPermanently = 301,
+    HttpStatus_302_Found = 302,
+    HttpStatus_303_SeeOther = 303,
+    HttpStatus_304_NotModified = 304,
+    HttpStatus_305_UseProxy = 305,
+    HttpStatus_307_TemporaryRedirect = 307,
+
+    // 4xx Client Error
+    HttpStatus_400_BadRequest = 400,
+    HttpStatus_401_Unauthorized = 401,
+    HttpStatus_402_PaymentRequired = 402,
+    HttpStatus_403_Forbidden = 403,
+    HttpStatus_404_NotFound = 404,
+    HttpStatus_405_MethodNotAllowed = 405,
+    HttpStatus_406_NotAcceptable = 406,
+    HttpStatus_407_ProxyAuthenticationRequired = 407,
+    HttpStatus_408_RequestTimeout = 408,
+    HttpStatus_409_Conflict = 409,
+    HttpStatus_410_Gone = 410,
+    HttpStatus_411_LengthRequired = 411,
+    HttpStatus_412_PreconditionFailed = 412,
+    HttpStatus_413_RequestEntityTooLarge = 413,
+    HttpStatus_414_RequestUriTooLong = 414,
+    HttpStatus_415_UnsupportedMediaType = 415,
+    HttpStatus_416_RequestedRangeNotSatisfiable = 416,
+    HttpStatus_417_ExpectationFailed = 417,
+    HttpStatus_422_UnprocessableEntity = 422,
+    HttpStatus_423_Locked = 423,
+    HttpStatus_424_FailedDependency = 424,
+    HttpStatus_426_UpgradeRequired = 426,
+
+    // 5xx Server Error
+    HttpStatus_500_InternalServerError = 500,
+    HttpStatus_501_NotImplemented = 501,
+    HttpStatus_502_BadGateway = 502,
+    HttpStatus_503_ServiceUnavailable = 503,
+    HttpStatus_504_GatewayTimeout = 504,
+    HttpStatus_505_HttpVersionNotSupported = 505,
+    HttpStatus_506_VariantAlsoNegotiates = 506,
+    HttpStatus_507_InsufficientStorage = 507,
+    HttpStatus_509_BandwidthLimitExceeded = 509,
+    HttpStatus_510_NotExtended = 510
+  };
+
+
+  enum HttpMethod
+  {
+    HttpMethod_Get = 0,
+    HttpMethod_Post = 1,
+    HttpMethod_Delete = 2,
+    HttpMethod_Put = 3
+  };
+
+
+  enum ImageFormat
+  {
+    ImageFormat_Png = 1
+  };
+
+
+  // https://en.wikipedia.org/wiki/HTTP_compression
+  enum HttpCompression
+  {
+    HttpCompression_None,
+    HttpCompression_Deflate,
+    HttpCompression_Gzip
+  };
+
+
+  // Specific Character Sets
+  // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.12.1.1.2
+  enum Encoding
+  {
+    Encoding_Ascii,
+    Encoding_Utf8,
+    Encoding_Latin1,
+    Encoding_Latin2,
+    Encoding_Latin3,
+    Encoding_Latin4,
+    Encoding_Latin5,                        // Turkish
+    Encoding_Cyrillic,
+    Encoding_Windows1251,                   // Windows-1251 (commonly used for Cyrillic)
+    Encoding_Arabic,
+    Encoding_Greek,
+    Encoding_Hebrew,
+    Encoding_Thai,                          // TIS 620-2533
+    Encoding_Japanese,                      // JIS X 0201 (Shift JIS): Katakana
+    Encoding_Chinese                        // GB18030 - Chinese simplified
+    //Encoding_JapaneseKanji,               // Multibyte - JIS X 0208: Kanji
+    //Encoding_JapaneseSupplementaryKanji,  // Multibyte - JIS X 0212: Supplementary Kanji set
+    //Encoding_Korean,                      // Multibyte - KS X 1001: Hangul and Hanja
+  };
+
+
+  // http://dicom.nema.org/medical/dicom/current/output/html/part03.html#sect_C.7.6.3.1.2
+  enum PhotometricInterpretation
+  {
+    PhotometricInterpretation_ARGB,  // Retired
+    PhotometricInterpretation_CMYK,  // Retired
+    PhotometricInterpretation_HSV,   // Retired
+    PhotometricInterpretation_Monochrome1,
+    PhotometricInterpretation_Monochrome2,
+    PhotometricInterpretation_Palette,
+    PhotometricInterpretation_RGB,
+    PhotometricInterpretation_YBRFull,
+    PhotometricInterpretation_YBRFull422,
+    PhotometricInterpretation_YBRPartial420,
+    PhotometricInterpretation_YBRPartial422,
+    PhotometricInterpretation_YBR_ICT,
+    PhotometricInterpretation_YBR_RCT,
+    PhotometricInterpretation_Unknown
+  };
+
+  enum DicomModule
+  {
+    DicomModule_Patient,
+    DicomModule_Study,
+    DicomModule_Series,
+    DicomModule_Instance,
+    DicomModule_Image
+  };
+
+  enum RequestOrigin
+  {
+    RequestOrigin_Unknown,
+    RequestOrigin_DicomProtocol,
+    RequestOrigin_RestApi,
+    RequestOrigin_Plugins,
+    RequestOrigin_Lua
+  };
+
+  enum ServerBarrierEvent
+  {
+    ServerBarrierEvent_Stop,
+    ServerBarrierEvent_Reload  // SIGHUP signal: reload configuration file
+  };
+
+  enum FileMode
+  {
+    FileMode_ReadBinary,
+    FileMode_WriteBinary
+  };
+
+  /**
+   * The value representations Orthanc knows about. They correspond to
+   * the DICOM 2016b version of the standard.
+   * http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_6.2.html
+   **/
+  enum ValueRepresentation
+  {
+    ValueRepresentation_ApplicationEntity = 1,     // AE
+    ValueRepresentation_AgeString = 2,             // AS
+    ValueRepresentation_AttributeTag = 3,          // AT (2 x uint16_t)
+    ValueRepresentation_CodeString = 4,            // CS
+    ValueRepresentation_Date = 5,                  // DA
+    ValueRepresentation_DecimalString = 6,         // DS
+    ValueRepresentation_DateTime = 7,              // DT
+    ValueRepresentation_FloatingPointSingle = 8,   // FL (float)
+    ValueRepresentation_FloatingPointDouble = 9,   // FD (double)
+    ValueRepresentation_IntegerString = 10,        // IS
+    ValueRepresentation_LongString = 11,           // LO
+    ValueRepresentation_LongText = 12,             // LT
+    ValueRepresentation_OtherByte = 13,            // OB
+    ValueRepresentation_OtherDouble = 14,          // OD
+    ValueRepresentation_OtherFloat = 15,           // OF
+    ValueRepresentation_OtherLong = 16,            // OL
+    ValueRepresentation_OtherWord = 17,            // OW
+    ValueRepresentation_PersonName = 18,           // PN
+    ValueRepresentation_ShortString = 19,          // SH
+    ValueRepresentation_SignedLong = 20,           // SL (int32_t)
+    ValueRepresentation_Sequence = 21,             // SQ
+    ValueRepresentation_SignedShort = 22,          // SS (int16_t)
+    ValueRepresentation_ShortText = 23,            // ST
+    ValueRepresentation_Time = 24,                 // TM
+    ValueRepresentation_UnlimitedCharacters = 25,  // UC
+    ValueRepresentation_UniqueIdentifier = 26,     // UI (UID)
+    ValueRepresentation_UnsignedLong = 27,         // UL (uint32_t)
+    ValueRepresentation_Unknown = 28,              // UN
+    ValueRepresentation_UniversalResource = 29,    // UR (URI or URL)
+    ValueRepresentation_UnsignedShort = 30,        // US (uint16_t)
+    ValueRepresentation_UnlimitedText = 31,        // UT
+    ValueRepresentation_NotSupported               // Not supported by Orthanc, or tag not in dictionary
+  };
+
+
+  /**
+   * WARNING: Do not change the explicit values in the enumerations
+   * below this point. This would result in incompatible databases
+   * between versions of Orthanc!
+   **/
+
+  enum CompressionType
+  {
+    /**
+     * Buffer/file that is stored as-is, in a raw fashion, without
+     * compression.
+     **/
+    CompressionType_None = 1,
+
+    /**
+     * Buffer that is compressed using the "deflate" algorithm (RFC
+     * 1951), wrapped inside the zlib data format (RFC 1950), prefixed
+     * with a "uint64_t" (8 bytes) that encodes the size of the
+     * uncompressed buffer. If the compressed buffer is empty, its
+     * represents an empty uncompressed buffer. This format is
+     * internal to Orthanc. If the 8 first bytes are skipped AND the
+     * buffer is non-empty, the buffer is compatible with the
+     * "deflate" HTTP compression.
+     **/
+    CompressionType_ZlibWithSize = 2
+  };
+
+  enum FileContentType
+  {
+    // If you add a value below, insert it in "PluginStorageArea" in
+    // the file "Plugins/Engine/OrthancPlugins.cpp"
+    FileContentType_Unknown = 0,
+    FileContentType_Dicom = 1,
+    FileContentType_DicomAsJson = 2,
+
+    // Make sure that the value "65535" can be stored into this enumeration
+    FileContentType_StartUser = 1024,
+    FileContentType_EndUser = 65535
+  };
+
+  enum ResourceType
+  {
+    ResourceType_Patient = 1,
+    ResourceType_Study = 2,
+    ResourceType_Series = 3,
+    ResourceType_Instance = 4
+  };
+
+
+  const char* EnumerationToString(ErrorCode code);
+
+  const char* EnumerationToString(HttpMethod method);
+
+  const char* EnumerationToString(HttpStatus status);
+
+  const char* EnumerationToString(ResourceType type);
+
+  const char* EnumerationToString(ImageFormat format);
+
+  const char* EnumerationToString(Encoding encoding);
+
+  const char* EnumerationToString(PhotometricInterpretation photometric);
+
+  const char* EnumerationToString(LogLevel level);
+
+  const char* EnumerationToString(RequestOrigin origin);
+
+  const char* EnumerationToString(PixelFormat format);
+
+  Encoding StringToEncoding(const char* encoding);
+
+  ResourceType StringToResourceType(const char* type);
+
+  ImageFormat StringToImageFormat(const char* format);
+
+  LogLevel StringToLogLevel(const char* level);
+
+  ValueRepresentation StringToValueRepresentation(const std::string& vr,
+                                                  bool throwIfUnsupported);
+
+  unsigned int GetBytesPerPixel(PixelFormat format);
+
+  bool GetDicomEncoding(Encoding& encoding,
+                        const char* specificCharacterSet);
+
+  ResourceType GetChildResourceType(ResourceType type);
+
+  ResourceType GetParentResourceType(ResourceType type);
+
+  DicomModule GetModule(ResourceType type);
+
+  const char* GetDicomSpecificCharacterSet(Encoding encoding);
+
+  HttpStatus ConvertErrorCodeToHttpStatus(ErrorCode error);
+
+  bool IsUserContentType(FileContentType type);
+
+  bool IsBinaryValueRepresentation(ValueRepresentation vr);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Logging.cpp	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,543 @@
+/**
+ * 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 "Logging.h"
+
+#if ORTHANC_ENABLE_LOGGING != 1
+
+namespace Orthanc
+{
+  namespace Logging
+  {
+    void Initialize()
+    {
+    }
+
+    void Finalize()
+    {
+    }
+
+    void Reset()
+    {
+    }
+
+    void Flush()
+    {
+    }
+
+    void EnableInfoLevel(bool enabled)
+    {
+    }
+
+    void EnableTraceLevel(bool enabled)
+    {
+    }
+
+    void SetTargetFile(const std::string& path)
+    {
+    }
+
+    void SetTargetFolder(const std::string& path)
+    {
+    }
+  }
+}
+
+
+#elif ORTHANC_ENABLE_LOGGING_PLUGIN == 1
+
+/*********************************************************
+ * Logger compatible with the Orthanc plugin SDK
+ *********************************************************/
+
+#include <boost/lexical_cast.hpp>
+
+namespace Orthanc
+{
+  namespace Logging
+  {
+    static OrthancPluginContext* context_ = NULL;
+
+    void Initialize(OrthancPluginContext* context)
+    {
+      context_ = context;
+    }
+
+    InternalLogger::InternalLogger(const char* level,
+                                   const char* file  /* ignored */,
+                                   int line  /* ignored */) :
+      level_(level)
+    {
+    }
+
+    InternalLogger::~InternalLogger()
+    {
+      if (context_ != NULL)
+      {
+        if (level_ == "ERROR")
+        {
+          OrthancPluginLogError(context_, message_.c_str());
+        }
+        else if (level_ == "WARNING")
+        {
+          OrthancPluginLogWarning(context_, message_.c_str());
+        }
+        else if (level_ == "INFO")
+        {
+          OrthancPluginLogInfo(context_, message_.c_str());
+        }
+        else
+        {
+          std::string s = "Unknown log level (" + level_ + ") for message: " + message_;
+          OrthancPluginLogError(context_, s.c_str());
+        }
+      }
+    }
+
+    InternalLogger& InternalLogger::operator<< (const std::string& message)
+    {
+      message_ += message;
+      return *this;
+    }
+
+    InternalLogger& InternalLogger::operator<< (const char* message)
+    {
+      message_ += std::string(message);
+      return *this;
+    }
+
+    InternalLogger& InternalLogger::operator<< (int message)
+    {
+      message_ += boost::lexical_cast<std::string>(message);
+      return *this;
+    }
+  }
+}
+
+
+#else  /* ORTHANC_ENABLE_LOGGING_PLUGIN == 0 && ORTHANC_ENABLE_LOGGING == 1 */
+
+/*********************************************************
+ * Internal logger of Orthanc, that mimics some
+ * behavior from Google Log.
+ *********************************************************/
+
+#include "OrthancException.h"
+#include "Enumerations.h"
+#include "Toolbox.h"
+#include "SystemToolbox.h"
+
+#include <fstream>
+#include <boost/filesystem.hpp>
+#include <boost/thread.hpp>
+
+#if BOOST_HAS_DATE_TIME == 1
+#  include <boost/date_time/posix_time/posix_time.hpp>
+#else
+#  error Boost::date_time is required
+#endif
+
+
+namespace
+{
+  struct LoggingContext
+  {
+    bool infoEnabled_;
+    bool traceEnabled_;
+    std::string  targetFile_;
+    std::string  targetFolder_;
+
+    std::ostream* error_;
+    std::ostream* warning_;
+    std::ostream* info_;
+
+    std::auto_ptr<std::ofstream> file_;
+
+    LoggingContext() : 
+      infoEnabled_(false),
+      traceEnabled_(false),
+      error_(&std::cerr),
+      warning_(&std::cerr),
+      info_(&std::cerr)
+    {
+    }
+  };
+}
+
+
+
+static std::auto_ptr<LoggingContext> loggingContext_;
+static boost::mutex  loggingMutex_;
+
+
+
+namespace Orthanc
+{
+  namespace Logging
+  {
+    static void GetLogPath(boost::filesystem::path& log,
+                           boost::filesystem::path& link,
+                           const std::string& suffix,
+                           const std::string& directory)
+    {
+      /**
+         From Google Log documentation:
+
+         Unless otherwise specified, logs will be written to the filename
+         "<program name>.<hostname>.<user name>.log<suffix>.",
+         followed by the date, time, and pid (you can't prevent the date,
+         time, and pid from being in the filename).
+
+         In this implementation : "hostname" and "username" are not used
+      **/
+
+      boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
+      boost::filesystem::path root(directory);
+      boost::filesystem::path exe(SystemToolbox::GetPathToExecutable());
+      
+      if (!boost::filesystem::exists(root) ||
+          !boost::filesystem::is_directory(root))
+      {
+        throw OrthancException(ErrorCode_CannotWriteFile);
+      }
+
+      char date[64];
+      sprintf(date, "%04d%02d%02d-%02d%02d%02d.%d",
+              static_cast<int>(now.date().year()),
+              now.date().month().as_number(),
+              now.date().day().as_number(),
+              now.time_of_day().hours(),
+              now.time_of_day().minutes(),
+              now.time_of_day().seconds(),
+              SystemToolbox::GetProcessId());
+
+      std::string programName = exe.filename().replace_extension("").string();
+
+      log = (root / (programName + ".log" + suffix + "." + std::string(date)));
+      link = (root / (programName + ".log" + suffix));
+    }
+
+
+    static void PrepareLogFolder(std::auto_ptr<std::ofstream>& file,
+                                 const std::string& suffix,
+                                 const std::string& directory)
+    {
+      boost::filesystem::path log, link;
+      GetLogPath(log, link, suffix, directory);
+
+#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)))
+      boost::filesystem::remove(link);
+      boost::filesystem::create_symlink(log.filename(), link);
+#endif
+
+      file.reset(new std::ofstream(log.string().c_str()));
+    }
+
+
+    void Initialize()
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+      loggingContext_.reset(new LoggingContext);
+    }
+
+    void Finalize()
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+      loggingContext_.reset(NULL);
+    }
+
+    void Reset()
+    {
+      // Recover the old logging context
+      std::auto_ptr<LoggingContext> old;
+
+      {
+        boost::mutex::scoped_lock lock(loggingMutex_);
+        if (loggingContext_.get() == NULL)
+        {
+          return;
+        }
+        else
+        {
+          old = loggingContext_;
+
+          // Create a new logging context, 
+          loggingContext_.reset(new LoggingContext);
+        }
+      }
+      
+      EnableInfoLevel(old->infoEnabled_);
+      EnableTraceLevel(old->traceEnabled_);
+
+      if (!old->targetFolder_.empty())
+      {
+        SetTargetFolder(old->targetFolder_);
+      }
+      else if (!old->targetFile_.empty())
+      {
+        SetTargetFile(old->targetFile_);
+      }
+    }
+
+    void EnableInfoLevel(bool enabled)
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+      assert(loggingContext_.get() != NULL);
+
+      loggingContext_->infoEnabled_ = enabled;
+      
+      if (!enabled)
+      {
+        // Also disable the "TRACE" level when info-level debugging is disabled
+        loggingContext_->traceEnabled_ = false;
+      }
+    }
+
+    void EnableTraceLevel(bool enabled)
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+      assert(loggingContext_.get() != NULL);
+
+      loggingContext_->traceEnabled_ = enabled;
+      
+      if (enabled)
+      {
+        // Also enable the "INFO" level when trace-level debugging is enabled
+        loggingContext_->infoEnabled_ = true;
+      }
+    }
+
+
+    static void CheckFile(std::auto_ptr<std::ofstream>& f)
+    {
+      if (loggingContext_->file_.get() == NULL ||
+          !loggingContext_->file_->is_open())
+      {
+        throw OrthancException(ErrorCode_CannotWriteFile);
+      }
+    }
+
+    void SetTargetFolder(const std::string& path)
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+      assert(loggingContext_.get() != NULL);
+
+      PrepareLogFolder(loggingContext_->file_, "" /* no suffix */, path);
+      CheckFile(loggingContext_->file_);
+
+      loggingContext_->targetFile_.clear();
+      loggingContext_->targetFolder_ = path;
+      loggingContext_->warning_ = loggingContext_->file_.get();
+      loggingContext_->error_ = loggingContext_->file_.get();
+      loggingContext_->info_ = loggingContext_->file_.get();
+    }
+
+
+    void SetTargetFile(const std::string& path)
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+      assert(loggingContext_.get() != NULL);
+
+      loggingContext_->file_.reset(new std::ofstream(path.c_str(), std::fstream::app));
+      CheckFile(loggingContext_->file_);
+
+      loggingContext_->targetFile_ = path;
+      loggingContext_->targetFolder_.clear();
+      loggingContext_->warning_ = loggingContext_->file_.get();
+      loggingContext_->error_ = loggingContext_->file_.get();
+      loggingContext_->info_ = loggingContext_->file_.get();
+    }
+
+
+    InternalLogger::InternalLogger(const char* level,
+                                   const char* file,
+                                   int line) : 
+      lock_(loggingMutex_), 
+      stream_(&null_)  // By default, logging to "/dev/null" is simulated
+    {
+      if (loggingContext_.get() == NULL)
+      {
+        fprintf(stderr, "ERROR: Trying to log a message after the finalization of the logging engine\n");
+        return;
+      }
+
+      try
+      {
+        LogLevel l = StringToLogLevel(level);
+      
+        if ((l == LogLevel_Info  && !loggingContext_->infoEnabled_) ||
+            (l == LogLevel_Trace && !loggingContext_->traceEnabled_))
+        {
+          // This logging level is disabled, directly exit and unlock
+          // the mutex to speed-up things. The stream is set to "/dev/null"
+          lock_.unlock();
+          return;
+        }
+
+        // Compute the header of the line, temporary release the lock as
+        // this is a time-consuming operation
+        lock_.unlock();
+        std::string header;
+
+        {
+          boost::filesystem::path path(file);
+          boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time();
+          boost::posix_time::time_duration duration = now.time_of_day();
+
+          /**
+             From Google Log documentation:
+
+             "Log lines have this form:
+
+             Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg...
+
+             where the fields are defined as follows:
+
+             L                A single character, representing the log level (eg 'I' for INFO)
+             mm               The month (zero padded; ie May is '05')
+             dd               The day (zero padded)
+             hh:mm:ss.uuuuuu  Time in hours, minutes and fractional seconds
+             threadid         The space-padded thread ID as returned by GetTID() (this matches the PID on Linux)
+             file             The file name
+             line             The line number
+             msg              The user-supplied message"
+
+             In this implementation, "threadid" is not printed.
+          **/
+
+          char date[32];
+          sprintf(date, "%c%02d%02d %02d:%02d:%02d.%06d ",
+                  level[0],
+                  now.date().month().as_number(),
+                  now.date().day().as_number(),
+                  duration.hours(),
+                  duration.minutes(),
+                  duration.seconds(),
+                  static_cast<int>(duration.fractional_seconds()));
+
+          header = std::string(date) + path.filename().string() + ":" + boost::lexical_cast<std::string>(line) + "] ";
+        }
+
+
+        // The header is computed, we now re-lock the mutex to access
+        // the stream objects. Pay attention that "loggingContext_",
+        // "infoEnabled_" or "traceEnabled_" might have changed while
+        // the mutex was unlocked.
+        lock_.lock();
+
+        if (loggingContext_.get() == NULL)
+        {
+          fprintf(stderr, "ERROR: Trying to log a message after the finalization of the logging engine\n");
+          return;
+        }
+
+        switch (l)
+        {
+          case LogLevel_Error:
+            stream_ = loggingContext_->error_;
+            break;
+
+          case LogLevel_Warning:
+            stream_ = loggingContext_->warning_;
+            break;
+
+          case LogLevel_Info:
+            if (loggingContext_->infoEnabled_)
+            {
+              stream_ = loggingContext_->info_;
+            }
+
+            break;
+
+          case LogLevel_Trace:
+            if (loggingContext_->traceEnabled_)
+            {
+              stream_ = loggingContext_->info_;
+            }
+
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_InternalError);
+        }
+
+        if (stream_ == &null_)
+        {
+          // The logging is disabled for this level. The stream is the
+          // "null_" member of this object, so we can release the global
+          // mutex.
+          lock_.unlock();
+        }
+
+        (*stream_) << header;
+      }
+      catch (...)
+      { 
+        // Something is going really wrong, probably running out of
+        // memory. Fallback to a degraded mode.
+        stream_ = loggingContext_->error_;
+        (*stream_) << "E???? ??:??:??.?????? ] ";
+      }
+    }
+
+
+    InternalLogger::~InternalLogger()
+    {
+      if (stream_ != &null_)
+      {
+#if defined(_WIN32)
+        *stream_ << "\r\n";
+#else
+        *stream_ << "\n";
+#endif
+
+        stream_->flush();
+      }
+    }
+      
+
+    void Flush()
+    {
+      boost::mutex::scoped_lock lock(loggingMutex_);
+
+      if (loggingContext_.get() != NULL &&
+          loggingContext_->file_.get() != NULL)
+      {
+        loggingContext_->file_->flush();
+      }
+    }
+  }
+}
+
+#endif   // ORTHANC_ENABLE_LOGGING
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Logging.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,178 @@
+/**
+ * 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
+
+#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();
+
+    void Reset();
+
+    void Flush();
+
+    void EnableInfoLevel(bool enabled);
+
+    void EnableTraceLevel(bool enabled);
+
+    void SetTargetFile(const std::string& path);
+
+    void SetTargetFolder(const std::string& path);
+
+    struct NullStream : public std::ostream 
+    {
+      NullStream() : 
+        std::ios(0), 
+        std::ostream(0)
+      {
+      }
+      
+      std::ostream& operator<< (const std::string& message)
+      {
+        return *this;
+      }
+
+      // This overload fixes build problems with Visual Studio 2015
+      std::ostream& operator<< (const char* message)
+      {
+        return *this;
+      }
+    };
+  }
+}
+
+
+#if ORTHANC_ENABLE_LOGGING != 1
+
+#  define LOG(level)   ::Orthanc::Logging::NullStream()
+#  define VLOG(level)  ::Orthanc::Logging::NullStream()
+
+
+#elif ORTHANC_ENABLE_LOGGING_PLUGIN == 1
+
+#  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__)
+#  define VLOG(level) ::Orthanc::Logging::InternalLogger("TRACE", __FILE__, __LINE__)
+
+namespace Orthanc
+{
+  namespace Logging
+  {
+    class InternalLogger
+    {
+    private:
+      boost::mutex::scoped_lock lock_;
+      NullStream                null_;
+      std::ostream*             stream_;
+
+    public:
+      InternalLogger(const char* level,
+                     const char* file,
+                     int line);
+
+      ~InternalLogger();
+      
+      std::ostream& operator<< (const std::string& message)
+      {
+        return (*stream_) << message;
+      }
+
+      // This overload fixes build problems with Visual Studio 2015
+      std::ostream& operator<< (const char* message)
+      {
+        return (*stream_) << message;
+      }
+    };
+  }
+}
+
+#endif  // ORTHANC_ENABLE_LOGGING
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/OrthancException.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,77 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 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
+
+#include <stdint.h>
+#include <string>
+#include "Enumerations.h"
+
+namespace Orthanc
+{
+  class OrthancException
+  {
+  protected:
+    ErrorCode  errorCode_;
+    HttpStatus httpStatus_;
+
+  public:
+    explicit OrthancException(ErrorCode errorCode) : 
+      errorCode_(errorCode),
+      httpStatus_(ConvertErrorCodeToHttpStatus(errorCode))
+    {
+    }
+
+    OrthancException(ErrorCode errorCode,
+                     HttpStatus httpStatus) :
+      errorCode_(errorCode),
+      httpStatus_(httpStatus)
+    {
+    }
+
+    ErrorCode GetErrorCode() const
+    {
+      return errorCode_;
+    }
+
+    HttpStatus GetHttpStatus() const
+    {
+      return httpStatus_;
+    }
+
+    const char* What() const
+    {
+      return EnumerationToString(errorCode_);
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/PrecompiledHeaders.cpp	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,34 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 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"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/PrecompiledHeaders.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,61 @@
+/**
+ * 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(_WIN32) && !defined(NOMINMAX)
+#define NOMINMAX
+#endif
+
+#if ORTHANC_USE_PRECOMPILED_HEADERS == 1
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/filesystem.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/locale.hpp>
+#include <boost/regex.hpp>
+#include <boost/thread.hpp>
+#include <boost/thread/shared_mutex.hpp>
+
+#include <json/value.h>
+
+#if ORTHANC_ENABLE_PUGIXML == 1
+#include <pugixml.hpp>
+#endif
+
+#include "Enumerations.h"
+#include "Logging.h"
+#include "OrthancException.h"
+#include "Toolbox.h"
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Toolbox.cpp	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,1254 @@
+/**
+ * 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 "Toolbox.h"
+
+#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 <algorithm>
+#include <ctype.h>
+
+#if BOOST_HAS_REGEX == 1
+#  include <boost/regex.hpp> 
+#endif
+
+#if BOOST_HAS_LOCALE != 1
+#  error Since version 0.7.6, Orthanc entirely relies on boost::locale
+#endif
+
+#if ORTHANC_ENABLE_MD5 == 1
+#  include "../Resources/ThirdParty/md5/md5.h"
+#endif
+
+#if ORTHANC_ENABLE_BASE64 == 1
+#  include "../Resources/ThirdParty/base64/base64.h"
+#endif
+
+
+#if defined(_MSC_VER) && (_MSC_VER < 1800)
+// Patch for the missing "_strtoll" symbol when compiling with Visual Studio < 2013
+extern "C"
+{
+  int64_t _strtoi64(const char *nptr, char **endptr, int base);
+  int64_t strtoll(const char *nptr, char **endptr, int base)
+  {
+    return _strtoi64(nptr, endptr, base);
+  } 
+}
+#endif
+
+
+#if defined(_WIN32)
+#  include <windows.h>   // For ::Sleep
+#endif
+
+
+#if ORTHANC_ENABLE_PUGIXML == 1
+#  include "ChunkedBuffer.h"
+#  include <pugixml.hpp>
+#endif
+
+
+namespace Orthanc
+{
+  void Toolbox::ToUpperCase(std::string& s)
+  {
+    std::transform(s.begin(), s.end(), s.begin(), toupper);
+  }
+
+
+  void Toolbox::ToLowerCase(std::string& s)
+  {
+    std::transform(s.begin(), s.end(), s.begin(), tolower);
+  }
+
+
+  void Toolbox::ToUpperCase(std::string& result,
+                            const std::string& source)
+  {
+    result = source;
+    ToUpperCase(result);
+  }
+
+  void Toolbox::ToLowerCase(std::string& result,
+                            const std::string& source)
+  {
+    result = source;
+    ToLowerCase(result);
+  }
+
+
+  void Toolbox::SplitUriComponents(UriComponents& components,
+                                   const std::string& uri)
+  {
+    static const char URI_SEPARATOR = '/';
+
+    components.clear();
+
+    if (uri.size() == 0 ||
+        uri[0] != URI_SEPARATOR)
+    {
+      throw OrthancException(ErrorCode_UriSyntax);
+    }
+
+    // Count the number of slashes in the URI to make an assumption
+    // about the number of components in the URI
+    unsigned int estimatedSize = 0;
+    for (unsigned int i = 0; i < uri.size(); i++)
+    {
+      if (uri[i] == URI_SEPARATOR)
+        estimatedSize++;
+    }
+
+    components.reserve(estimatedSize - 1);
+
+    unsigned int start = 1;
+    unsigned int end = 1;
+    while (end < uri.size())
+    {
+      // This is the loop invariant
+      assert(uri[start - 1] == '/' && (end >= start));
+
+      if (uri[end] == '/')
+      {
+        components.push_back(std::string(&uri[start], end - start));
+        end++;
+        start = end;
+      }
+      else
+      {
+        end++;
+      }
+    }
+
+    if (start < uri.size())
+    {
+      components.push_back(std::string(&uri[start], end - start));
+    }
+
+    for (size_t i = 0; i < components.size(); i++)
+    {
+      if (components[i].size() == 0)
+      {
+        // Empty component, as in: "/coucou//e"
+        throw OrthancException(ErrorCode_UriSyntax);
+      }
+    }
+  }
+
+
+  void Toolbox::TruncateUri(UriComponents& target,
+                            const UriComponents& source,
+                            size_t fromLevel)
+  {
+    target.clear();
+
+    if (source.size() > fromLevel)
+    {
+      target.resize(source.size() - fromLevel);
+
+      size_t j = 0;
+      for (size_t i = fromLevel; i < source.size(); i++, j++)
+      {
+        target[j] = source[i];
+      }
+
+      assert(j == target.size());
+    }
+  }
+  
+
+
+  bool Toolbox::IsChildUri(const UriComponents& baseUri,
+                           const UriComponents& testedUri)
+  {
+    if (testedUri.size() < baseUri.size())
+    {
+      return false;
+    }
+
+    for (size_t i = 0; i < baseUri.size(); i++)
+    {
+      if (baseUri[i] != testedUri[i])
+        return false;
+    }
+
+    return true;
+  }
+
+
+  std::string Toolbox::AutodetectMimeType(const std::string& path)
+  {
+    std::string contentType;
+    size_t lastDot = path.rfind('.');
+    size_t lastSlash = path.rfind('/');
+
+    if (lastDot == std::string::npos ||
+        (lastSlash != std::string::npos && lastDot < lastSlash))
+    {
+      // No trailing dot, unable to detect the content type
+    }
+    else
+    {
+      const char* extension = &path[lastDot + 1];
+    
+      // http://en.wikipedia.org/wiki/Mime_types
+      // Text types
+      if (!strcmp(extension, "txt"))
+        contentType = "text/plain";
+      else if (!strcmp(extension, "html"))
+        contentType = "text/html";
+      else if (!strcmp(extension, "xml"))
+        contentType = "text/xml";
+      else if (!strcmp(extension, "css"))
+        contentType = "text/css";
+
+      // Application types
+      else if (!strcmp(extension, "js"))
+        contentType = "application/javascript";
+      else if (!strcmp(extension, "json"))
+        contentType = "application/json";
+      else if (!strcmp(extension, "pdf"))
+        contentType = "application/pdf";
+
+      // Images types
+      else if (!strcmp(extension, "jpg") || !strcmp(extension, "jpeg"))
+        contentType = "image/jpeg";
+      else if (!strcmp(extension, "gif"))
+        contentType = "image/gif";
+      else if (!strcmp(extension, "png"))
+        contentType = "image/png";
+    }
+
+    return contentType;
+  }
+
+
+  std::string Toolbox::FlattenUri(const UriComponents& components,
+                                  size_t fromLevel)
+  {
+    if (components.size() <= fromLevel)
+    {
+      return "/";
+    }
+    else
+    {
+      std::string r;
+
+      for (size_t i = fromLevel; i < components.size(); i++)
+      {
+        r += "/" + components[i];
+      }
+
+      return r;
+    }
+  }
+
+
+#if ORTHANC_ENABLE_MD5 == 1
+  static char GetHexadecimalCharacter(uint8_t value)
+  {
+    assert(value < 16);
+
+    if (value < 10)
+    {
+      return value + '0';
+    }
+    else
+    {
+      return (value - 10) + 'a';
+    }
+  }
+
+
+  void Toolbox::ComputeMD5(std::string& result,
+                           const std::string& data)
+  {
+    if (data.size() > 0)
+    {
+      ComputeMD5(result, &data[0], data.size());
+    }
+    else
+    {
+      ComputeMD5(result, NULL, 0);
+    }
+  }
+
+
+  void Toolbox::ComputeMD5(std::string& result,
+                           const void* data,
+                           size_t size)
+  {
+    md5_state_s state;
+    md5_init(&state);
+
+    if (size > 0)
+    {
+      md5_append(&state, 
+                 reinterpret_cast<const md5_byte_t*>(data), 
+                 static_cast<int>(size));
+    }
+
+    md5_byte_t actualHash[16];
+    md5_finish(&state, actualHash);
+
+    result.resize(32);
+    for (unsigned int i = 0; i < 16; i++)
+    {
+      result[2 * i] = GetHexadecimalCharacter(static_cast<uint8_t>(actualHash[i] / 16));
+      result[2 * i + 1] = GetHexadecimalCharacter(static_cast<uint8_t>(actualHash[i] % 16));
+    }
+  }
+#endif
+
+
+#if ORTHANC_ENABLE_BASE64 == 1
+  void Toolbox::EncodeBase64(std::string& result, 
+                             const std::string& data)
+  {
+    result = base64_encode(data);
+  }
+
+  void Toolbox::DecodeBase64(std::string& result, 
+                             const std::string& data)
+  {
+    for (size_t i = 0; i < data.length(); i++)
+    {
+      if (!isalnum(data[i]) &&
+          data[i] != '+' &&
+          data[i] != '/' &&
+          data[i] != '=')
+      {
+        // This is not a valid character for a Base64 string
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+    }
+
+    result = base64_decode(data);
+  }
+
+
+#  if BOOST_HAS_REGEX == 1
+  bool Toolbox::DecodeDataUriScheme(std::string& mime,
+                                    std::string& content,
+                                    const std::string& source)
+  {
+    boost::regex pattern("data:([^;]+);base64,([a-zA-Z0-9=+/]*)",
+                         boost::regex::icase /* case insensitive search */);
+
+    boost::cmatch what;
+    if (regex_match(source.c_str(), what, pattern))
+    {
+      mime = what[1];
+      DecodeBase64(content, what[2]);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+#  endif
+
+
+  void Toolbox::EncodeDataUriScheme(std::string& result,
+                                    const std::string& mime,
+                                    const std::string& content)
+  {
+    result = "data:" + mime + ";base64," + base64_encode(content);
+  }
+
+#endif
+
+
+  static const char* GetBoostLocaleEncoding(const Encoding sourceEncoding)
+  {
+    switch (sourceEncoding)
+    {
+      case Encoding_Utf8:
+        return "UTF-8";
+
+      case Encoding_Ascii:
+        return "ASCII";
+
+      case Encoding_Latin1:
+        return "ISO-8859-1";
+        break;
+
+      case Encoding_Latin2:
+        return "ISO-8859-2";
+        break;
+
+      case Encoding_Latin3:
+        return "ISO-8859-3";
+        break;
+
+      case Encoding_Latin4:
+        return "ISO-8859-4";
+        break;
+
+      case Encoding_Latin5:
+        return "ISO-8859-9";
+        break;
+
+      case Encoding_Cyrillic:
+        return "ISO-8859-5";
+        break;
+
+      case Encoding_Windows1251:
+        return "WINDOWS-1251";
+        break;
+
+      case Encoding_Arabic:
+        return "ISO-8859-6";
+        break;
+
+      case Encoding_Greek:
+        return "ISO-8859-7";
+        break;
+
+      case Encoding_Hebrew:
+        return "ISO-8859-8";
+        break;
+        
+      case Encoding_Japanese:
+        return "SHIFT-JIS";
+        break;
+
+      case Encoding_Chinese:
+        return "GB18030";
+        break;
+
+      case Encoding_Thai:
+        return "TIS620.2533-0";
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  std::string Toolbox::ConvertToUtf8(const std::string& source,
+                                     Encoding sourceEncoding)
+  {
+    if (sourceEncoding == Encoding_Utf8)
+    {
+      // Already in UTF-8: No conversion is required
+      return source;
+    }
+
+    if (sourceEncoding == Encoding_Ascii)
+    {
+      return ConvertToAscii(source);
+    }
+
+    const char* encoding = GetBoostLocaleEncoding(sourceEncoding);
+
+    try
+    {
+      return boost::locale::conv::to_utf<char>(source, encoding);
+    }
+    catch (std::runtime_error&)
+    {
+      // Bad input string or bad encoding
+      return ConvertToAscii(source);
+    }
+  }
+
+
+  std::string Toolbox::ConvertFromUtf8(const std::string& source,
+                                       Encoding targetEncoding)
+  {
+    if (targetEncoding == Encoding_Utf8)
+    {
+      // Already in UTF-8: No conversion is required
+      return source;
+    }
+
+    if (targetEncoding == Encoding_Ascii)
+    {
+      return ConvertToAscii(source);
+    }
+
+    const char* encoding = GetBoostLocaleEncoding(targetEncoding);
+
+    try
+    {
+      return boost::locale::conv::from_utf<char>(source, encoding);
+    }
+    catch (std::runtime_error&)
+    {
+      // Bad input string or bad encoding
+      return ConvertToAscii(source);
+    }
+  }
+
+
+  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;
+
+    result.reserve(source.size() + 1);
+    for (size_t i = 0; i < source.size(); i++)
+    {
+      if (source[i] <= 127 && source[i] >= 0 && !iscntrl(source[i]))
+      {
+        result.push_back(source[i]);
+      }
+    }
+
+    return result;
+  }
+
+
+  void Toolbox::ComputeSHA1(std::string& result,
+                            const void* data,
+                            size_t size)
+  {
+    boost::uuids::detail::sha1 sha1;
+
+    if (size > 0)
+    {
+      sha1.process_bytes(data, size);
+    }
+
+    unsigned int digest[5];
+
+    // Sanity check for the memory layout: A SHA-1 digest is 160 bits wide
+    assert(sizeof(unsigned int) == 4 && sizeof(digest) == (160 / 8)); 
+    
+    sha1.get_digest(digest);
+
+    result.resize(8 * 5 + 4);
+    sprintf(&result[0], "%08x-%08x-%08x-%08x-%08x",
+            digest[0],
+            digest[1],
+            digest[2],
+            digest[3],
+            digest[4]);
+  }
+
+  void Toolbox::ComputeSHA1(std::string& result,
+                            const std::string& data)
+  {
+    if (data.size() > 0)
+    {
+      ComputeSHA1(result, data.c_str(), data.size());
+    }
+    else
+    {
+      ComputeSHA1(result, NULL, 0);
+    }
+  }
+
+
+  bool Toolbox::IsSHA1(const char* str,
+                       size_t size)
+  {
+    if (size == 0)
+    {
+      return false;
+    }
+
+    const char* start = str;
+    const char* end = str + size;
+
+    // Trim the beginning of the string
+    while (start < end)
+    {
+      if (*start == '\0' ||
+          isspace(*start))
+      {
+        start++;
+      }
+      else
+      {
+        break;
+      }
+    }
+
+    // Trim the trailing of the string
+    while (start < end)
+    {
+      if (*(end - 1) == '\0' ||
+          isspace(*(end - 1)))
+      {
+        end--;
+      }
+      else
+      {
+        break;
+      }
+    }
+
+    if (end - start != 44)
+    {
+      return false;
+    }
+
+    for (unsigned int i = 0; i < 44; i++)
+    {
+      if (i == 8 ||
+          i == 17 ||
+          i == 26 ||
+          i == 35)
+      {
+        if (start[i] != '-')
+          return false;
+      }
+      else
+      {
+        if (!isalnum(start[i]))
+          return false;
+      }
+    }
+
+    return true;
+  }
+
+
+  bool Toolbox::IsSHA1(const std::string& s)
+  {
+    if (s.size() == 0)
+    {
+      return false;
+    }
+    else
+    {
+      return IsSHA1(s.c_str(), s.size());
+    }
+  }
+
+
+  std::string Toolbox::StripSpaces(const std::string& source)
+  {
+    size_t first = 0;
+
+    while (first < source.length() &&
+           isspace(source[first]))
+    {
+      first++;
+    }
+
+    if (first == source.length())
+    {
+      // String containing only spaces
+      return "";
+    }
+
+    size_t last = source.length();
+    while (last > first &&
+           isspace(source[last - 1]))
+    {
+      last--;
+    }          
+    
+    assert(first <= last);
+    return source.substr(first, last - first);
+  }
+
+
+  static char Hex2Dec(char c)
+  {
+    return ((c >= '0' && c <= '9') ? c - '0' :
+            ((c >= 'a' && c <= 'f') ? c - 'a' + 10 : c - 'A' + 10));
+  }
+
+  void Toolbox::UrlDecode(std::string& s)
+  {
+    // http://en.wikipedia.org/wiki/Percent-encoding
+    // http://www.w3schools.com/tags/ref_urlencode.asp
+    // http://stackoverflow.com/questions/154536/encode-decode-urls-in-c
+
+    if (s.size() == 0)
+    {
+      return;
+    }
+
+    size_t source = 0;
+    size_t target = 0;
+
+    while (source < s.size())
+    {
+      if (s[source] == '%' &&
+          source + 2 < s.size() &&
+          isalnum(s[source + 1]) &&
+          isalnum(s[source + 2]))
+      {
+        s[target] = (Hex2Dec(s[source + 1]) << 4) | Hex2Dec(s[source + 2]);
+        source += 3;
+        target += 1;
+      }
+      else
+      {
+        if (s[source] == '+')
+          s[target] = ' ';
+        else
+          s[target] = s[source];
+
+        source++;
+        target++;
+      }
+    }
+
+    s.resize(target);
+  }
+
+
+  Endianness Toolbox::DetectEndianness()
+  {
+    // http://sourceforge.net/p/predef/wiki/Endianness/
+
+    uint8_t buffer[4];
+
+    buffer[0] = 0x00;
+    buffer[1] = 0x01;
+    buffer[2] = 0x02;
+    buffer[3] = 0x03;
+
+    switch (*((uint32_t *)buffer)) 
+    {
+      case 0x00010203: 
+        return Endianness_Big;
+
+      case 0x03020100: 
+        return Endianness_Little;
+        
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+#if BOOST_HAS_REGEX == 1
+  std::string Toolbox::WildcardToRegularExpression(const std::string& source)
+  {
+    // TODO - Speed up this with a regular expression
+
+    std::string result = source;
+
+    // Escape all special characters
+    boost::replace_all(result, "\\", "\\\\");
+    boost::replace_all(result, "^", "\\^");
+    boost::replace_all(result, ".", "\\.");
+    boost::replace_all(result, "$", "\\$");
+    boost::replace_all(result, "|", "\\|");
+    boost::replace_all(result, "(", "\\(");
+    boost::replace_all(result, ")", "\\)");
+    boost::replace_all(result, "[", "\\[");
+    boost::replace_all(result, "]", "\\]");
+    boost::replace_all(result, "+", "\\+");
+    boost::replace_all(result, "/", "\\/");
+    boost::replace_all(result, "{", "\\{");
+    boost::replace_all(result, "}", "\\}");
+
+    // Convert wildcards '*' and '?' to their regex equivalents
+    boost::replace_all(result, "?", ".");
+    boost::replace_all(result, "*", ".*");
+
+    return result;
+  }
+#endif
+
+
+
+  void Toolbox::TokenizeString(std::vector<std::string>& result,
+                               const std::string& value,
+                               char separator)
+  {
+    result.clear();
+
+    std::string currentItem;
+
+    for (size_t i = 0; i < value.size(); i++)
+    {
+      if (value[i] == separator)
+      {
+        result.push_back(currentItem);
+        currentItem.clear();
+      }
+      else
+      {
+        currentItem.push_back(value[i]);
+      }
+    }
+
+    result.push_back(currentItem);
+  }
+
+
+#if ORTHANC_ENABLE_PUGIXML == 1
+  class ChunkedBufferWriter : public pugi::xml_writer
+  {
+  private:
+    ChunkedBuffer buffer_;
+
+  public:
+    virtual void write(const void *data, size_t size)
+    {
+      if (size > 0)
+      {
+        buffer_.AddChunk(reinterpret_cast<const char*>(data), size);
+      }
+    }
+
+    void Flatten(std::string& s)
+    {
+      buffer_.Flatten(s);
+    }
+  };
+
+
+  static void JsonToXmlInternal(pugi::xml_node& target,
+                                const Json::Value& source,
+                                const std::string& arrayElement)
+  {
+    // http://jsoncpp.sourceforge.net/value_8h_source.html#l00030
+
+    switch (source.type())
+    {
+      case Json::nullValue:
+      {
+        target.append_child(pugi::node_pcdata).set_value("null");
+        break;
+      }
+
+      case Json::intValue:
+      {
+        std::string s = boost::lexical_cast<std::string>(source.asInt());
+        target.append_child(pugi::node_pcdata).set_value(s.c_str());
+        break;
+      }
+
+      case Json::uintValue:
+      {
+        std::string s = boost::lexical_cast<std::string>(source.asUInt());
+        target.append_child(pugi::node_pcdata).set_value(s.c_str());
+        break;
+      }
+
+      case Json::realValue:
+      {
+        std::string s = boost::lexical_cast<std::string>(source.asFloat());
+        target.append_child(pugi::node_pcdata).set_value(s.c_str());
+        break;
+      }
+
+      case Json::stringValue:
+      {
+        target.append_child(pugi::node_pcdata).set_value(source.asString().c_str());
+        break;
+      }
+
+      case Json::booleanValue:
+      {
+        target.append_child(pugi::node_pcdata).set_value(source.asBool() ? "true" : "false");
+        break;
+      }
+
+      case Json::arrayValue:
+      {
+        for (Json::Value::ArrayIndex i = 0; i < source.size(); i++)
+        {
+          pugi::xml_node node = target.append_child();
+          node.set_name(arrayElement.c_str());
+          JsonToXmlInternal(node, source[i], arrayElement);
+        }
+        break;
+      }
+        
+      case Json::objectValue:
+      {
+        Json::Value::Members members = source.getMemberNames();
+
+        for (size_t i = 0; i < members.size(); i++)
+        {
+          pugi::xml_node node = target.append_child();
+          node.set_name(members[i].c_str());
+          JsonToXmlInternal(node, source[members[i]], arrayElement);          
+        }
+
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void Toolbox::JsonToXml(std::string& target,
+                          const Json::Value& source,
+                          const std::string& rootElement,
+                          const std::string& arrayElement)
+  {
+    pugi::xml_document doc;
+
+    pugi::xml_node n = doc.append_child(rootElement.c_str());
+    JsonToXmlInternal(n, source, arrayElement);
+
+    pugi::xml_node decl = doc.prepend_child(pugi::node_declaration);
+    decl.append_attribute("version").set_value("1.0");
+    decl.append_attribute("encoding").set_value("utf-8");
+
+    ChunkedBufferWriter writer;
+    doc.save(writer, "  ", pugi::format_default, pugi::encoding_utf8);
+    writer.Flatten(target);
+  }
+
+#endif
+
+
+  
+  bool Toolbox::IsInteger(const std::string& str)
+  {
+    std::string s = StripSpaces(str);
+
+    if (s.size() == 0)
+    {
+      return false;
+    }
+
+    size_t pos = 0;
+    if (s[0] == '-')
+    {
+      if (s.size() == 1)
+      {
+        return false;
+      }
+
+      pos = 1;
+    }
+
+    while (pos < s.size())
+    {
+      if (!isdigit(s[pos]))
+      {
+        return false;
+      }
+
+      pos++;
+    }
+
+    return true;
+  }
+
+
+  void Toolbox::CopyJsonWithoutComments(Json::Value& target,
+                                        const Json::Value& source)
+  {
+    switch (source.type())
+    {
+      case Json::nullValue:
+        target = Json::nullValue;
+        break;
+
+      case Json::intValue:
+        target = source.asInt64();
+        break;
+
+      case Json::uintValue:
+        target = source.asUInt64();
+        break;
+
+      case Json::realValue:
+        target = source.asDouble();
+        break;
+
+      case Json::stringValue:
+        target = source.asString();
+        break;
+
+      case Json::booleanValue:
+        target = source.asBool();
+        break;
+
+      case Json::arrayValue:
+      {
+        target = Json::arrayValue;
+        for (Json::Value::ArrayIndex i = 0; i < source.size(); i++)
+        {
+          Json::Value& item = target.append(Json::nullValue);
+          CopyJsonWithoutComments(item, source[i]);
+        }
+
+        break;
+      }
+
+      case Json::objectValue:
+      {
+        target = Json::objectValue;
+        Json::Value::Members members = source.getMemberNames();
+        for (Json::Value::ArrayIndex i = 0; i < members.size(); i++)
+        {
+          const std::string item = members[i];
+          CopyJsonWithoutComments(target[item], source[item]);
+        }
+
+        break;
+      }
+
+      default:
+        break;
+    }
+  }
+
+
+  bool Toolbox::StartsWith(const std::string& str,
+                           const std::string& prefix)
+  {
+    if (str.size() < prefix.size())
+    {
+      return false;
+    }
+    else
+    {
+      return str.compare(0, prefix.size(), prefix) == 0;
+    }
+  }
+  
+
+  static bool IsUnreservedCharacter(char c)
+  {
+    // This function checks whether "c" is an unserved character
+    // wrt. an URI percent-encoding
+    // https://en.wikipedia.org/wiki/Percent-encoding#Percent-encoding%5Fin%5Fa%5FURI
+
+    return ((c >= 'A' && c <= 'Z') ||
+            (c >= 'a' && c <= 'z') ||
+            (c >= '0' && c <= '9') ||
+            c == '-' ||
+            c == '_' ||
+            c == '.' ||
+            c == '~');
+  }
+
+  void Toolbox::UriEncode(std::string& target,
+                          const std::string& source)
+  {
+    // Estimate the length of the percent-encoded URI
+    size_t length = 0;
+
+    for (size_t i = 0; i < source.size(); i++)
+    {
+      if (IsUnreservedCharacter(source[i]))
+      {
+        length += 1;
+      }
+      else
+      {
+        // This character must be percent-encoded
+        length += 3;
+      }
+    }
+
+    target.clear();
+    target.reserve(length);
+
+    for (size_t i = 0; i < source.size(); i++)
+    {
+      if (IsUnreservedCharacter(source[i]))
+      {
+        target.push_back(source[i]);
+      }
+      else
+      {
+        // This character must be percent-encoded
+        uint8_t byte = static_cast<uint8_t>(source[i]);
+        uint8_t a = byte >> 4;
+        uint8_t b = byte & 0x0f;
+
+        target.push_back('%');
+        target.push_back(a < 10 ? a + '0' : a - 10 + 'A');
+        target.push_back(b < 10 ? b + '0' : b - 10 + 'A');
+      }
+    }
+  }
+
+
+  static bool HasField(const Json::Value& json,
+                       const std::string& key,
+                       Json::ValueType expectedType)
+  {
+    if (json.type() != Json::objectValue ||
+        !json.isMember(key))
+    {
+      return false;
+    }
+    else if (json[key].type() == expectedType)
+    {
+      return true;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+  }
+
+
+  std::string Toolbox::GetJsonStringField(const Json::Value& json,
+                                          const std::string& key,
+                                          const std::string& defaultValue)
+  {
+    if (HasField(json, key, Json::stringValue))
+    {
+      return json[key].asString();
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  bool Toolbox::GetJsonBooleanField(const ::Json::Value& json,
+                                    const std::string& key,
+                                    bool defaultValue)
+  {
+    if (HasField(json, key, Json::booleanValue))
+    {
+      return json[key].asBool();
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  int Toolbox::GetJsonIntegerField(const ::Json::Value& json,
+                                   const std::string& key,
+                                   int defaultValue)
+  {
+    if (HasField(json, key, Json::intValue))
+    {
+      return json[key].asInt();
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  unsigned int Toolbox::GetJsonUnsignedIntegerField(const ::Json::Value& json,
+                                                    const std::string& key,
+                                                    unsigned int defaultValue)
+  {
+    int v = GetJsonIntegerField(json, key, defaultValue);
+
+    if (v < 0)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+    else
+    {
+      return static_cast<unsigned int>(v);
+    }
+  }
+
+
+  bool Toolbox::IsUuid(const std::string& str)
+  {
+    if (str.size() != 36)
+    {
+      return false;
+    }
+
+    for (size_t i = 0; i < str.length(); i++)
+    {
+      if (i == 8 || i == 13 || i == 18 || i == 23)
+      {
+        if (str[i] != '-')
+          return false;
+      }
+      else
+      {
+        if (!isalnum(str[i]))
+          return false;
+      }
+    }
+
+    return true;
+  }
+
+
+  bool Toolbox::StartsWithUuid(const std::string& str)
+  {
+    if (str.size() < 36)
+    {
+      return false;
+    }
+
+    if (str.size() == 36)
+    {
+      return IsUuid(str);
+    }
+
+    assert(str.size() > 36);
+    if (!isspace(str[36]))
+    {
+      return false;
+    }
+
+    return IsUuid(str.substr(0, 36));
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Core/Toolbox.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,210 @@
+/**
+ * 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
+
+#include "Enumerations.h"
+
+#include <stdint.h>
+#include <vector>
+#include <string>
+#include <json/json.h>
+
+
+#if !defined(ORTHANC_ENABLE_BASE64)
+#  error The macro ORTHANC_ENABLE_BASE64 must be defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_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;
+
+  class NullType
+  {
+  };
+
+  namespace Toolbox
+  {
+    void ToUpperCase(std::string& s);  // Inplace version
+
+    void ToLowerCase(std::string& s);  // Inplace version
+
+    void ToUpperCase(std::string& result,
+                     const std::string& source);
+
+    void ToLowerCase(std::string& result,
+                     const std::string& source);
+
+    void SplitUriComponents(UriComponents& components,
+                            const std::string& uri);
+  
+    void TruncateUri(UriComponents& target,
+                     const UriComponents& source,
+                     size_t fromLevel);
+  
+    bool IsChildUri(const UriComponents& baseUri,
+                    const UriComponents& testedUri);
+
+    std::string AutodetectMimeType(const std::string& path);
+
+    std::string FlattenUri(const UriComponents& components,
+                           size_t fromLevel = 0);
+
+#if ORTHANC_ENABLE_MD5 == 1
+    void ComputeMD5(std::string& result,
+                    const std::string& data);
+
+    void ComputeMD5(std::string& result,
+                    const void* data,
+                    size_t size);
+#endif
+
+    void ComputeSHA1(std::string& result,
+                     const std::string& data);
+
+    void ComputeSHA1(std::string& result,
+                     const void* data,
+                     size_t size);
+
+    bool IsSHA1(const char* str,
+                size_t size);
+
+    bool IsSHA1(const std::string& s);
+
+#if ORTHANC_ENABLE_BASE64 == 1
+    void DecodeBase64(std::string& result, 
+                      const std::string& data);
+
+    void EncodeBase64(std::string& result, 
+                      const std::string& data);
+
+#  if BOOST_HAS_REGEX == 1
+    bool DecodeDataUriScheme(std::string& mime,
+                             std::string& content,
+                             const std::string& source);
+#  endif
+
+    void EncodeDataUriScheme(std::string& result,
+                             const std::string& mime,
+                             const std::string& content);
+#endif
+
+    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);
+
+    // In-place percent-decoding for URL
+    void UrlDecode(std::string& s);
+
+    Endianness DetectEndianness();
+
+#if BOOST_HAS_REGEX == 1
+    std::string WildcardToRegularExpression(const std::string& s);
+#endif
+
+    void TokenizeString(std::vector<std::string>& result,
+                        const std::string& source,
+                        char separator);
+
+#if ORTHANC_ENABLE_PUGIXML == 1
+    void JsonToXml(std::string& target,
+                   const Json::Value& source,
+                   const std::string& rootElement = "root",
+                   const std::string& arrayElement = "item");
+#endif
+
+    bool IsInteger(const std::string& str);
+
+    void CopyJsonWithoutComments(Json::Value& target,
+                                 const Json::Value& source);
+
+    bool StartsWith(const std::string& str,
+                    const std::string& prefix);
+
+    void UriEncode(std::string& target,
+                   const std::string& source);
+
+    std::string GetJsonStringField(const ::Json::Value& json,
+                                   const std::string& key,
+                                   const std::string& defaultValue);
+
+    bool GetJsonBooleanField(const ::Json::Value& json,
+                             const std::string& key,
+                             bool defaultValue);
+
+    int GetJsonIntegerField(const ::Json::Value& json,
+                            const std::string& key,
+                            int defaultValue);
+
+    unsigned int GetJsonUnsignedIntegerField(const ::Json::Value& json,
+                                             const std::string& key,
+                                             unsigned int defaultValue);
+
+    bool IsUuid(const std::string& str);
+
+    bool StartsWithUuid(const std::string& str);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/Samples/Common/ExportedSymbols.list	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,7 @@
+# This is the list of the symbols that must be exported by Orthanc
+# plugins, if targeting OS X
+
+_OrthancPluginInitialize
+_OrthancPluginFinalize
+_OrthancPluginGetName
+_OrthancPluginGetVersion
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,1222 @@
+/**
+ * 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 "OrthancPluginCppWrapper.h"
+
+#include <json/reader.h>
+#include <json/writer.h>
+
+
+namespace OrthancPlugins
+{
+  void MemoryBuffer::Check(OrthancPluginErrorCode code)
+  {
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      // 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
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+
+
+  MemoryBuffer::MemoryBuffer(OrthancPluginContext* context) : 
+    context_(context)
+  {
+    buffer_.data = NULL;
+    buffer_.size = 0;
+  }
+
+
+  void MemoryBuffer::Clear()
+  {
+    if (buffer_.data != NULL)
+    {
+      OrthancPluginFreeMemoryBuffer(context_, &buffer_);
+      buffer_.data = NULL;
+      buffer_.size = 0;
+    }
+  }
+
+
+  void MemoryBuffer::Assign(OrthancPluginMemoryBuffer& other)
+  {
+    Clear();
+
+    buffer_.data = other.data;
+    buffer_.size = other.size;
+
+    other.data = NULL;
+    other.size = 0;
+  }
+
+
+  void MemoryBuffer::ToString(std::string& target) const
+  {
+    if (buffer_.size == 0)
+    {
+      target.clear();
+    }
+    else
+    {
+      target.assign(reinterpret_cast<const char*>(buffer_.data), buffer_.size);
+    }
+  }
+
+
+  void MemoryBuffer::ToJson(Json::Value& target) const
+  {
+    if (buffer_.data == NULL ||
+        buffer_.size == 0)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    const char* tmp = reinterpret_cast<const char*>(buffer_.data);
+
+    Json::Reader reader;
+    if (!reader.parse(tmp, tmp + buffer_.size, target))
+    {
+      OrthancPluginLogError(context_, "Cannot convert some memory buffer to JSON");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  bool MemoryBuffer::RestApiGet(const std::string& uri,
+                                bool applyPlugins)
+  {
+    Clear();
+
+    if (applyPlugins)
+    {
+      return CheckHttp(OrthancPluginRestApiGetAfterPlugins(context_, &buffer_, uri.c_str()));
+    }
+    else
+    {
+      return CheckHttp(OrthancPluginRestApiGet(context_, &buffer_, uri.c_str()));
+    }
+  }
+
+  
+  bool MemoryBuffer::RestApiPost(const std::string& uri,
+                                 const char* body,
+                                 size_t bodySize,
+                                 bool applyPlugins)
+  {
+    Clear();
+
+    if (applyPlugins)
+    {
+      return CheckHttp(OrthancPluginRestApiPostAfterPlugins(context_, &buffer_, uri.c_str(), body, bodySize));
+    }
+    else
+    {
+      return CheckHttp(OrthancPluginRestApiPost(context_, &buffer_, uri.c_str(), body, bodySize));
+    }
+  }
+
+
+  bool MemoryBuffer::RestApiPut(const std::string& uri,
+                                const char* body,
+                                size_t bodySize,
+                                bool applyPlugins)
+  {
+    Clear();
+
+    if (applyPlugins)
+    {
+      return CheckHttp(OrthancPluginRestApiPutAfterPlugins(context_, &buffer_, uri.c_str(), body, bodySize));
+    }
+    else
+    {
+      return CheckHttp(OrthancPluginRestApiPut(context_, &buffer_, uri.c_str(), body, bodySize));
+    }
+  }
+
+
+  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)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+    else
+    {
+      Clear();
+      str_ = str;
+    }
+  }
+
+
+  void OrthancString::Clear()
+  {
+    if (str_ != NULL)
+    {
+      OrthancPluginFreeString(context_, str_);
+      str_ = NULL;
+    }
+  }
+
+
+  void OrthancString::ToString(std::string& target) const
+  {
+    if (str_ == NULL)
+    {
+      target.clear();
+    }
+    else
+    {
+      target.assign(str_);
+    }
+  }
+
+
+  void OrthancString::ToJson(Json::Value& target) const
+  {
+    if (str_ == NULL)
+    {
+      OrthancPluginLogError(context_, "Cannot convert an empty memory buffer to JSON");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    Json::Reader reader;
+    if (!reader.parse(str_, target))
+    {
+      OrthancPluginLogError(context_, "Cannot convert some memory buffer to JSON");
+      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);
+    }
+  }
+  
+
+  OrthancConfiguration::OrthancConfiguration(OrthancPluginContext* context) : 
+    context_(context)
+  {
+    OrthancString str(context);
+    str.Assign(OrthancPluginGetConfiguration(context));
+
+    if (str.GetContent() == NULL)
+    {
+      OrthancPluginLogError(context, "Cannot access the Orthanc configuration");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    str.ToJson(configuration_);
+
+    if (configuration_.type() != Json::objectValue)
+    {
+      OrthancPluginLogError(context, "Unable to read the Orthanc configuration");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+
+
+  OrthancPluginContext* OrthancConfiguration::GetContext() const
+  {
+    if (context_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(Plugin);
+    }
+    else
+    {
+      return context_;
+    }
+  }
+
+
+  std::string OrthancConfiguration::GetPath(const std::string& key) const
+  {
+    if (path_.empty())
+    {
+      return key;
+    }
+    else
+    {
+      return path_ + "." + key;
+    }
+  }
+
+
+  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
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    target.context_ = context_;
+    target.path_ = GetPath(key);
+
+    if (!configuration_.isMember(key))
+    {
+      target.configuration_ = Json::objectValue;
+    }
+    else
+    {
+      if (configuration_[key].type() != Json::objectValue)
+      {
+        if (context_ != NULL)
+        {
+          std::string s = "The configuration section \"" + target.path_ + "\" is not an associative array as expected";
+          OrthancPluginLogError(context_, s.c_str());
+        }
+
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+      }
+
+      target.configuration_ = configuration_[key];
+    }
+  }
+
+
+  bool OrthancConfiguration::LookupStringValue(std::string& target,
+                                               const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    if (!configuration_.isMember(key))
+    {
+      return false;
+    }
+
+    if (configuration_[key].type() != Json::stringValue)
+    {
+      if (context_ != NULL)
+      {
+        std::string s = "The configuration option \"" + GetPath(key) + "\" is not a string as expected";
+        OrthancPluginLogError(context_, s.c_str());
+      }
+
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+
+    target = configuration_[key].asString();
+    return true;
+  }
+
+
+  bool OrthancConfiguration::LookupIntegerValue(int& target,
+                                                const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    if (!configuration_.isMember(key))
+    {
+      return false;
+    }
+
+    switch (configuration_[key].type())
+    {
+      case Json::intValue:
+        target = configuration_[key].asInt();
+        return true;
+        
+      case Json::uintValue:
+        target = configuration_[key].asUInt();
+        return true;
+        
+      default:
+        if (context_ != NULL)
+        {
+          std::string s = "The configuration option \"" + GetPath(key) + "\" is not an integer as expected";
+          OrthancPluginLogError(context_, s.c_str());
+        }
+
+        ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+  }
+
+
+  bool OrthancConfiguration::LookupUnsignedIntegerValue(unsigned int& target,
+                                                        const std::string& key) const
+  {
+    int tmp;
+    if (!LookupIntegerValue(tmp, key))
+    {
+      return false;
+    }
+
+    if (tmp < 0)
+    {
+      if (context_ != NULL)
+      {
+        std::string s = "The configuration option \"" + GetPath(key) + "\" is not a positive integer as expected";
+        OrthancPluginLogError(context_, s.c_str());
+      }
+
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+    else
+    {
+      target = static_cast<unsigned int>(tmp);
+      return true;
+    }
+  }
+
+
+  bool OrthancConfiguration::LookupBooleanValue(bool& target,
+                                                const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    if (!configuration_.isMember(key))
+    {
+      return false;
+    }
+
+    if (configuration_[key].type() != Json::booleanValue)
+    {
+      if (context_ != NULL)
+      {
+        std::string s = "The configuration option \"" + GetPath(key) + "\" is not a Boolean as expected";
+        OrthancPluginLogError(context_, s.c_str());
+      }
+
+      ORTHANC_PLUGINS_THROW_EXCEPTION(BadFileFormat);
+    }
+
+    target = configuration_[key].asBool();
+    return true;
+  }
+
+
+  bool OrthancConfiguration::LookupFloatValue(float& target,
+                                              const std::string& key) const
+  {
+    assert(configuration_.type() == Json::objectValue);
+
+    if (!configuration_.isMember(key))
+    {
+      return false;
+    }
+
+    switch (configuration_[key].type())
+    {
+      case Json::realValue:
+        target = configuration_[key].asFloat();
+        return true;
+        
+      case Json::intValue:
+        target = static_cast<float>(configuration_[key].asInt());
+        return true;
+        
+      case Json::uintValue:
+        target = static_cast<float>(configuration_[key].asUInt());
+        return true;
+        
+      default:
+        if (context_ != NULL)
+        {
+          std::string s = "The configuration option \"" + GetPath(key) + "\" is not an integer as expected";
+          OrthancPluginLogError(context_, s.c_str());
+        }
+
+        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;
+    }
+  }
+
+  
+  std::string OrthancConfiguration::GetStringValue(const std::string& key,
+                                                   const std::string& defaultValue) const
+  {
+    std::string tmp;
+    if (LookupStringValue(tmp, key))
+    {
+      return tmp;
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  int OrthancConfiguration::GetIntegerValue(const std::string& key,
+                                            int defaultValue) const
+  {
+    int tmp;
+    if (LookupIntegerValue(tmp, key))
+    {
+      return tmp;
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  unsigned int OrthancConfiguration::GetUnsignedIntegerValue(const std::string& key,
+                                                             unsigned int defaultValue) const
+  {
+    unsigned int tmp;
+    if (LookupUnsignedIntegerValue(tmp, key))
+    {
+      return tmp;
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  bool OrthancConfiguration::GetBooleanValue(const std::string& key,
+                                             bool defaultValue) const
+  {
+    bool tmp;
+    if (LookupBooleanValue(tmp, key))
+    {
+      return tmp;
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  float OrthancConfiguration::GetFloatValue(const std::string& key,
+                                            float defaultValue) const
+  {
+    float tmp;
+    if (LookupFloatValue(tmp, key))
+    {
+      return tmp;
+    }
+    else
+    {
+      return defaultValue;
+    }
+  }
+
+
+  void OrthancImage::Clear()
+  {
+    if (image_ != NULL)
+    {
+      OrthancPluginFreeImage(context_, image_);
+      image_ = NULL;
+    }
+  }
+
+
+  void OrthancImage::CheckImageAvailable()
+  {
+    if (image_ == NULL)
+    {
+      OrthancPluginLogError(context_, "Trying to access a NULL image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  OrthancImage::OrthancImage(OrthancPluginContext*  context) :
+    context_(context),
+    image_(NULL)
+  {
+    if (context == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  OrthancImage::OrthancImage(OrthancPluginContext*  context,
+                             OrthancPluginImage*    image) :
+    context_(context),
+    image_(image)
+  {
+    if (context == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+  
+
+  OrthancImage::OrthancImage(OrthancPluginContext*     context,
+                             OrthancPluginPixelFormat  format,
+                             uint32_t                  width,
+                             uint32_t                  height) :
+    context_(context)
+  {
+    if (context == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+    else
+    {
+      image_ = OrthancPluginCreateImage(context, format, width, height);
+    }
+  }
+
+
+  void OrthancImage::UncompressPngImage(const void* data,
+                                        size_t size)
+  {
+    Clear();
+    image_ = OrthancPluginUncompressImage(context_, data, size, OrthancPluginImageFormat_Png);
+    if (image_ == NULL)
+    {
+      OrthancPluginLogError(context_, "Cannot uncompress a PNG image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  void OrthancImage::UncompressJpegImage(const void* data,
+                                         size_t size)
+  {
+    Clear();
+    image_ = OrthancPluginUncompressImage(context_, data, size, OrthancPluginImageFormat_Jpeg);
+    if (image_ == NULL)
+    {
+      OrthancPluginLogError(context_, "Cannot uncompress a JPEG image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  void OrthancImage::DecodeDicomImage(const void* data,
+                                      size_t size,
+                                      unsigned int frame)
+  {
+    Clear();
+    image_ = OrthancPluginDecodeDicomImage(context_, data, size, frame);
+    if (image_ == NULL)
+    {
+      OrthancPluginLogError(context_, "Cannot uncompress a DICOM image");
+      ORTHANC_PLUGINS_THROW_EXCEPTION(ParameterOutOfRange);
+    }
+  }
+
+
+  OrthancPluginPixelFormat OrthancImage::GetPixelFormat()
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImagePixelFormat(context_, image_);
+  }
+
+
+  unsigned int OrthancImage::GetWidth()
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImageWidth(context_, image_);
+  }
+
+
+  unsigned int OrthancImage::GetHeight()
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImageHeight(context_, image_);
+  }
+
+
+  unsigned int OrthancImage::GetPitch()
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImagePitch(context_, image_);
+  }
+
+    
+  const void* OrthancImage::GetBuffer()
+  {
+    CheckImageAvailable();
+    return OrthancPluginGetImageBuffer(context_, image_);
+  }
+
+
+  void OrthancImage::CompressPngImage(MemoryBuffer& target)
+  {
+    CheckImageAvailable();
+    
+    OrthancPluginMemoryBuffer tmp;
+    OrthancPluginCompressPngImage(context_, &tmp, GetPixelFormat(), 
+                                  GetWidth(), GetHeight(), GetPitch(), GetBuffer());
+
+    target.Assign(tmp);
+  }
+
+
+  void OrthancImage::CompressJpegImage(MemoryBuffer& target,
+                                       uint8_t quality)
+  {
+    CheckImageAvailable();
+    
+    OrthancPluginMemoryBuffer tmp;
+    OrthancPluginCompressJpegImage(context_, &tmp, GetPixelFormat(), 
+                                   GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
+    
+    target.Assign(tmp);
+  }
+
+
+  void OrthancImage::AnswerPngImage(OrthancPluginRestOutput* output)
+  {
+    CheckImageAvailable();
+    OrthancPluginCompressAndAnswerPngImage(context_, output, GetPixelFormat(),
+                                           GetWidth(), GetHeight(), GetPitch(), GetBuffer());
+  }
+
+
+  void OrthancImage::AnswerJpegImage(OrthancPluginRestOutput* output,
+                                     uint8_t quality)
+  {
+    CheckImageAvailable();
+    OrthancPluginCompressAndAnswerJpegImage(context_, output, GetPixelFormat(),
+                                            GetWidth(), GetHeight(), GetPitch(), GetBuffer(), quality);
+  }
+
+
+
+#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))
+    {
+      return false;
+    }
+    else
+    {
+      answer.ToJson(result);
+      return true;
+    }
+  }
+
+
+  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))
+    {
+      return false;
+    }
+    else
+    {
+      answer.ToJson(result);
+      return true;
+    }
+  }
+
+
+  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))
+    {
+      return false;
+    }
+    else
+    {
+      answer.ToJson(result);
+      return true;
+    }
+  }
+
+
+  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)
+  {
+    OrthancPluginErrorCode error;
+
+    if (applyPlugins)
+    {
+      error = OrthancPluginRestApiDeleteAfterPlugins(context, uri.c_str());
+    }
+    else
+    {
+      error = OrthancPluginRestApiDelete(context, uri.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);
+    }
+  }
+
+
+  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;
+    }
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,524 @@
+/**
+ * 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
+
+#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 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 MemoryBuffer : public boost::noncopyable
+  {
+  private:
+    OrthancPluginContext*      context_;
+    OrthancPluginMemoryBuffer  buffer_;
+
+    void Check(OrthancPluginErrorCode code);
+
+    bool CheckHttp(OrthancPluginErrorCode code);
+
+  public:
+    MemoryBuffer(OrthancPluginContext* context);
+
+    ~MemoryBuffer()
+    {
+      Clear();
+    }
+
+    OrthancPluginMemoryBuffer* operator*()
+    {
+      return &buffer_;
+    }
+
+    // This transfers ownership
+    void Assign(OrthancPluginMemoryBuffer& other);
+
+    const char* GetData() const
+    {
+      if (buffer_.size > 0)
+      {
+        return reinterpret_cast<const char*>(buffer_.data);
+      }
+      else
+      {
+        return NULL;
+      }
+    }
+
+    size_t GetSize() const
+    {
+      return buffer_.size;
+    }
+
+    void Clear();
+
+    void ToString(std::string& target) const;
+
+    void ToJson(Json::Value& target) const;
+
+    bool RestApiGet(const std::string& uri,
+                    bool applyPlugins);
+
+    bool RestApiPost(const std::string& uri,
+                     const char* body,
+                     size_t bodySize,
+                     bool applyPlugins);
+
+    bool RestApiPut(const std::string& uri,
+                    const char* body,
+                    size_t bodySize,
+                    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)
+    {
+      return RestApiPost(uri, body.empty() ? NULL : body.c_str(), body.size(), applyPlugins);
+    }
+
+    bool RestApiPut(const std::string& uri,
+                    const std::string& body,
+                    bool applyPlugins)
+    {
+      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);
+  };
+
+
+  class OrthancString : public boost::noncopyable
+  {
+  private:
+    OrthancPluginContext*  context_;
+    char*                  str_;
+
+    void Clear();
+
+  public:
+    OrthancString(OrthancPluginContext* context) :
+      context_(context),
+      str_(NULL)
+    {
+    }
+
+    ~OrthancString()
+    {
+      Clear();
+    }
+
+    // This transfers ownership, warning: The string must have been
+    // allocated by the Orthanc core
+    void Assign(char* str);
+
+    const char* GetContent() const
+    {
+      return str_;
+    }
+
+    void ToString(std::string& target) const;
+
+    void ToJson(Json::Value& target) const;
+  };
+
+
+  class OrthancConfiguration : public boost::noncopyable
+  {
+  private:
+    OrthancPluginContext*  context_;
+    Json::Value            configuration_;  // Necessarily a Json::objectValue
+    std::string            path_;
+
+    std::string GetPath(const std::string& key) const;
+
+  public:
+    OrthancConfiguration() : context_(NULL)
+    {
+    }
+
+    OrthancConfiguration(OrthancPluginContext* context);
+
+    OrthancPluginContext* GetContext() const;
+
+    const Json::Value& GetJson() const
+    {
+      return configuration_;
+    }
+
+    bool IsSection(const std::string& key) const;
+
+    void GetSection(OrthancConfiguration& target,
+                    const std::string& key) const;
+
+    bool LookupStringValue(std::string& target,
+                           const std::string& key) const;
+    
+    bool LookupIntegerValue(int& target,
+                            const std::string& key) const;
+
+    bool LookupUnsignedIntegerValue(unsigned int& target,
+                                    const std::string& key) const;
+
+    bool LookupBooleanValue(bool& target,
+                            const std::string& key) const;
+
+    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;
+
+    int GetIntegerValue(const std::string& key,
+                        int defaultValue) const;
+
+    unsigned int GetUnsignedIntegerValue(const std::string& key,
+                                         unsigned int defaultValue) const;
+
+    bool GetBooleanValue(const std::string& key,
+                         bool defaultValue) const;
+
+    float GetFloatValue(const std::string& key,
+                        float defaultValue) const;
+  };
+
+  class OrthancImage : public boost::noncopyable
+  {
+  private:
+    OrthancPluginContext*  context_;
+    OrthancPluginImage*    image_;
+
+    void Clear();
+
+    void CheckImageAvailable();
+
+  public:
+    OrthancImage(OrthancPluginContext*  context);
+
+    OrthancImage(OrthancPluginContext*  context,
+                 OrthancPluginImage*    image);
+
+    OrthancImage(OrthancPluginContext*     context,
+                 OrthancPluginPixelFormat  format,
+                 uint32_t                  width,
+                 uint32_t                  height);
+
+    ~OrthancImage()
+    {
+      Clear();
+    }
+
+    void UncompressPngImage(const void* data,
+                            size_t size);
+
+    void UncompressJpegImage(const void* data,
+                             size_t size);
+
+    void DecodeDicomImage(const void* data,
+                          size_t size,
+                          unsigned int frame);
+
+    OrthancPluginPixelFormat GetPixelFormat();
+
+    unsigned int GetWidth();
+
+    unsigned int GetHeight();
+
+    unsigned int GetPitch();
+    
+    const void* GetBuffer();
+
+    void CompressPngImage(MemoryBuffer& target);
+
+    void CompressJpegImage(MemoryBuffer& target,
+                           uint8_t quality);
+
+    void AnswerPngImage(OrthancPluginRestOutput* output);
+
+    void AnswerJpegImage(OrthancPluginRestOutput* output,
+                         uint8_t quality);
+  };
+
+
+#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 RestApiPut(result, context, uri, body.empty() ? NULL : body.c_str(), 
+                      body.size(), applyPlugins);
+  }
+
+  bool RestApiDelete(OrthancPluginContext* context,
+                     const std::string& uri,
+                     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)
+  {
+    if (context != NULL)
+    {
+      OrthancPluginLogError(context, message.c_str());
+    }
+  }
+
+  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
+  {
+    template <RestCallback Callback>
+    OrthancPluginErrorCode Protect(OrthancPluginRestOutput* output,
+                                   const char* url,
+                                   const OrthancPluginHttpRequest* request)
+    {
+      try
+      {
+        Callback(output, url, request);
+        return OrthancPluginErrorCode_Success;
+      }
+      catch (ORTHANC_PLUGINS_EXCEPTION_CLASS& e)
+      {
+        return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
+      }
+      catch (boost::bad_lexical_cast&)
+      {
+        return OrthancPluginErrorCode_BadFileFormat;
+      }
+      catch (...)
+      {
+        return OrthancPluginErrorCode_Plugin;
+      }
+    }
+  }
+
+  
+  template <RestCallback Callback>
+  void RegisterRestCallback(OrthancPluginContext* context,
+                            const std::string& uri,
+                            bool isThreadSafe)
+  {
+    if (isThreadSafe)
+    {
+      OrthancPluginRegisterRestCallbackNoLock(context, uri.c_str(), Internals::Protect<Callback>);
+    }
+    else
+    {
+      OrthancPluginRegisterRestCallback(context, uri.c_str(), Internals::Protect<Callback>);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/Samples/Common/OrthancPluginException.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,101 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017 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
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Plugins/Samples/Common/VersionScript.map	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,12 @@
+# This is a version-script for Orthanc plugins
+
+{
+global:
+  OrthancPluginInitialize;
+  OrthancPluginFinalize;
+  OrthancPluginGetName;
+  OrthancPluginGetVersion;
+
+local:
+  *;
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/CMake/AutoGeneratedCode.cmake	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,53 @@
+set(AUTOGENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/AUTOGENERATED")
+set(AUTOGENERATED_SOURCES)
+
+file(MAKE_DIRECTORY ${AUTOGENERATED_DIR})
+include_directories(${AUTOGENERATED_DIR})
+
+macro(EmbedResources)
+  # Convert a semicolon separated list to a whitespace separated string
+  set(SCRIPT_OPTIONS)
+  set(SCRIPT_ARGUMENTS)
+  set(DEPENDENCIES)
+  set(IS_PATH_NAME false)
+
+  # Loop over the arguments of the function
+  foreach(arg ${ARGN})
+    # Extract the first character of the argument
+    string(SUBSTRING "${arg}" 0 1 FIRST_CHAR)
+    if (${FIRST_CHAR} STREQUAL "-")
+      # If the argument starts with a dash "-", this is an option to
+      # EmbedResources.py
+      list(APPEND SCRIPT_OPTIONS ${arg})
+    else()
+      if (${IS_PATH_NAME})
+        list(APPEND SCRIPT_ARGUMENTS "${arg}")
+        list(APPEND DEPENDENCIES "${arg}")
+        set(IS_PATH_NAME false)
+      else()
+        list(APPEND SCRIPT_ARGUMENTS "${arg}")
+        set(IS_PATH_NAME true)
+      endif()
+    endif()
+  endforeach()
+
+  set(TARGET_BASE "${AUTOGENERATED_DIR}/EmbeddedResources")
+  add_custom_command(
+    OUTPUT
+    "${TARGET_BASE}.h"
+    "${TARGET_BASE}.cpp"
+    COMMAND 
+    ${PYTHON_EXECUTABLE}
+    "${ORTHANC_ROOT}/Resources/EmbedResources.py"
+    ${SCRIPT_OPTIONS}
+    "${AUTOGENERATED_DIR}/EmbeddedResources"
+    ${SCRIPT_ARGUMENTS}
+    DEPENDS
+    "${ORTHANC_ROOT}/Resources/EmbedResources.py"
+    ${DEPENDENCIES}
+    )
+
+  list(APPEND AUTOGENERATED_SOURCES
+    "${AUTOGENERATED_DIR}/EmbeddedResources.cpp"
+    ) 
+endmacro()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/CMake/BoostConfiguration.cmake	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,238 @@
+if (STATIC_BUILD OR NOT USE_SYSTEM_BOOST)
+  set(BOOST_STATIC 1)
+else()
+  include(FindBoost)
+
+  set(BOOST_STATIC 0)
+  #set(Boost_DEBUG 1)
+  #set(Boost_USE_STATIC_LIBS ON)
+
+  find_package(Boost
+    COMPONENTS filesystem thread system date_time regex locale ${ORTHANC_BOOST_COMPONENTS})
+
+  if (NOT Boost_FOUND)
+    message(FATAL_ERROR "Unable to locate Boost on this system")
+  endif()
+
+  # Boost releases 1.44 through 1.47 supply both V2 and V3 filesystem
+  # http://www.boost.org/doc/libs/1_46_1/libs/filesystem/v3/doc/index.htm
+  if (${Boost_VERSION} LESS 104400)
+    add_definitions(
+      -DBOOST_HAS_FILESYSTEM_V3=0
+      )
+  else()
+    add_definitions(
+      -DBOOST_HAS_FILESYSTEM_V3=1
+      -DBOOST_FILESYSTEM_VERSION=3
+      )
+  endif()
+
+  #if (${Boost_VERSION} LESS 104800)
+  # boost::locale is only available from 1.48.00
+  #message("Too old version of Boost (${Boost_LIB_VERSION}): Building the static version")
+  #  set(BOOST_STATIC 1)
+  #endif()
+
+  include_directories(${Boost_INCLUDE_DIRS})
+  link_libraries(${Boost_LIBRARIES})
+endif()
+
+
+if (BOOST_STATIC)
+  # Parameters for Boost 1.60.0
+  set(BOOST_NAME boost_1_60_0)
+  set(BOOST_BCP_SUFFIX bcpdigest-1.0.1)
+  set(BOOST_MD5 "a789f8ec2056ad1c2d5f0cb64687cc7b")
+  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})
+
+  DownloadPackage(${BOOST_MD5} ${BOOST_URL} "${BOOST_SOURCES_DIR}")
+
+  set(BOOST_SOURCES)
+
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "Darwin" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
+      ${CMAKE_SYSTEM_NAME} STREQUAL "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
+      )
+    add_definitions(
+      -DBOOST_LOCALE_WITH_ICONV=1
+      -DBOOST_LOCALE_NO_WINAPI_BACKEND=1
+      -DBOOST_LOCALE_NO_STD_BACKEND=1
+      )
+
+    if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase" OR
+        ${CMAKE_SYSTEM_NAME} STREQUAL "PNaCl" OR
+        ${CMAKE_SYSTEM_NAME} STREQUAL "NaCl32" OR
+        ${CMAKE_SYSTEM_NAME} STREQUAL "NaCl64")
+      add_definitions(-DBOOST_HAS_SCHED_YIELD=1)
+    endif()
+
+  elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+    list(APPEND BOOST_SOURCES
+      ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_dll.cpp
+      ${BOOST_SOURCES_DIR}/libs/thread/src/win32/thread.cpp
+      ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_pe.cpp
+      ${BOOST_FILESYSTEM_SOURCES_DIR}/windows_file_codecvt.cpp
+      )
+
+    # Starting with release 0.8.2, Orthanc statically links against
+    # libiconv, even on Windows. Indeed, the "WCONV" library of
+    # Windows XP seems not to support properly several codepages
+    # (notably "Latin3", "Hebrew", and "Arabic").
+
+    if (USE_BOOST_ICONV)
+      include(${ORTHANC_ROOT}/Resources/CMake/LibIconvConfiguration.cmake)
+    else()
+      add_definitions(-DBOOST_LOCALE_WITH_WCONV=1)
+    endif()
+
+    add_definitions(
+      -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()
+
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+    list(APPEND BOOST_SOURCES
+      ${BOOST_SOURCES_DIR}/libs/filesystem/src/utf8_codecvt_facet.cpp
+      )
+  endif()
+
+  aux_source_directory(${BOOST_SOURCES_DIR}/libs/regex/src BOOST_REGEX_SOURCES)
+
+  list(APPEND BOOST_SOURCES
+    ${BOOST_REGEX_SOURCES}
+    ${BOOST_SOURCES_DIR}/libs/date_time/src/gregorian/greg_month.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" OR
+        ${CMAKE_SYSTEM_NAME} STREQUAL "PNaCl" OR
+        ${CMAKE_SYSTEM_NAME} STREQUAL "NaCl32" OR
+        ${CMAKE_SYSTEM_NAME} STREQUAL "NaCl64")
+      list(APPEND BOOST_SOURCES
+        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/codecvt.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/collate.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/converter.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/numeric.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/posix/posix_backend.cpp
+        )
+    elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+      list(APPEND BOOST_SOURCES
+        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/collate.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/converter.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/lcid.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/numeric.cpp
+        ${BOOST_SOURCES_DIR}/libs/locale/src/win32/win_backend.cpp
+        )
+    else()
+      message(FATAL_ERROR "Support your platform here")
+    endif()
+
+    list(APPEND BOOST_SOURCES
+      ${BOOST_REGEX_SOURCES}
+      ${BOOST_SOURCES_DIR}/libs/date_time/src/gregorian/greg_month.cpp
+      ${BOOST_SOURCES_DIR}/libs/system/src/error_code.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/shared/generator.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/date_time.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/formatting.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/ids.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/localization_backend.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/message.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/shared/mo_lambda.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/util/codecvt_converter.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/util/default_locale.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/util/gregorian.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/util/info.cpp
+      ${BOOST_SOURCES_DIR}/libs/locale/src/util/locale_data.cpp
+      )        
+  endif()
+
+  add_definitions(
+    # Static build of Boost
+    -DBOOST_ALL_NO_LIB 
+    -DBOOST_ALL_NOLIB 
+    -DBOOST_DATE_TIME_NO_LIB 
+    -DBOOST_THREAD_BUILD_LIB
+    -DBOOST_PROGRAM_OPTIONS_NO_LIB
+    -DBOOST_REGEX_NO_LIB
+    -DBOOST_SYSTEM_NO_LIB
+    -DBOOST_LOCALE_NO_LIB
+    -DBOOST_HAS_LOCALE=1
+    )
+
+  if (CMAKE_COMPILER_IS_GNUCXX)
+    add_definitions(-isystem ${BOOST_SOURCES_DIR})
+  endif()
+
+  include_directories(
+    ${BOOST_SOURCES_DIR}
+    )
+
+  source_group(ThirdParty\\boost REGULAR_EXPRESSION ${BOOST_SOURCES_DIR}/.*)
+
+else()
+  add_definitions(
+    -DBOOST_HAS_LOCALE=1
+    )
+endif()
+
+
+add_definitions(
+  -DBOOST_HAS_DATE_TIME=1
+  -DBOOST_HAS_REGEX=1
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/CMake/Compiler.cmake	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,188 @@
+# This file sets all the compiler-related flags
+
+if (CMAKE_CROSSCOMPILING)
+  # Cross-compilation necessarily implies standalone and static build
+  SET(STATIC_BUILD ON)
+  SET(STANDALONE_BUILD ON)
+endif()
+
+if (CMAKE_COMPILER_IS_GNUCXX)
+  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wno-long-long -Wno-implicit-function-declaration")  
+  # --std=c99 makes libcurl not to compile
+  # -pedantic gives a lot of warnings on OpenSSL 
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -Wno-variadic-macros")
+
+  if (CMAKE_CROSSCOMPILING)
+    # http://stackoverflow.com/a/3543845/881731
+    set(CMAKE_RC_COMPILE_OBJECT "<CMAKE_RC_COMPILER> -O coff -I<CMAKE_CURRENT_SOURCE_DIR> <SOURCE> <OBJECT>")
+  endif()
+
+elseif (MSVC)
+  # Use static runtime under Visual Studio
+  # http://www.cmake.org/Wiki/CMake_FAQ#Dynamic_Replace
+  # http://stackoverflow.com/a/6510446
+  foreach(flag_var
+    CMAKE_C_FLAGS_DEBUG
+    CMAKE_CXX_FLAGS_DEBUG
+    CMAKE_C_FLAGS_RELEASE 
+    CMAKE_CXX_FLAGS_RELEASE
+    CMAKE_C_FLAGS_MINSIZEREL 
+    CMAKE_CXX_FLAGS_MINSIZEREL 
+    CMAKE_C_FLAGS_RELWITHDEBINFO 
+    CMAKE_CXX_FLAGS_RELWITHDEBINFO) 
+    string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
+    string(REGEX REPLACE "/MDd" "/MTd" ${flag_var} "${${flag_var}}")
+  endforeach(flag_var)
+
+  # Add /Zm256 compiler option to Visual Studio to fix PCH errors
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zm256")
+
+  add_definitions(
+    -D_CRT_SECURE_NO_WARNINGS=1
+    -D_CRT_SECURE_NO_DEPRECATE=1
+    )
+
+  if (MSVC_VERSION LESS 1600)
+    # Starting with Visual Studio >= 2010 (i.e. macro _MSC_VER >=
+    # 1600), Microsoft ships a standard-compliant <stdint.h>
+    # header. For earlier versions of Visual Studio, give access to a
+    # compatibility header.
+    # http://stackoverflow.com/a/70630/881731
+    # https://en.wikibooks.org/wiki/C_Programming/C_Reference/stdint.h#External_links
+    include_directories(${ORTHANC_ROOT}/Resources/ThirdParty/VisualStudio)
+  endif()
+
+  link_libraries(netapi32)
+endif()
+
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "kFreeBSD" OR
+    ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
+  set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined")
+  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
+
+  if (NOT DEFINED ENABLE_PLUGINS_VERSION_SCRIPT OR 
+      ENABLE_PLUGINS_VERSION_SCRIPT)
+    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--version-script=${ORTHANC_ROOT}/Plugins/Samples/Common/VersionScript.map")
+  endif()
+
+  # Remove the "-rdynamic" option
+  # http://www.mail-archive.com/cmake@cmake.org/msg08837.html
+  set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "")
+  link_libraries(uuid pthread rt)
+
+  if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
+    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed")
+    set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--as-needed")
+    set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--as-needed")
+    add_definitions(
+      -D_LARGEFILE64_SOURCE=1 
+      -D_FILE_OFFSET_BITS=64
+      )
+    link_libraries(dl)
+  endif()
+
+  CHECK_INCLUDE_FILES(uuid/uuid.h HAVE_UUID_H)
+  if (NOT HAVE_UUID_H)
+    message(FATAL_ERROR "Please install the uuid-dev package")
+  endif()
+
+elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  if (MSVC)
+    message("MSVC compiler version = " ${MSVC_VERSION} "\n")
+    # Starting Visual Studio 2013 (version 1800), it is not possible
+    # to target Windows XP anymore
+    if (MSVC_VERSION LESS 1800)
+      add_definitions(
+        -DWINVER=0x0501
+        -D_WIN32_WINNT=0x0501
+        )
+    endif()
+  else()
+    add_definitions(
+      -DWINVER=0x0501
+      -D_WIN32_WINNT=0x0501
+      )
+  endif()
+
+  add_definitions(
+    -D_CRT_SECURE_NO_WARNINGS=1
+    )
+  link_libraries(rpcrt4 ws2_32)
+
+  if (CMAKE_COMPILER_IS_GNUCXX)
+    # Some additional C/C++ compiler flags for MinGW
+    SET(MINGW_NO_WARNINGS "-Wno-unused-function -Wno-unused-variable")
+    SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${MINGW_NO_WARNINGS} -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast")
+    SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MINGW_NO_WARNINGS}")
+
+    # This is a patch for MinGW64
+    SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++")
+    SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--allow-multiple-definition -static-libgcc -static-libstdc++")
+
+    CHECK_LIBRARY_EXISTS(winpthread pthread_create "" HAVE_WIN_PTHREAD)
+    if (HAVE_WIN_PTHREAD)
+      # This line is necessary to compile with recent versions of MinGW,
+      # otherwise "libwinpthread-1.dll" is not statically linked.
+      SET(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES} -Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic")
+      add_definitions(-DHAVE_WIN_PTHREAD=1)
+    else()
+      add_definitions(-DHAVE_WIN_PTHREAD=0)
+    endif()
+  endif()
+
+elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -exported_symbols_list ${ORTHANC_ROOT}/Plugins/Samples/Common/ExportedSymbols.list")
+
+  add_definitions(
+    -D_XOPEN_SOURCE=1
+    )
+  link_libraries(iconv)
+
+  CHECK_INCLUDE_FILES(uuid/uuid.h HAVE_UUID_H)
+  if (NOT HAVE_UUID_H)
+    message(FATAL_ERROR "Please install the uuid-dev package")
+  endif()
+
+endif()
+
+
+if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
+  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -I${LSB_PATH}/include")
+  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -nostdinc++ -I${LSB_PATH}/include -I${LSB_PATH}/include/c++ -I${LSB_PATH}/include/c++/backward -fpermissive")
+  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --lsb-target-version=${LSB_TARGET_VERSION} -L${LSB_LIBPATH}")
+endif()
+
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
+  # In FreeBSD, the "/usr/local/" folder contains the ports and need to be imported
+  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I/usr/local/include")
+  SET(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -I/usr/local/include")
+  SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/local/lib")
+  SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -L/usr/local/lib")
+endif()
+
+
+if (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()
+  add_definitions(-DORTHANC_STATIC=0)
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/CMake/DownloadPackage.cmake	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,172 @@
+macro(GetUrlFilename TargetVariable Url)
+  string(REGEX REPLACE "^.*/" "" ${TargetVariable} "${Url}")
+endmacro()
+
+
+macro(GetUrlExtension TargetVariable Url)
+  #string(REGEX REPLACE "^.*/[^.]*\\." "" TMP "${Url}")
+  string(REGEX REPLACE "^.*\\." "" TMP "${Url}")
+  string(TOLOWER "${TMP}" "${TargetVariable}")
+endmacro()
+
+
+
+##
+## Setup the patch command-line tool
+##
+
+if (NOT ORTHANC_DISABLE_PATCH)
+  if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
+    set(PATCH_EXECUTABLE ${CMAKE_CURRENT_LIST_DIR}/../ThirdParty/patch/patch.exe)
+    if (NOT EXISTS ${PATCH_EXECUTABLE})
+      message(FATAL_ERROR "Unable to find the patch.exe tool that is shipped with Orthanc")
+    endif()
+
+  else ()
+    find_program(PATCH_EXECUTABLE patch)
+    if (${PATCH_EXECUTABLE} MATCHES "PATCH_EXECUTABLE-NOTFOUND")
+      message(FATAL_ERROR "Please install the 'patch' standard command-line tool")
+    endif()
+  endif()
+endif()
+
+
+
+##
+## Check the existence of the required decompression tools
+##
+
+if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
+  find_program(ZIP_EXECUTABLE 7z 
+    PATHS 
+    "$ENV{ProgramFiles}/7-Zip"
+    "$ENV{ProgramW6432}/7-Zip"
+    )
+
+  if (${ZIP_EXECUTABLE} MATCHES "ZIP_EXECUTABLE-NOTFOUND")
+    message(FATAL_ERROR "Please install the '7-zip' software (http://www.7-zip.org/)")
+  endif()
+
+else()
+  find_program(UNZIP_EXECUTABLE unzip)
+  if (${UNZIP_EXECUTABLE} MATCHES "UNZIP_EXECUTABLE-NOTFOUND")
+    message(FATAL_ERROR "Please install the 'unzip' package")
+  endif()
+
+  find_program(TAR_EXECUTABLE tar)
+  if (${TAR_EXECUTABLE} MATCHES "TAR_EXECUTABLE-NOTFOUND")
+    message(FATAL_ERROR "Please install the 'tar' package")
+  endif()
+endif()
+
+
+macro(DownloadPackage MD5 Url TargetDirectory)
+  if (NOT IS_DIRECTORY "${TargetDirectory}")
+    GetUrlFilename(TMP_FILENAME "${Url}")
+
+    set(TMP_PATH "${CMAKE_SOURCE_DIR}/ThirdPartyDownloads/${TMP_FILENAME}")
+    if (NOT EXISTS "${TMP_PATH}")
+      message("Downloading ${Url}")
+
+      # This fixes issue 6: "I think cmake shouldn't download the
+      # packages which are not in the system, it should stop and let
+      # user know."
+      # https://code.google.com/p/orthanc/issues/detail?id=6
+      if (NOT STATIC_BUILD AND NOT ALLOW_DOWNLOADS)
+	message(FATAL_ERROR "CMake is not allowed to download from Internet. Please set the ALLOW_DOWNLOADS option to ON")
+      endif()
+
+      file(DOWNLOAD "${Url}" "${TMP_PATH}" 
+        SHOW_PROGRESS EXPECTED_MD5 "${MD5}"
+        TIMEOUT 60 INACTIVITY_TIMEOUT 60)
+    else()
+      message("Using local copy of ${Url}")
+    endif()
+
+    GetUrlExtension(TMP_EXTENSION "${Url}")
+    #message(${TMP_EXTENSION})
+    message("Uncompressing ${TMP_FILENAME}")
+
+    if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")
+      # How to silently extract files using 7-zip
+      # http://superuser.com/questions/331148/7zip-command-line-extract-silently-quietly
+
+      if (("${TMP_EXTENSION}" STREQUAL "gz") OR 
+          ("${TMP_EXTENSION}" STREQUAL "tgz") OR
+          ("${TMP_EXTENSION}" STREQUAL "xz"))
+        execute_process(
+          COMMAND ${ZIP_EXECUTABLE} e -y ${TMP_PATH}
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          OUTPUT_QUIET
+          )
+
+        if (Failure)
+          message(FATAL_ERROR "Error while running the uncompression tool")
+        endif()
+
+        if ("${TMP_EXTENSION}" STREQUAL "tgz")
+          string(REGEX REPLACE ".tgz$" ".tar" TMP_FILENAME2 "${TMP_FILENAME}")
+        elseif ("${TMP_EXTENSION}" STREQUAL "gz")
+          string(REGEX REPLACE ".gz$" "" TMP_FILENAME2 "${TMP_FILENAME}")
+        elseif ("${TMP_EXTENSION}" STREQUAL "xz")
+          string(REGEX REPLACE ".xz" "" TMP_FILENAME2 "${TMP_FILENAME}")
+        endif()
+
+        execute_process(
+          COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_FILENAME2}
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          OUTPUT_QUIET
+          )
+      elseif ("${TMP_EXTENSION}" STREQUAL "zip")
+        execute_process(
+          COMMAND ${ZIP_EXECUTABLE} x -y ${TMP_PATH}
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          OUTPUT_QUIET
+          )
+      else()
+        message(FATAL_ERROR "Support your platform here")
+      endif()
+
+    else()
+      if ("${TMP_EXTENSION}" STREQUAL "zip")
+        execute_process(
+          COMMAND sh -c "${UNZIP_EXECUTABLE} -q ${TMP_PATH}"
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+        )
+      elseif (("${TMP_EXTENSION}" STREQUAL "gz") OR ("${TMP_EXTENSION}" STREQUAL "tgz"))
+        #message("tar xvfz ${TMP_PATH}")
+        execute_process(
+          COMMAND sh -c "${TAR_EXECUTABLE} xfz ${TMP_PATH}"
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          )
+      elseif ("${TMP_EXTENSION}" STREQUAL "bz2")
+        execute_process(
+          COMMAND sh -c "${TAR_EXECUTABLE} xfj ${TMP_PATH}"
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          )
+      elseif ("${TMP_EXTENSION}" STREQUAL "xz")
+        execute_process(
+          COMMAND sh -c "${TAR_EXECUTABLE} xf ${TMP_PATH}"
+          WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+          RESULT_VARIABLE Failure
+          )
+      else()
+        message(FATAL_ERROR "Unknown package format.")
+      endif()
+    endif()
+   
+    if (Failure)
+      message(FATAL_ERROR "Error while running the uncompression tool")
+    endif()
+
+    if (NOT IS_DIRECTORY "${TargetDirectory}")
+      message(FATAL_ERROR "The package was not uncompressed at the proper location. Check the CMake instructions.")
+    endif()
+  endif()
+endmacro()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/CMake/JsonCppConfiguration.cmake	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,60 @@
+if (STATIC_BUILD OR NOT USE_SYSTEM_JSONCPP)
+  set(JSONCPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/jsoncpp-0.10.5)
+  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}")
+
+  set(JSONCPP_SOURCES
+    ${JSONCPP_SOURCES_DIR}/src/lib_json/json_reader.cpp
+    ${JSONCPP_SOURCES_DIR}/src/lib_json/json_value.cpp
+    ${JSONCPP_SOURCES_DIR}/src/lib_json/json_writer.cpp
+    )
+
+  include_directories(
+    ${JSONCPP_SOURCES_DIR}/include
+    )
+
+  source_group(ThirdParty\\JsonCpp REGULAR_EXPRESSION ${JSONCPP_SOURCES_DIR}/.*)
+
+else()
+  find_path(JSONCPP_INCLUDE_DIR json/reader.h
+    /usr/include/jsoncpp
+    /usr/local/include/jsoncpp
+    )
+
+  message("JsonCpp include dir: ${JSONCPP_INCLUDE_DIR}")
+  include_directories(${JSONCPP_INCLUDE_DIR})
+  link_libraries(jsoncpp)
+
+  CHECK_INCLUDE_FILE_CXX(${JSONCPP_INCLUDE_DIR}/json/reader.h HAVE_JSONCPP_H)
+  if (NOT HAVE_JSONCPP_H)
+    message(FATAL_ERROR "Please install the libjsoncpp-dev package")
+  endif()
+
+  # Switch to the C++11 standard if the version of JsonCpp is 1.y.z
+  if (EXISTS ${JSONCPP_INCLUDE_DIR}/json/version.h)
+    file(STRINGS
+      "${JSONCPP_INCLUDE_DIR}/json/version.h" 
+      JSONCPP_VERSION_MAJOR1 REGEX
+      ".*define JSONCPP_VERSION_MAJOR.*")
+
+    if (NOT JSONCPP_VERSION_MAJOR1)
+      message(FATAL_ERROR "Unable to extract the major version of JsonCpp")
+    endif()
+    
+    string(REGEX REPLACE
+      ".*JSONCPP_VERSION_MAJOR.*([0-9]+)$" "\\1" 
+      JSONCPP_VERSION_MAJOR ${JSONCPP_VERSION_MAJOR1})
+    message("JsonCpp major version: ${JSONCPP_VERSION_MAJOR}")
+
+    if (CMAKE_COMPILER_IS_GNUCXX AND 
+        JSONCPP_VERSION_MAJOR GREATER 0)
+      message("Switching to C++11 standard, as version of JsonCpp is >= 1.0.0")
+      set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wno-deprecated-declarations")
+    endif()
+  else()
+    message("Unable to detect the major version of JsonCpp, assuming < 1.0.0")
+  endif()
+
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/EmbedResources.py	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,431 @@
+#!/usr/bin/python
+
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017 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/>.
+
+
+import sys
+import os
+import os.path
+import pprint
+import re
+
+UPCASE_CHECK = True
+USE_SYSTEM_EXCEPTION = False
+EXCEPTION_CLASS = 'OrthancException'
+OUT_OF_RANGE_EXCEPTION = 'OrthancException(ErrorCode_ParameterOutOfRange)'
+INEXISTENT_PATH_EXCEPTION = 'OrthancException(ErrorCode_InexistentItem)'
+NAMESPACE = 'Orthanc'
+
+ARGS = []
+for i in range(len(sys.argv)):
+    if not sys.argv[i].startswith('--'):
+        ARGS.append(sys.argv[i])
+    elif sys.argv[i].lower() == '--no-upcase-check':
+        UPCASE_CHECK = False
+    elif sys.argv[i].lower() == '--system-exception':
+        USE_SYSTEM_EXCEPTION = True
+        EXCEPTION_CLASS = '::std::runtime_error'
+        OUT_OF_RANGE_EXCEPTION = '%s("Parameter out of range")' % EXCEPTION_CLASS
+        INEXISTENT_PATH_EXCEPTION = '%s("Unknown path in a directory resource")' % EXCEPTION_CLASS
+    elif sys.argv[i].startswith('--namespace='):
+        NAMESPACE = sys.argv[i][sys.argv[i].find('=') + 1 : ]
+
+if len(ARGS) < 2 or len(ARGS) % 2 != 0:
+    print ('Usage:')
+    print ('python %s [--no-upcase-check] [--system-exception] [--namespace=<Namespace>] <TargetBaseFilename> [ <Name> <Source> ]*' % sys.argv[0])
+    exit(-1)
+
+TARGET_BASE_FILENAME = ARGS[1]
+SOURCES = ARGS[2:]
+
+try:
+    # Make sure the destination directory exists
+    os.makedirs(os.path.normpath(os.path.join(TARGET_BASE_FILENAME, '..')))
+except:
+    pass
+
+
+#####################################################################
+## Read each resource file
+#####################################################################
+
+def CheckNoUpcase(s):
+    global UPCASE_CHECK
+    if (UPCASE_CHECK and
+        re.search('[A-Z]', s) != None):
+        raise Exception("Path in a directory with an upcase letter: %s" % s)
+
+resources = {}
+
+counter = 0
+i = 0
+while i < len(SOURCES):
+    resourceName = SOURCES[i].upper()
+    pathName = SOURCES[i + 1]
+
+    if not os.path.exists(pathName):
+        raise Exception("Non existing path: %s" % pathName)
+
+    if resourceName in resources:
+        raise Exception("Twice the same resource: " + resourceName)
+    
+    if os.path.isdir(pathName):
+        # The resource is a directory: Recursively explore its files
+        content = {}
+        for root, dirs, files in os.walk(pathName):
+            base = os.path.relpath(root, pathName)
+
+            # Fix issue #24 (Build fails on OSX when directory has .DS_Store files):
+            # Ignore folders whose name starts with a dot (".")
+            if base.find('/.') != -1:
+                print('Ignoring folder: %s' % root)
+                continue
+
+            for f in files:
+                if f.find('~') == -1:  # Ignore Emacs backup files
+                    if base == '.':
+                        r = f
+                    else:
+                        r = os.path.join(base, f)
+
+                    CheckNoUpcase(r)
+                    r = '/' + r.replace('\\', '/')
+                    if r in content:
+                        raise Exception("Twice the same filename (check case): " + r)
+
+                    content[r] = {
+                        'Filename' : os.path.join(root, f),
+                        'Index' : counter
+                        }
+                    counter += 1
+
+        resources[resourceName] = {
+            'Type' : 'Directory',
+            'Files' : content
+            }
+
+    elif os.path.isfile(pathName):
+        resources[resourceName] = {
+            'Type' : 'File',
+            'Index' : counter,
+            'Filename' : pathName
+            }
+        counter += 1
+
+    else:
+        raise Exception("Not a regular file, nor a directory: " + pathName)
+
+    i += 2
+
+#pprint.pprint(resources)
+
+
+#####################################################################
+## Write .h header
+#####################################################################
+
+header = open(TARGET_BASE_FILENAME + '.h', 'w')
+
+header.write("""
+#pragma once
+
+#include <string>
+#include <list>
+
+#if defined(_MSC_VER)
+#  pragma warning(disable: 4065)  // "Switch statement contains 'default' but no 'case' labels"
+#endif
+
+namespace %s
+{
+  namespace EmbeddedResources
+  {
+    enum FileResourceId
+    {
+""" % NAMESPACE)
+
+isFirst = True
+for name in resources:
+    if resources[name]['Type'] == 'File':
+        if isFirst:
+            isFirst = False
+        else:    
+            header.write(',\n')
+        header.write('      %s' % name)
+
+header.write("""
+    };
+
+    enum DirectoryResourceId
+    {
+""")
+
+isFirst = True
+for name in resources:
+    if resources[name]['Type'] == 'Directory':
+        if isFirst:
+            isFirst = False
+        else:    
+            header.write(',\n')
+        header.write('      %s' % name)
+
+header.write("""
+    };
+
+    const void* GetFileResourceBuffer(FileResourceId id);
+    size_t GetFileResourceSize(FileResourceId id);
+    void GetFileResource(std::string& result, FileResourceId id);
+
+    const void* GetDirectoryResourceBuffer(DirectoryResourceId id, const char* path);
+    size_t GetDirectoryResourceSize(DirectoryResourceId id, const char* path);
+    void GetDirectoryResource(std::string& result, DirectoryResourceId id, const char* path);
+
+    void ListResources(std::list<std::string>& result, DirectoryResourceId id);
+  }
+}
+""")
+header.close()
+
+
+
+#####################################################################
+## Write the resource content in the .cpp source
+#####################################################################
+
+PYTHON_MAJOR_VERSION = sys.version_info[0]
+
+def WriteResource(cpp, item):
+    cpp.write('    static const uint8_t resource%dBuffer[] = {' % item['Index'])
+
+    f = open(item['Filename'], "rb")
+    content = f.read()
+    f.close()
+
+    # http://stackoverflow.com/a/1035360
+    pos = 0
+    for b in content:
+        if PYTHON_MAJOR_VERSION == 2:
+            c = ord(b[0])
+        else:
+            c = b
+
+        if pos > 0:
+            cpp.write(', ')
+
+        if (pos % 16) == 0:
+            cpp.write('\n    ')
+
+        if c < 0:
+            raise Exception("Internal error")
+
+        cpp.write("0x%02x" % c)
+        pos += 1
+
+    # Zero-size array are disallowed, so we put one single void character in it.
+    if pos == 0:
+        cpp.write('  0')
+
+    cpp.write('  };\n')
+    cpp.write('    static const size_t resource%dSize = %d;\n' % (item['Index'], pos))
+
+
+cpp = open(TARGET_BASE_FILENAME + '.cpp', 'w')
+
+cpp.write('#include "%s.h"\n' % os.path.basename(TARGET_BASE_FILENAME))
+
+if USE_SYSTEM_EXCEPTION:
+    cpp.write('#include <stdexcept>')
+else:
+    cpp.write('#include "%s/Core/OrthancException.h"' % os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
+
+cpp.write("""
+#include <stdint.h>
+#include <string.h>
+
+namespace %s
+{
+  namespace EmbeddedResources
+  {
+""" % NAMESPACE)
+
+
+for name in resources:
+    if resources[name]['Type'] == 'File':
+        WriteResource(cpp, resources[name])
+    else:
+        for f in resources[name]['Files']:
+            WriteResource(cpp, resources[name]['Files'][f])
+
+
+
+#####################################################################
+## Write the accessors to the file resources in .cpp
+#####################################################################
+
+cpp.write("""
+    const void* GetFileResourceBuffer(FileResourceId id)
+    {
+      switch (id)
+      {
+""")
+for name in resources:
+    if resources[name]['Type'] == 'File':
+        cpp.write('      case %s:\n' % name)
+        cpp.write('        return resource%dBuffer;\n' % resources[name]['Index'])
+
+cpp.write("""
+      default:
+        throw %s;
+      }
+    }
+
+    size_t GetFileResourceSize(FileResourceId id)
+    {
+      switch (id)
+      {
+""" % OUT_OF_RANGE_EXCEPTION)
+
+for name in resources:
+    if resources[name]['Type'] == 'File':
+        cpp.write('      case %s:\n' % name)
+        cpp.write('        return resource%dSize;\n' % resources[name]['Index'])
+
+cpp.write("""
+      default:
+        throw %s;
+      }
+    }
+""" % OUT_OF_RANGE_EXCEPTION)
+
+
+
+#####################################################################
+## Write the accessors to the directory resources in .cpp
+#####################################################################
+
+cpp.write("""
+    const void* GetDirectoryResourceBuffer(DirectoryResourceId id, const char* path)
+    {
+      switch (id)
+      {
+""")
+
+for name in resources:
+    if resources[name]['Type'] == 'Directory':
+        cpp.write('      case %s:\n' % name)
+        isFirst = True
+        for path in resources[name]['Files']:
+            cpp.write('        if (!strcmp(path, "%s"))\n' % path)
+            cpp.write('          return resource%dBuffer;\n' % resources[name]['Files'][path]['Index'])
+        cpp.write('        throw %s;\n\n' % INEXISTENT_PATH_EXCEPTION)
+
+cpp.write("""      default:
+        throw %s;
+      }
+    }
+
+    size_t GetDirectoryResourceSize(DirectoryResourceId id, const char* path)
+    {
+      switch (id)
+      {
+""" % OUT_OF_RANGE_EXCEPTION)
+
+for name in resources:
+    if resources[name]['Type'] == 'Directory':
+        cpp.write('      case %s:\n' % name)
+        isFirst = True
+        for path in resources[name]['Files']:
+            cpp.write('        if (!strcmp(path, "%s"))\n' % path)
+            cpp.write('          return resource%dSize;\n' % resources[name]['Files'][path]['Index'])
+        cpp.write('        throw %s;\n\n' % INEXISTENT_PATH_EXCEPTION)
+
+cpp.write("""      default:
+        throw %s;
+      }
+    }
+""" % OUT_OF_RANGE_EXCEPTION)
+
+
+
+
+#####################################################################
+## List the resources in a directory
+#####################################################################
+
+cpp.write("""
+    void ListResources(std::list<std::string>& result, DirectoryResourceId id)
+    {
+      result.clear();
+
+      switch (id)
+      {
+""")
+
+for name in resources:
+    if resources[name]['Type'] == 'Directory':
+        cpp.write('      case %s:\n' % name)
+        for path in sorted(resources[name]['Files']):
+            cpp.write('        result.push_back("%s");\n' % path)
+        cpp.write('        break;\n\n')
+
+cpp.write("""      default:
+        throw %s;
+      }
+    }
+""" % OUT_OF_RANGE_EXCEPTION)
+
+
+
+
+#####################################################################
+## Write the convenience wrappers in .cpp
+#####################################################################
+
+cpp.write("""
+    void GetFileResource(std::string& result, FileResourceId id)
+    {
+      size_t size = GetFileResourceSize(id);
+      result.resize(size);
+      if (size > 0)
+        memcpy(&result[0], GetFileResourceBuffer(id), size);
+    }
+
+    void GetDirectoryResource(std::string& result, DirectoryResourceId id, const char* path)
+    {
+      size_t size = GetDirectoryResourceSize(id, path);
+      result.resize(size);
+      if (size > 0)
+        memcpy(&result[0], GetDirectoryResourceBuffer(id, path), size);
+    }
+  }
+}
+""")
+cpp.close()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/MinGW-W64-Toolchain32.cmake	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,17 @@
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Windows)
+
+# which compilers to use for C and C++
+set(CMAKE_C_COMPILER i686-w64-mingw32-gcc)
+set(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)
+set(CMAKE_RC_COMPILER i686-w64-mingw32-windres)
+
+# here is the target environment located
+set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32)
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/MinGW-W64-Toolchain64.cmake	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,17 @@
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Windows)
+
+# which compilers to use for C and C++
+set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
+set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
+set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
+
+# here is the target environment located
+set(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32)
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/MinGWToolchain.cmake	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,17 @@
+# the name of the target operating system
+set(CMAKE_SYSTEM_NAME Windows)
+
+# which compilers to use for C and C++
+set(CMAKE_C_COMPILER i586-mingw32msvc-gcc)
+set(CMAKE_CXX_COMPILER i586-mingw32msvc-g++)
+set(CMAKE_RC_COMPILER i586-mingw32msvc-windres)
+
+# here is the target environment located
+set(CMAKE_FIND_ROOT_PATH /usr/i586-mingw32msvc)
+
+# adjust the default behaviour of the FIND_XXX() commands:
+# search headers and libraries in the target environment, search 
+# programs in the host environment
+set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/ThirdParty/VisualStudio/stdint.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,259 @@
+// ISO C9x  compliant stdint.h for Microsoft Visual Studio
+// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 
+// 
+//  Copyright (c) 2006-2013 Alexander Chemeris
+// 
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+// 
+//   1. Redistributions of source code must retain the above copyright notice,
+//      this list of conditions and the following disclaimer.
+// 
+//   2. Redistributions in binary form must reproduce the above copyright
+//      notice, this list of conditions and the following disclaimer in the
+//      documentation and/or other materials provided with the distribution.
+// 
+//   3. Neither the name of the product nor the names of its contributors may
+//      be used to endorse or promote products derived from this software
+//      without specific prior written permission.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// 
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef _MSC_VER // [
+#error "Use this header only with Microsoft Visual C++ compilers!"
+#endif // _MSC_VER ]
+
+#ifndef _MSC_STDINT_H_ // [
+#define _MSC_STDINT_H_
+
+#if _MSC_VER > 1000
+#pragma once
+#endif
+
+#if _MSC_VER >= 1600 // [
+#include <stdint.h>
+#else // ] _MSC_VER >= 1600 [
+
+#include <limits.h>
+
+// For Visual Studio 6 in C++ mode and for many Visual Studio versions when
+// compiling for ARM we should wrap <wchar.h> include with 'extern "C++" {}'
+// or compiler give many errors like this:
+//   error C2733: second C linkage of overloaded function 'wmemchr' not allowed
+#ifdef __cplusplus
+extern "C" {
+#endif
+#  include <wchar.h>
+#ifdef __cplusplus
+}
+#endif
+
+// Define _W64 macros to mark types changing their size, like intptr_t.
+#ifndef _W64
+#  if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300
+#     define _W64 __w64
+#  else
+#     define _W64
+#  endif
+#endif
+
+
+// 7.18.1 Integer types
+
+// 7.18.1.1 Exact-width integer types
+
+// Visual Studio 6 and Embedded Visual C++ 4 doesn't
+// realize that, e.g. char has the same size as __int8
+// so we give up on __intX for them.
+#if (_MSC_VER < 1300)
+   typedef signed char       int8_t;
+   typedef signed short      int16_t;
+   typedef signed int        int32_t;
+   typedef unsigned char     uint8_t;
+   typedef unsigned short    uint16_t;
+   typedef unsigned int      uint32_t;
+#else
+   typedef signed __int8     int8_t;
+   typedef signed __int16    int16_t;
+   typedef signed __int32    int32_t;
+   typedef unsigned __int8   uint8_t;
+   typedef unsigned __int16  uint16_t;
+   typedef unsigned __int32  uint32_t;
+#endif
+typedef signed __int64       int64_t;
+typedef unsigned __int64     uint64_t;
+
+
+// 7.18.1.2 Minimum-width integer types
+typedef int8_t    int_least8_t;
+typedef int16_t   int_least16_t;
+typedef int32_t   int_least32_t;
+typedef int64_t   int_least64_t;
+typedef uint8_t   uint_least8_t;
+typedef uint16_t  uint_least16_t;
+typedef uint32_t  uint_least32_t;
+typedef uint64_t  uint_least64_t;
+
+// 7.18.1.3 Fastest minimum-width integer types
+typedef int8_t    int_fast8_t;
+typedef int16_t   int_fast16_t;
+typedef int32_t   int_fast32_t;
+typedef int64_t   int_fast64_t;
+typedef uint8_t   uint_fast8_t;
+typedef uint16_t  uint_fast16_t;
+typedef uint32_t  uint_fast32_t;
+typedef uint64_t  uint_fast64_t;
+
+// 7.18.1.4 Integer types capable of holding object pointers
+#ifdef _WIN64 // [
+   typedef signed __int64    intptr_t;
+   typedef unsigned __int64  uintptr_t;
+#else // _WIN64 ][
+   typedef _W64 signed int   intptr_t;
+   typedef _W64 unsigned int uintptr_t;
+#endif // _WIN64 ]
+
+// 7.18.1.5 Greatest-width integer types
+typedef int64_t   intmax_t;
+typedef uint64_t  uintmax_t;
+
+
+// 7.18.2 Limits of specified-width integer types
+
+#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [   See footnote 220 at page 257 and footnote 221 at page 259
+
+// 7.18.2.1 Limits of exact-width integer types
+#define INT8_MIN     ((int8_t)_I8_MIN)
+#define INT8_MAX     _I8_MAX
+#define INT16_MIN    ((int16_t)_I16_MIN)
+#define INT16_MAX    _I16_MAX
+#define INT32_MIN    ((int32_t)_I32_MIN)
+#define INT32_MAX    _I32_MAX
+#define INT64_MIN    ((int64_t)_I64_MIN)
+#define INT64_MAX    _I64_MAX
+#define UINT8_MAX    _UI8_MAX
+#define UINT16_MAX   _UI16_MAX
+#define UINT32_MAX   _UI32_MAX
+#define UINT64_MAX   _UI64_MAX
+
+// 7.18.2.2 Limits of minimum-width integer types
+#define INT_LEAST8_MIN    INT8_MIN
+#define INT_LEAST8_MAX    INT8_MAX
+#define INT_LEAST16_MIN   INT16_MIN
+#define INT_LEAST16_MAX   INT16_MAX
+#define INT_LEAST32_MIN   INT32_MIN
+#define INT_LEAST32_MAX   INT32_MAX
+#define INT_LEAST64_MIN   INT64_MIN
+#define INT_LEAST64_MAX   INT64_MAX
+#define UINT_LEAST8_MAX   UINT8_MAX
+#define UINT_LEAST16_MAX  UINT16_MAX
+#define UINT_LEAST32_MAX  UINT32_MAX
+#define UINT_LEAST64_MAX  UINT64_MAX
+
+// 7.18.2.3 Limits of fastest minimum-width integer types
+#define INT_FAST8_MIN    INT8_MIN
+#define INT_FAST8_MAX    INT8_MAX
+#define INT_FAST16_MIN   INT16_MIN
+#define INT_FAST16_MAX   INT16_MAX
+#define INT_FAST32_MIN   INT32_MIN
+#define INT_FAST32_MAX   INT32_MAX
+#define INT_FAST64_MIN   INT64_MIN
+#define INT_FAST64_MAX   INT64_MAX
+#define UINT_FAST8_MAX   UINT8_MAX
+#define UINT_FAST16_MAX  UINT16_MAX
+#define UINT_FAST32_MAX  UINT32_MAX
+#define UINT_FAST64_MAX  UINT64_MAX
+
+// 7.18.2.4 Limits of integer types capable of holding object pointers
+#ifdef _WIN64 // [
+#  define INTPTR_MIN   INT64_MIN
+#  define INTPTR_MAX   INT64_MAX
+#  define UINTPTR_MAX  UINT64_MAX
+#else // _WIN64 ][
+#  define INTPTR_MIN   INT32_MIN
+#  define INTPTR_MAX   INT32_MAX
+#  define UINTPTR_MAX  UINT32_MAX
+#endif // _WIN64 ]
+
+// 7.18.2.5 Limits of greatest-width integer types
+#define INTMAX_MIN   INT64_MIN
+#define INTMAX_MAX   INT64_MAX
+#define UINTMAX_MAX  UINT64_MAX
+
+// 7.18.3 Limits of other integer types
+
+#ifdef _WIN64 // [
+#  define PTRDIFF_MIN  _I64_MIN
+#  define PTRDIFF_MAX  _I64_MAX
+#else  // _WIN64 ][
+#  define PTRDIFF_MIN  _I32_MIN
+#  define PTRDIFF_MAX  _I32_MAX
+#endif  // _WIN64 ]
+
+#define SIG_ATOMIC_MIN  INT_MIN
+#define SIG_ATOMIC_MAX  INT_MAX
+
+#ifndef SIZE_MAX // [
+#  ifdef _WIN64 // [
+#     define SIZE_MAX  _UI64_MAX
+#  else // _WIN64 ][
+#     define SIZE_MAX  _UI32_MAX
+#  endif // _WIN64 ]
+#endif // SIZE_MAX ]
+
+// WCHAR_MIN and WCHAR_MAX are also defined in <wchar.h>
+#ifndef WCHAR_MIN // [
+#  define WCHAR_MIN  0
+#endif  // WCHAR_MIN ]
+#ifndef WCHAR_MAX // [
+#  define WCHAR_MAX  _UI16_MAX
+#endif  // WCHAR_MAX ]
+
+#define WINT_MIN  0
+#define WINT_MAX  _UI16_MAX
+
+#endif // __STDC_LIMIT_MACROS ]
+
+
+// 7.18.4 Limits of other integer types
+
+#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [   See footnote 224 at page 260
+
+// 7.18.4.1 Macros for minimum-width integer constants
+
+#define INT8_C(val)  val##i8
+#define INT16_C(val) val##i16
+#define INT32_C(val) val##i32
+#define INT64_C(val) val##i64
+
+#define UINT8_C(val)  val##ui8
+#define UINT16_C(val) val##ui16
+#define UINT32_C(val) val##ui32
+#define UINT64_C(val) val##ui64
+
+// 7.18.4.2 Macros for greatest-width integer constants
+// These #ifndef's are needed to prevent collisions with <boost/cstdint.hpp>.
+// Check out Issue 9 for the details.
+#ifndef INTMAX_C //   [
+#  define INTMAX_C   INT64_C
+#endif // INTMAX_C    ]
+#ifndef UINTMAX_C //  [
+#  define UINTMAX_C  UINT64_C
+#endif // UINTMAX_C   ]
+
+#endif // __STDC_CONSTANT_MACROS ]
+
+#endif // _MSC_VER >= 1600 ]
+
+#endif // _MSC_STDINT_H_ ]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/ThirdParty/base64/base64.cpp	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,128 @@
+/* 
+   base64.cpp and base64.h
+
+   Copyright (C) 2004-2008 René Nyffenegger
+
+   This source code is provided 'as-is', without any express or implied
+   warranty. In no event will the author be held liable for any damages
+   arising from the use of this software.
+
+   Permission is granted to anyone to use this software for any purpose,
+   including commercial applications, and to alter it and redistribute it
+   freely, subject to the following restrictions:
+
+   1. The origin of this source code must not be misrepresented; you must not
+      claim that you wrote the original source code. If you use this source code
+      in a product, an acknowledgment in the product documentation would be
+      appreciated but is not required.
+
+   2. Altered source versions must be plainly marked as such, and must not be
+      misrepresented as being the original source code.
+
+   3. This notice may not be removed or altered from any source distribution.
+
+   René Nyffenegger rene.nyffenegger@adp-gmbh.ch
+
+*/
+
+#include "base64.h"
+#include <string.h>
+
+static const std::string base64_chars = 
+             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+             "abcdefghijklmnopqrstuvwxyz"
+             "0123456789+/";
+
+
+static inline bool is_base64(unsigned char c) {
+  return (isalnum(c) || (c == '+') || (c == '/'));
+}
+
+std::string base64_encode(const std::string& stringToEncode) 
+{
+  const unsigned char* bytes_to_encode = reinterpret_cast<const unsigned char*>
+    (stringToEncode.size() > 0 ? &stringToEncode[0] : NULL);
+  unsigned int in_len = stringToEncode.size();
+  
+  std::string ret;
+  int i = 0;
+  int j = 0;
+  unsigned char char_array_3[3];
+  unsigned char char_array_4[4];
+
+  while (in_len--) {
+    char_array_3[i++] = *(bytes_to_encode++);
+    if (i == 3) {
+      char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+      char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
+      char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
+      char_array_4[3] = char_array_3[2] & 0x3f;
+
+      for(i = 0; (i <4) ; i++)
+        ret += base64_chars[char_array_4[i]];
+      i = 0;
+    }
+  }
+
+  if (i)
+  {
+    for(j = i; j < 3; j++)
+      char_array_3[j] = '\0';
+
+    char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+    char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
+    char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
+    char_array_4[3] = char_array_3[2] & 0x3f;
+
+    for (j = 0; (j < i + 1); j++)
+      ret += base64_chars[char_array_4[j]];
+
+    while((i++ < 3))
+      ret += '=';
+
+  }
+
+  return ret;
+}
+
+
+std::string base64_decode(const std::string& encoded_string) {
+  int in_len = encoded_string.size();
+  int i = 0;
+  int j = 0;
+  int in_ = 0;
+  unsigned char char_array_4[4], char_array_3[3];
+  std::string ret;
+
+  while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
+    char_array_4[i++] = encoded_string[in_]; in_++;
+    if (i ==4) {
+      for (i = 0; i <4; i++)
+        char_array_4[i] = base64_chars.find(char_array_4[i]);
+
+      char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
+      char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
+      char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
+
+      for (i = 0; (i < 3); i++)
+        ret += char_array_3[i];
+      i = 0;
+    }
+  }
+
+  if (i) {
+    for (j = i; j <4; j++)
+      char_array_4[j] = 0;
+
+    for (j = 0; j <4; j++)
+      char_array_4[j] = base64_chars.find(char_array_4[j]);
+
+    char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
+    char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
+    char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
+
+    for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
+  }
+
+  return ret;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/ThirdParty/base64/base64.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,4 @@
+#include <string>
+
+std::string base64_encode(const std::string& stringToEncode);
+std::string base64_decode(const std::string& s);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/WindowsResources.py	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,90 @@
+#!/usr/bin/python
+
+# Orthanc - A Lightweight, RESTful DICOM Store
+# Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+# Department, University Hospital of Liege, Belgium
+# Copyright (C) 2017 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/>.
+
+
+import os
+import sys
+import datetime
+
+if len(sys.argv) != 5:
+    sys.stderr.write('Usage: %s <Version> <ProductName> <Filename> <Description>\n\n' % sys.argv[0])
+    sys.stderr.write('Example: %s 0.9.1 Orthanc Orthanc.exe "Lightweight, RESTful DICOM server for medical imaging"\n' % sys.argv[0])
+    sys.exit(-1)
+
+SOURCE = os.path.join(os.path.dirname(__file__), 'WindowsResources.rc')
+
+VERSION = sys.argv[1]
+PRODUCT = sys.argv[2]
+FILENAME = sys.argv[3]
+DESCRIPTION = sys.argv[4]
+
+if VERSION == 'mainline':
+    VERSION = '999.999.999'
+    RELEASE = 'This is a mainline build, not an official release'
+else:
+    RELEASE = 'Release %s' % VERSION
+
+v = VERSION.split('.')
+if len(v) != 2 and len(v) != 3:
+    sys.stderr.write('Bad version number: %s\n' % VERSION)
+    sys.exit(-1)
+
+if len(v) == 2:
+    v.append('0')
+
+extension = os.path.splitext(FILENAME)[1]
+if extension.lower() == '.dll':
+    BLOCK = '040904E4'
+    TYPE = 'VFT_DLL'
+elif extension.lower() == '.exe':
+    #BLOCK = '040904B0'   # LANG_ENGLISH/SUBLANG_ENGLISH_US,
+    BLOCK = '040904E4'   # Lang=US English, CharSet=Windows Multilingual
+    TYPE = 'VFT_APP'
+else:
+    sys.stderr.write('Unsupported extension (.EXE or .DLL only): %s\n' % extension)
+    sys.exit(-1)
+
+
+with open(SOURCE, 'r') as source:
+    content = source.read()
+    content = content.replace('${VERSION_MAJOR}', v[0])
+    content = content.replace('${VERSION_MINOR}', v[1])
+    content = content.replace('${VERSION_PATCH}', v[2])
+    content = content.replace('${RELEASE}', RELEASE)
+    content = content.replace('${DESCRIPTION}', DESCRIPTION)
+    content = content.replace('${PRODUCT}', PRODUCT)   
+    content = content.replace('${FILENAME}', FILENAME)   
+    content = content.replace('${YEAR}', str(datetime.datetime.now().year))
+    content = content.replace('${BLOCK}', BLOCK)
+    content = content.replace('${TYPE}', TYPE)
+
+    sys.stdout.write(content)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Resources/WindowsResources.rc	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,30 @@
+#include <winver.h>
+
+VS_VERSION_INFO VERSIONINFO
+   FILEVERSION ${VERSION_MAJOR},${VERSION_MINOR},0,${VERSION_PATCH}
+   PRODUCTVERSION ${VERSION_MAJOR},${VERSION_MINOR},0,0
+   FILEOS VOS_NT_WINDOWS32
+   FILETYPE ${TYPE}
+   BEGIN
+      BLOCK "StringFileInfo"
+      BEGIN
+         BLOCK "${BLOCK}"
+         BEGIN
+            VALUE "Comments", "${RELEASE}"
+            VALUE "CompanyName", "University Hospital of Liege, Belgium"
+            VALUE "FileDescription", "${DESCRIPTION}"
+            VALUE "FileVersion", "${VERSION_MAJOR}.${VERSION_MINOR}.0.${VERSION_PATCH}"
+            VALUE "InternalName", "${PRODUCT}"
+            VALUE "LegalCopyright", "(c) 2012-${YEAR}, Sebastien Jodogne, University Hospital of Liege, Belgium"
+            VALUE "LegalTrademarks", "Licensing information is available at http://www.orthanc-server.com/"
+            VALUE "OriginalFilename", "${FILENAME}"
+            VALUE "ProductName", "${PRODUCT}"
+            VALUE "ProductVersion", "${VERSION_MAJOR}.${VERSION_MINOR}"
+         END
+      END
+
+      BLOCK "VarFileInfo"
+      BEGIN
+        VALUE "Translation", 0x409, 1252  // U.S. English
+      END
+   END
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Orthanc/Sdk-1.1.0/orthanc/OrthancCPlugin.h	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,5365 @@
+/**
+ * \mainpage
+ *
+ * This C/C++ SDK allows external developers to create plugins that
+ * can be loaded into Orthanc to extend its functionality. Each
+ * Orthanc plugin must expose 4 public functions with the following
+ * signatures:
+ * 
+ * -# <tt>int32_t OrthancPluginInitialize(const OrthancPluginContext* context)</tt>:
+ *    This function is invoked by Orthanc when it loads the plugin on startup.
+ *    The plugin must:
+ *    - Check its compatibility with the Orthanc version using
+ *      ::OrthancPluginCheckVersion().
+ *    - Store the context pointer so that it can use the plugin 
+ *      services of Orthanc.
+ *    - Register all its REST callbacks using ::OrthancPluginRegisterRestCallback().
+ *    - Possibly register its callback for received DICOM instances using ::OrthancPluginRegisterOnStoredInstanceCallback().
+ *    - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback().
+ *    - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea().
+ *    - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2().
+ *    - Possibly register a handler for C-Find SCP using OrthancPluginRegisterFindCallback().
+ *    - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback().
+ *    - Possibly register a handler for C-Move SCP using OrthancPluginRegisterMoveCallback().
+ *    - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback().
+ *    - Possibly register a callback to filter incoming HTTP requests using OrthancPluginRegisterIncomingHttpRequestFilter().
+ * -# <tt>void OrthancPluginFinalize()</tt>:
+ *    This function is invoked by Orthanc during its shutdown. The plugin
+ *    must free all its memory.
+ * -# <tt>const char* OrthancPluginGetName()</tt>:
+ *    The plugin must return a short string to identify itself.
+ * -# <tt>const char* OrthancPluginGetVersion()</tt>:
+ *    The plugin must return a string containing its version number.
+ *
+ * The name and the version of a plugin is only used to prevent it
+ * from being loaded twice. Note that, in C++, it is mandatory to
+ * declare these functions within an <tt>extern "C"</tt> section.
+ * 
+ * To ensure multi-threading safety, the various REST callbacks are
+ * guaranteed to be executed in mutual exclusion since Orthanc
+ * 0.8.5. If this feature is undesired (notably when developing
+ * high-performance plugins handling simultaneous requests), use
+ * ::OrthancPluginRegisterRestCallbackNoLock().
+ **/
+
+
+
+/**
+ * @defgroup Images Images and compression
+ * @brief Functions to deal with images and compressed buffers.
+ *
+ * @defgroup REST REST
+ * @brief Functions to answer REST requests in a callback.
+ *
+ * @defgroup Callbacks Callbacks
+ * @brief Functions to register and manage callbacks by the plugins.
+ *
+ * @defgroup DicomCallbaks DicomCallbaks
+ * @brief Functions to register and manage DICOM callbacks (worklists, C-Find, C-MOVE).
+ *
+ * @defgroup Orthanc Orthanc
+ * @brief Functions to access the content of the Orthanc server.
+ **/
+
+
+
+/**
+ * @defgroup Toolbox Toolbox
+ * @brief Generic functions to help with the creation of plugins.
+ **/
+
+
+
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+
+#pragma once
+
+
+#include <stdio.h>
+#include <string.h>
+
+#ifdef WIN32
+#define ORTHANC_PLUGINS_API __declspec(dllexport)
+#else
+#define ORTHANC_PLUGINS_API
+#endif
+
+#define ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER     1
+#define ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER     1
+#define ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER  0
+
+
+
+/********************************************************************
+ ** Check that function inlining is properly supported. The use of
+ ** inlining is required, to avoid the duplication of object code
+ ** between two compilation modules that would use the Orthanc Plugin
+ ** API.
+ ********************************************************************/
+
+/* If the auto-detection of the "inline" keyword below does not work
+   automatically and that your compiler is known to properly support
+   inlining, uncomment the following #define and adapt the definition
+   of "static inline". */
+
+/* #define ORTHANC_PLUGIN_INLINE static inline */
+
+#ifndef ORTHANC_PLUGIN_INLINE
+#  if __STDC_VERSION__ >= 199901L
+/*   This is C99 or above: http://predef.sourceforge.net/prestd.html */
+#    define ORTHANC_PLUGIN_INLINE static inline
+#  elif defined(__cplusplus)
+/*   This is C++ */
+#    define ORTHANC_PLUGIN_INLINE static inline
+#  elif defined(__GNUC__)
+/*   This is GCC running in C89 mode */
+#    define ORTHANC_PLUGIN_INLINE static __inline
+#  elif defined(_MSC_VER)
+/*   This is Visual Studio running in C89 mode */
+#    define ORTHANC_PLUGIN_INLINE static __inline
+#  else
+#    error Your compiler is not known to support the "inline" keyword
+#  endif
+#endif
+
+
+
+/********************************************************************
+ ** Inclusion of standard libraries.
+ ********************************************************************/
+
+/**
+ * For Microsoft Visual Studio, a compatibility "stdint.h" can be
+ * downloaded at the following URL:
+ * https://orthanc.googlecode.com/hg/Resources/ThirdParty/VisualStudio/stdint.h
+ **/
+#include <stdint.h>
+
+#include <stdlib.h>
+
+
+
+/********************************************************************
+ ** Definition of the Orthanc Plugin API.
+ ********************************************************************/
+
+/** @{ */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+  /**
+   * The various error codes that can be returned by the Orthanc core.
+   **/
+  typedef enum
+  {
+    OrthancPluginErrorCode_InternalError = -1    /*!< Internal error */,
+    OrthancPluginErrorCode_Success = 0    /*!< Success */,
+    OrthancPluginErrorCode_Plugin = 1    /*!< Error encountered within the plugin engine */,
+    OrthancPluginErrorCode_NotImplemented = 2    /*!< Not implemented yet */,
+    OrthancPluginErrorCode_ParameterOutOfRange = 3    /*!< Parameter out of range */,
+    OrthancPluginErrorCode_NotEnoughMemory = 4    /*!< Not enough memory */,
+    OrthancPluginErrorCode_BadParameterType = 5    /*!< Bad type for a parameter */,
+    OrthancPluginErrorCode_BadSequenceOfCalls = 6    /*!< Bad sequence of calls */,
+    OrthancPluginErrorCode_InexistentItem = 7    /*!< Accessing an inexistent item */,
+    OrthancPluginErrorCode_BadRequest = 8    /*!< Bad request */,
+    OrthancPluginErrorCode_NetworkProtocol = 9    /*!< Error in the network protocol */,
+    OrthancPluginErrorCode_SystemCommand = 10    /*!< Error while calling a system command */,
+    OrthancPluginErrorCode_Database = 11    /*!< Error with the database engine */,
+    OrthancPluginErrorCode_UriSyntax = 12    /*!< Badly formatted URI */,
+    OrthancPluginErrorCode_InexistentFile = 13    /*!< Inexistent file */,
+    OrthancPluginErrorCode_CannotWriteFile = 14    /*!< Cannot write to file */,
+    OrthancPluginErrorCode_BadFileFormat = 15    /*!< Bad file format */,
+    OrthancPluginErrorCode_Timeout = 16    /*!< Timeout */,
+    OrthancPluginErrorCode_UnknownResource = 17    /*!< Unknown resource */,
+    OrthancPluginErrorCode_IncompatibleDatabaseVersion = 18    /*!< Incompatible version of the database */,
+    OrthancPluginErrorCode_FullStorage = 19    /*!< The file storage is full */,
+    OrthancPluginErrorCode_CorruptedFile = 20    /*!< Corrupted file (e.g. inconsistent MD5 hash) */,
+    OrthancPluginErrorCode_InexistentTag = 21    /*!< Inexistent tag */,
+    OrthancPluginErrorCode_ReadOnly = 22    /*!< Cannot modify a read-only data structure */,
+    OrthancPluginErrorCode_IncompatibleImageFormat = 23    /*!< Incompatible format of the images */,
+    OrthancPluginErrorCode_IncompatibleImageSize = 24    /*!< Incompatible size of the images */,
+    OrthancPluginErrorCode_SharedLibrary = 25    /*!< Error while using a shared library (plugin) */,
+    OrthancPluginErrorCode_UnknownPluginService = 26    /*!< Plugin invoking an unknown service */,
+    OrthancPluginErrorCode_UnknownDicomTag = 27    /*!< Unknown DICOM tag */,
+    OrthancPluginErrorCode_BadJson = 28    /*!< Cannot parse a JSON document */,
+    OrthancPluginErrorCode_Unauthorized = 29    /*!< Bad credentials were provided to an HTTP request */,
+    OrthancPluginErrorCode_BadFont = 30    /*!< Badly formatted font file */,
+    OrthancPluginErrorCode_DatabasePlugin = 31    /*!< The plugin implementing a custom database back-end does not fulfill the proper interface */,
+    OrthancPluginErrorCode_StorageAreaPlugin = 32    /*!< Error in the plugin implementing a custom storage area */,
+    OrthancPluginErrorCode_EmptyRequest = 33    /*!< The request is empty */,
+    OrthancPluginErrorCode_NotAcceptable = 34    /*!< Cannot send a response which is acceptable according to the Accept HTTP header */,
+    OrthancPluginErrorCode_SQLiteNotOpened = 1000    /*!< SQLite: The database is not opened */,
+    OrthancPluginErrorCode_SQLiteAlreadyOpened = 1001    /*!< SQLite: Connection is already open */,
+    OrthancPluginErrorCode_SQLiteCannotOpen = 1002    /*!< SQLite: Unable to open the database */,
+    OrthancPluginErrorCode_SQLiteStatementAlreadyUsed = 1003    /*!< SQLite: This cached statement is already being referred to */,
+    OrthancPluginErrorCode_SQLiteExecute = 1004    /*!< SQLite: Cannot execute a command */,
+    OrthancPluginErrorCode_SQLiteRollbackWithoutTransaction = 1005    /*!< SQLite: Rolling back a nonexistent transaction (have you called Begin()?) */,
+    OrthancPluginErrorCode_SQLiteCommitWithoutTransaction = 1006    /*!< SQLite: Committing a nonexistent transaction */,
+    OrthancPluginErrorCode_SQLiteRegisterFunction = 1007    /*!< SQLite: Unable to register a function */,
+    OrthancPluginErrorCode_SQLiteFlush = 1008    /*!< SQLite: Unable to flush the database */,
+    OrthancPluginErrorCode_SQLiteCannotRun = 1009    /*!< SQLite: Cannot run a cached statement */,
+    OrthancPluginErrorCode_SQLiteCannotStep = 1010    /*!< SQLite: Cannot step over a cached statement */,
+    OrthancPluginErrorCode_SQLiteBindOutOfRange = 1011    /*!< SQLite: Bing a value while out of range (serious error) */,
+    OrthancPluginErrorCode_SQLitePrepareStatement = 1012    /*!< SQLite: Cannot prepare a cached statement */,
+    OrthancPluginErrorCode_SQLiteTransactionAlreadyStarted = 1013    /*!< SQLite: Beginning the same transaction twice */,
+    OrthancPluginErrorCode_SQLiteTransactionCommit = 1014    /*!< SQLite: Failure when committing the transaction */,
+    OrthancPluginErrorCode_SQLiteTransactionBegin = 1015    /*!< SQLite: Cannot start a transaction */,
+    OrthancPluginErrorCode_DirectoryOverFile = 2000    /*!< The directory to be created is already occupied by a regular file */,
+    OrthancPluginErrorCode_FileStorageCannotWrite = 2001    /*!< Unable to create a subdirectory or a file in the file storage */,
+    OrthancPluginErrorCode_DirectoryExpected = 2002    /*!< The specified path does not point to a directory */,
+    OrthancPluginErrorCode_HttpPortInUse = 2003    /*!< The TCP port of the HTTP server is privileged or already in use */,
+    OrthancPluginErrorCode_DicomPortInUse = 2004    /*!< The TCP port of the DICOM server is privileged or already in use */,
+    OrthancPluginErrorCode_BadHttpStatusInRest = 2005    /*!< This HTTP status is not allowed in a REST API */,
+    OrthancPluginErrorCode_RegularFileExpected = 2006    /*!< The specified path does not point to a regular file */,
+    OrthancPluginErrorCode_PathToExecutable = 2007    /*!< Unable to get the path to the executable */,
+    OrthancPluginErrorCode_MakeDirectory = 2008    /*!< Cannot create a directory */,
+    OrthancPluginErrorCode_BadApplicationEntityTitle = 2009    /*!< An application entity title (AET) cannot be empty or be longer than 16 characters */,
+    OrthancPluginErrorCode_NoCFindHandler = 2010    /*!< No request handler factory for DICOM C-FIND SCP */,
+    OrthancPluginErrorCode_NoCMoveHandler = 2011    /*!< No request handler factory for DICOM C-MOVE SCP */,
+    OrthancPluginErrorCode_NoCStoreHandler = 2012    /*!< No request handler factory for DICOM C-STORE SCP */,
+    OrthancPluginErrorCode_NoApplicationEntityFilter = 2013    /*!< No application entity filter */,
+    OrthancPluginErrorCode_NoSopClassOrInstance = 2014    /*!< DicomUserConnection: Unable to find the SOP class and instance */,
+    OrthancPluginErrorCode_NoPresentationContext = 2015    /*!< DicomUserConnection: No acceptable presentation context for modality */,
+    OrthancPluginErrorCode_DicomFindUnavailable = 2016    /*!< DicomUserConnection: The C-FIND command is not supported by the remote SCP */,
+    OrthancPluginErrorCode_DicomMoveUnavailable = 2017    /*!< DicomUserConnection: The C-MOVE command is not supported by the remote SCP */,
+    OrthancPluginErrorCode_CannotStoreInstance = 2018    /*!< Cannot store an instance */,
+    OrthancPluginErrorCode_CreateDicomNotString = 2019    /*!< Only string values are supported when creating DICOM instances */,
+    OrthancPluginErrorCode_CreateDicomOverrideTag = 2020    /*!< Trying to override a value inherited from a parent module */,
+    OrthancPluginErrorCode_CreateDicomUseContent = 2021    /*!< Use \"Content\" to inject an image into a new DICOM instance */,
+    OrthancPluginErrorCode_CreateDicomNoPayload = 2022    /*!< No payload is present for one instance in the series */,
+    OrthancPluginErrorCode_CreateDicomUseDataUriScheme = 2023    /*!< The payload of the DICOM instance must be specified according to Data URI scheme */,
+    OrthancPluginErrorCode_CreateDicomBadParent = 2024    /*!< Trying to attach a new DICOM instance to an inexistent resource */,
+    OrthancPluginErrorCode_CreateDicomParentIsInstance = 2025    /*!< Trying to attach a new DICOM instance to an instance (must be a series, study or patient) */,
+    OrthancPluginErrorCode_CreateDicomParentEncoding = 2026    /*!< Unable to get the encoding of the parent resource */,
+    OrthancPluginErrorCode_UnknownModality = 2027    /*!< Unknown modality */,
+    OrthancPluginErrorCode_BadJobOrdering = 2028    /*!< Bad ordering of filters in a job */,
+    OrthancPluginErrorCode_JsonToLuaTable = 2029    /*!< Cannot convert the given JSON object to a Lua table */,
+    OrthancPluginErrorCode_CannotCreateLua = 2030    /*!< Cannot create the Lua context */,
+    OrthancPluginErrorCode_CannotExecuteLua = 2031    /*!< Cannot execute a Lua command */,
+    OrthancPluginErrorCode_LuaAlreadyExecuted = 2032    /*!< Arguments cannot be pushed after the Lua function is executed */,
+    OrthancPluginErrorCode_LuaBadOutput = 2033    /*!< The Lua function does not give the expected number of outputs */,
+    OrthancPluginErrorCode_NotLuaPredicate = 2034    /*!< The Lua function is not a predicate (only true/false outputs allowed) */,
+    OrthancPluginErrorCode_LuaReturnsNoString = 2035    /*!< The Lua function does not return a string */,
+    OrthancPluginErrorCode_StorageAreaAlreadyRegistered = 2036    /*!< Another plugin has already registered a custom storage area */,
+    OrthancPluginErrorCode_DatabaseBackendAlreadyRegistered = 2037    /*!< Another plugin has already registered a custom database back-end */,
+    OrthancPluginErrorCode_DatabaseNotInitialized = 2038    /*!< Plugin trying to call the database during its initialization */,
+    OrthancPluginErrorCode_SslDisabled = 2039    /*!< Orthanc has been built without SSL support */,
+    OrthancPluginErrorCode_CannotOrderSlices = 2040    /*!< Unable to order the slices of the series */,
+    OrthancPluginErrorCode_NoWorklistHandler = 2041    /*!< No request handler factory for DICOM C-Find Modality SCP */,
+    OrthancPluginErrorCode_AlreadyExistingTag = 2042    /*!< Cannot override the value of a tag that already exists */,
+
+    _OrthancPluginErrorCode_INTERNAL = 0x7fffffff
+  } OrthancPluginErrorCode;
+
+
+  /**
+   * Forward declaration of one of the mandatory functions for Orthanc
+   * plugins.
+   **/
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName();
+
+
+  /**
+   * The various HTTP methods for a REST call.
+   **/
+  typedef enum
+  {
+    OrthancPluginHttpMethod_Get = 1,    /*!< GET request */
+    OrthancPluginHttpMethod_Post = 2,   /*!< POST request */
+    OrthancPluginHttpMethod_Put = 3,    /*!< PUT request */
+    OrthancPluginHttpMethod_Delete = 4, /*!< DELETE request */
+
+    _OrthancPluginHttpMethod_INTERNAL = 0x7fffffff
+  } OrthancPluginHttpMethod;
+
+
+  /**
+   * @brief The parameters of a REST request.
+   * @ingroup Callbacks
+   **/
+  typedef struct
+  {
+    /**
+     * @brief The HTTP method.
+     **/
+    OrthancPluginHttpMethod method;    
+
+    /**
+     * @brief The number of groups of the regular expression.
+     **/
+    uint32_t                groupsCount;
+
+    /**
+     * @brief The matched values for the groups of the regular expression.
+     **/
+    const char* const*      groups;
+
+    /**
+     * @brief For a GET request, the number of GET parameters.
+     **/
+    uint32_t                getCount;
+
+    /**
+     * @brief For a GET request, the keys of the GET parameters.
+     **/
+    const char* const*      getKeys;
+
+    /**
+     * @brief For a GET request, the values of the GET parameters.
+     **/
+    const char* const*      getValues;
+
+    /**
+     * @brief For a PUT or POST request, the content of the body.
+     **/
+    const char*             body;
+
+    /**
+     * @brief For a PUT or POST request, the number of bytes of the body.
+     **/
+    uint32_t                bodySize;
+
+
+    /* --------------------------------------------------
+       New in version 0.8.1
+       -------------------------------------------------- */
+
+    /**
+     * @brief The number of HTTP headers.
+     **/
+    uint32_t                headersCount;
+
+    /**
+     * @brief The keys of the HTTP headers (always converted to low-case).
+     **/
+    const char* const*      headersKeys;
+
+    /**
+     * @brief The values of the HTTP headers.
+     **/
+    const char* const*      headersValues;
+
+  } OrthancPluginHttpRequest;
+
+
+  typedef enum 
+  {
+    /* Generic services */
+    _OrthancPluginService_LogInfo = 1,
+    _OrthancPluginService_LogWarning = 2,
+    _OrthancPluginService_LogError = 3,
+    _OrthancPluginService_GetOrthancPath = 4,
+    _OrthancPluginService_GetOrthancDirectory = 5,
+    _OrthancPluginService_GetConfigurationPath = 6,
+    _OrthancPluginService_SetPluginProperty = 7,
+    _OrthancPluginService_GetGlobalProperty = 8,
+    _OrthancPluginService_SetGlobalProperty = 9,
+    _OrthancPluginService_GetCommandLineArgumentsCount = 10,
+    _OrthancPluginService_GetCommandLineArgument = 11,
+    _OrthancPluginService_GetExpectedDatabaseVersion = 12,
+    _OrthancPluginService_GetConfiguration = 13,
+    _OrthancPluginService_BufferCompression = 14,
+    _OrthancPluginService_ReadFile = 15,
+    _OrthancPluginService_WriteFile = 16,
+    _OrthancPluginService_GetErrorDescription = 17,
+    _OrthancPluginService_CallHttpClient = 18,
+    _OrthancPluginService_RegisterErrorCode = 19,
+    _OrthancPluginService_RegisterDictionaryTag = 20,
+    _OrthancPluginService_DicomBufferToJson = 21,
+    _OrthancPluginService_DicomInstanceToJson = 22,
+    _OrthancPluginService_CreateDicom = 23,
+    _OrthancPluginService_ComputeMd5 = 24,
+    _OrthancPluginService_ComputeSha1 = 25,
+    _OrthancPluginService_LookupDictionary = 26,
+    _OrthancPluginService_CallHttpClient2 = 27,
+    _OrthancPluginService_GenerateUuid = 28,
+
+    /* Registration of callbacks */
+    _OrthancPluginService_RegisterRestCallback = 1000,
+    _OrthancPluginService_RegisterOnStoredInstanceCallback = 1001,
+    _OrthancPluginService_RegisterStorageArea = 1002,
+    _OrthancPluginService_RegisterOnChangeCallback = 1003,
+    _OrthancPluginService_RegisterRestCallbackNoLock = 1004,
+    _OrthancPluginService_RegisterWorklistCallback = 1005,
+    _OrthancPluginService_RegisterDecodeImageCallback = 1006,
+    _OrthancPluginService_RegisterIncomingHttpRequestFilter = 1007,
+    _OrthancPluginService_RegisterFindCallback = 1008,
+    _OrthancPluginService_RegisterMoveCallback = 1009,
+
+    /* Sending answers to REST calls */
+    _OrthancPluginService_AnswerBuffer = 2000,
+    _OrthancPluginService_CompressAndAnswerPngImage = 2001,  /* Unused as of Orthanc 0.9.4 */
+    _OrthancPluginService_Redirect = 2002,
+    _OrthancPluginService_SendHttpStatusCode = 2003,
+    _OrthancPluginService_SendUnauthorized = 2004,
+    _OrthancPluginService_SendMethodNotAllowed = 2005,
+    _OrthancPluginService_SetCookie = 2006,
+    _OrthancPluginService_SetHttpHeader = 2007,
+    _OrthancPluginService_StartMultipartAnswer = 2008,
+    _OrthancPluginService_SendMultipartItem = 2009,
+    _OrthancPluginService_SendHttpStatus = 2010,
+    _OrthancPluginService_CompressAndAnswerImage = 2011,
+    _OrthancPluginService_SendMultipartItem2 = 2012,
+
+    /* Access to the Orthanc database and API */
+    _OrthancPluginService_GetDicomForInstance = 3000,
+    _OrthancPluginService_RestApiGet = 3001,
+    _OrthancPluginService_RestApiPost = 3002,
+    _OrthancPluginService_RestApiDelete = 3003,
+    _OrthancPluginService_RestApiPut = 3004,
+    _OrthancPluginService_LookupPatient = 3005,
+    _OrthancPluginService_LookupStudy = 3006,
+    _OrthancPluginService_LookupSeries = 3007,
+    _OrthancPluginService_LookupInstance = 3008,
+    _OrthancPluginService_LookupStudyWithAccessionNumber = 3009,
+    _OrthancPluginService_RestApiGetAfterPlugins = 3010,
+    _OrthancPluginService_RestApiPostAfterPlugins = 3011,
+    _OrthancPluginService_RestApiDeleteAfterPlugins = 3012,
+    _OrthancPluginService_RestApiPutAfterPlugins = 3013,
+    _OrthancPluginService_ReconstructMainDicomTags = 3014,
+    _OrthancPluginService_RestApiGet2 = 3015,
+
+    /* Access to DICOM instances */
+    _OrthancPluginService_GetInstanceRemoteAet = 4000,
+    _OrthancPluginService_GetInstanceSize = 4001,
+    _OrthancPluginService_GetInstanceData = 4002,
+    _OrthancPluginService_GetInstanceJson = 4003,
+    _OrthancPluginService_GetInstanceSimplifiedJson = 4004,
+    _OrthancPluginService_HasInstanceMetadata = 4005,
+    _OrthancPluginService_GetInstanceMetadata = 4006,
+    _OrthancPluginService_GetInstanceOrigin = 4007,
+
+    /* Services for plugins implementing a database back-end */
+    _OrthancPluginService_RegisterDatabaseBackend = 5000,
+    _OrthancPluginService_DatabaseAnswer = 5001,
+    _OrthancPluginService_RegisterDatabaseBackendV2 = 5002,
+    _OrthancPluginService_StorageAreaCreate = 5003,
+    _OrthancPluginService_StorageAreaRead = 5004,
+    _OrthancPluginService_StorageAreaRemove = 5005,
+
+    /* Primitives for handling images */
+    _OrthancPluginService_GetImagePixelFormat = 6000,
+    _OrthancPluginService_GetImageWidth = 6001,
+    _OrthancPluginService_GetImageHeight = 6002,
+    _OrthancPluginService_GetImagePitch = 6003,
+    _OrthancPluginService_GetImageBuffer = 6004,
+    _OrthancPluginService_UncompressImage = 6005,
+    _OrthancPluginService_FreeImage = 6006,
+    _OrthancPluginService_CompressImage = 6007,
+    _OrthancPluginService_ConvertPixelFormat = 6008,
+    _OrthancPluginService_GetFontsCount = 6009,
+    _OrthancPluginService_GetFontInfo = 6010,
+    _OrthancPluginService_DrawText = 6011,
+    _OrthancPluginService_CreateImage = 6012,
+    _OrthancPluginService_CreateImageAccessor = 6013,
+    _OrthancPluginService_DecodeDicomImage = 6014,
+
+    /* Primitives for handling C-Find, C-Move and worklists */
+    _OrthancPluginService_WorklistAddAnswer = 7000,
+    _OrthancPluginService_WorklistMarkIncomplete = 7001,
+    _OrthancPluginService_WorklistIsMatch = 7002,
+    _OrthancPluginService_WorklistGetDicomQuery = 7003,
+    _OrthancPluginService_FindAddAnswer = 7004,
+    _OrthancPluginService_FindMarkIncomplete = 7005,
+    _OrthancPluginService_GetFindQuerySize = 7006,
+    _OrthancPluginService_GetFindQueryTag = 7007,
+    _OrthancPluginService_GetFindQueryTagName = 7008,
+    _OrthancPluginService_GetFindQueryValue = 7009,
+
+    _OrthancPluginService_INTERNAL = 0x7fffffff
+  } _OrthancPluginService;
+
+
+  typedef enum
+  {
+    _OrthancPluginProperty_Description = 1,
+    _OrthancPluginProperty_RootUri = 2,
+    _OrthancPluginProperty_OrthancExplorer = 3,
+
+    _OrthancPluginProperty_INTERNAL = 0x7fffffff
+  } _OrthancPluginProperty;
+
+
+
+  /**
+   * The memory layout of the pixels of an image.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    /**
+     * @brief Graylevel 8bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * one byte.
+     **/
+    OrthancPluginPixelFormat_Grayscale8 = 1,
+
+    /**
+     * @brief Graylevel, unsigned 16bpp image.
+     *
+     * The image is graylevel. Each pixel is unsigned and stored in
+     * two bytes.
+     **/
+    OrthancPluginPixelFormat_Grayscale16 = 2,
+
+    /**
+     * @brief Graylevel, signed 16bpp image.
+     *
+     * The image is graylevel. Each pixel is signed and stored in two
+     * bytes.
+     **/
+    OrthancPluginPixelFormat_SignedGrayscale16 = 3,
+
+    /**
+     * @brief Color image in RGB24 format.
+     *
+     * This format describes a color image. The pixels are stored in 3
+     * consecutive bytes. The memory layout is RGB.
+     **/
+    OrthancPluginPixelFormat_RGB24 = 4,
+
+    /**
+     * @brief Color image in RGBA32 format.
+     *
+     * This format describes a color image. The pixels are stored in 4
+     * consecutive bytes. The memory layout is RGBA.
+     **/
+    OrthancPluginPixelFormat_RGBA32 = 5,
+
+    OrthancPluginPixelFormat_Unknown = 6,   /*!< Unknown pixel format */
+
+    _OrthancPluginPixelFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginPixelFormat;
+
+
+
+  /**
+   * The content types that are supported by Orthanc plugins.
+   **/
+  typedef enum
+  {
+    OrthancPluginContentType_Unknown = 0,      /*!< Unknown content type */
+    OrthancPluginContentType_Dicom = 1,        /*!< DICOM */
+    OrthancPluginContentType_DicomAsJson = 2,  /*!< JSON summary of a DICOM file */
+
+    _OrthancPluginContentType_INTERNAL = 0x7fffffff
+  } OrthancPluginContentType;
+
+
+
+  /**
+   * The supported types of DICOM resources.
+   **/
+  typedef enum
+  {
+    OrthancPluginResourceType_Patient = 0,     /*!< Patient */
+    OrthancPluginResourceType_Study = 1,       /*!< Study */
+    OrthancPluginResourceType_Series = 2,      /*!< Series */
+    OrthancPluginResourceType_Instance = 3,    /*!< Instance */
+    OrthancPluginResourceType_None = 4,        /*!< Unavailable resource type */
+
+    _OrthancPluginResourceType_INTERNAL = 0x7fffffff
+  } OrthancPluginResourceType;
+
+
+
+  /**
+   * The supported types of changes that can happen to DICOM resources.
+   * @ingroup Callbacks
+   **/
+  typedef enum
+  {
+    OrthancPluginChangeType_CompletedSeries = 0,    /*!< Series is now complete */
+    OrthancPluginChangeType_Deleted = 1,            /*!< Deleted resource */
+    OrthancPluginChangeType_NewChildInstance = 2,   /*!< A new instance was added to this resource */
+    OrthancPluginChangeType_NewInstance = 3,        /*!< New instance received */
+    OrthancPluginChangeType_NewPatient = 4,         /*!< New patient created */
+    OrthancPluginChangeType_NewSeries = 5,          /*!< New series created */
+    OrthancPluginChangeType_NewStudy = 6,           /*!< New study created */
+    OrthancPluginChangeType_StablePatient = 7,      /*!< Timeout: No new instance in this patient */
+    OrthancPluginChangeType_StableSeries = 8,       /*!< Timeout: No new instance in this series */
+    OrthancPluginChangeType_StableStudy = 9,        /*!< Timeout: No new instance in this study */
+    OrthancPluginChangeType_OrthancStarted = 10,    /*!< Orthanc has started */
+    OrthancPluginChangeType_OrthancStopped = 11,    /*!< Orthanc is stopping */
+    OrthancPluginChangeType_UpdatedAttachment = 12, /*!< Some user-defined attachment has changed for this resource */
+    OrthancPluginChangeType_UpdatedMetadata = 13,   /*!< Some user-defined metadata has changed for this resource */
+
+    _OrthancPluginChangeType_INTERNAL = 0x7fffffff
+  } OrthancPluginChangeType;
+
+
+  /**
+   * The compression algorithms that are supported by the Orthanc core.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    OrthancPluginCompressionType_Zlib = 0,          /*!< Standard zlib compression */
+    OrthancPluginCompressionType_ZlibWithSize = 1,  /*!< zlib, prefixed with uncompressed size (uint64_t) */
+    OrthancPluginCompressionType_Gzip = 2,          /*!< Standard gzip compression */
+    OrthancPluginCompressionType_GzipWithSize = 3,  /*!< gzip, prefixed with uncompressed size (uint64_t) */
+
+    _OrthancPluginCompressionType_INTERNAL = 0x7fffffff
+  } OrthancPluginCompressionType;
+
+
+  /**
+   * The image formats that are supported by the Orthanc core.
+   * @ingroup Images
+   **/
+  typedef enum
+  {
+    OrthancPluginImageFormat_Png = 0,    /*!< Image compressed using PNG */
+    OrthancPluginImageFormat_Jpeg = 1,   /*!< Image compressed using JPEG */
+    OrthancPluginImageFormat_Dicom = 2,  /*!< Image compressed using DICOM */
+
+    _OrthancPluginImageFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginImageFormat;
+
+
+  /**
+   * The value representations present in the DICOM standard (version 2013).
+   * @ingroup Toolbox
+   **/
+  typedef enum
+  {
+    OrthancPluginValueRepresentation_AE = 1,   /*!< Application Entity */
+    OrthancPluginValueRepresentation_AS = 2,   /*!< Age String */
+    OrthancPluginValueRepresentation_AT = 3,   /*!< Attribute Tag */
+    OrthancPluginValueRepresentation_CS = 4,   /*!< Code String */
+    OrthancPluginValueRepresentation_DA = 5,   /*!< Date */
+    OrthancPluginValueRepresentation_DS = 6,   /*!< Decimal String */
+    OrthancPluginValueRepresentation_DT = 7,   /*!< Date Time */
+    OrthancPluginValueRepresentation_FD = 8,   /*!< Floating Point Double */
+    OrthancPluginValueRepresentation_FL = 9,   /*!< Floating Point Single */
+    OrthancPluginValueRepresentation_IS = 10,  /*!< Integer String */
+    OrthancPluginValueRepresentation_LO = 11,  /*!< Long String */
+    OrthancPluginValueRepresentation_LT = 12,  /*!< Long Text */
+    OrthancPluginValueRepresentation_OB = 13,  /*!< Other Byte String */
+    OrthancPluginValueRepresentation_OF = 14,  /*!< Other Float String */
+    OrthancPluginValueRepresentation_OW = 15,  /*!< Other Word String */
+    OrthancPluginValueRepresentation_PN = 16,  /*!< Person Name */
+    OrthancPluginValueRepresentation_SH = 17,  /*!< Short String */
+    OrthancPluginValueRepresentation_SL = 18,  /*!< Signed Long */
+    OrthancPluginValueRepresentation_SQ = 19,  /*!< Sequence of Items */
+    OrthancPluginValueRepresentation_SS = 20,  /*!< Signed Short */
+    OrthancPluginValueRepresentation_ST = 21,  /*!< Short Text */
+    OrthancPluginValueRepresentation_TM = 22,  /*!< Time */
+    OrthancPluginValueRepresentation_UI = 23,  /*!< Unique Identifier (UID) */
+    OrthancPluginValueRepresentation_UL = 24,  /*!< Unsigned Long */
+    OrthancPluginValueRepresentation_UN = 25,  /*!< Unknown */
+    OrthancPluginValueRepresentation_US = 26,  /*!< Unsigned Short */
+    OrthancPluginValueRepresentation_UT = 27,  /*!< Unlimited Text */
+
+    _OrthancPluginValueRepresentation_INTERNAL = 0x7fffffff
+  } OrthancPluginValueRepresentation;
+
+
+  /**
+   * The possible output formats for a DICOM-to-JSON conversion.
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomToJson()
+   **/
+  typedef enum
+  {
+    OrthancPluginDicomToJsonFormat_Full = 1,    /*!< Full output, with most details */
+    OrthancPluginDicomToJsonFormat_Short = 2,   /*!< Tags output as hexadecimal numbers */
+    OrthancPluginDicomToJsonFormat_Human = 3,   /*!< Human-readable JSON */
+
+    _OrthancPluginDicomToJsonFormat_INTERNAL = 0x7fffffff
+  } OrthancPluginDicomToJsonFormat;
+
+
+  /**
+   * Flags to customize a DICOM-to-JSON conversion. By default, binary
+   * tags are formatted using Data URI scheme.
+   * @ingroup Toolbox
+   **/
+  typedef enum
+  {
+    OrthancPluginDicomToJsonFlags_IncludeBinary         = (1 << 0),  /*!< Include the binary tags */
+    OrthancPluginDicomToJsonFlags_IncludePrivateTags    = (1 << 1),  /*!< Include the private tags */
+    OrthancPluginDicomToJsonFlags_IncludeUnknownTags    = (1 << 2),  /*!< Include the tags unknown by the dictionary */
+    OrthancPluginDicomToJsonFlags_IncludePixelData      = (1 << 3),  /*!< Include the pixel data */
+    OrthancPluginDicomToJsonFlags_ConvertBinaryToAscii  = (1 << 4),  /*!< Output binary tags as-is, dropping non-ASCII */
+    OrthancPluginDicomToJsonFlags_ConvertBinaryToNull   = (1 << 5),  /*!< Signal binary tags as null values */
+
+    _OrthancPluginDicomToJsonFlags_INTERNAL = 0x7fffffff
+  } OrthancPluginDicomToJsonFlags;
+
+
+  /**
+   * Flags to the creation of a DICOM file.
+   * @ingroup Toolbox
+   * @see OrthancPluginCreateDicom()
+   **/
+  typedef enum
+  {
+    OrthancPluginCreateDicomFlags_DecodeDataUriScheme   = (1 << 0),  /*!< Decode fields encoded using data URI scheme */
+    OrthancPluginCreateDicomFlags_GenerateIdentifiers   = (1 << 1),  /*!< Automatically generate DICOM identifiers */
+
+    _OrthancPluginCreateDicomFlags_INTERNAL = 0x7fffffff
+  } OrthancPluginCreateDicomFlags;
+
+
+  /**
+   * The constraints on the DICOM identifiers that must be supported
+   * by the database plugins.
+   **/
+  typedef enum
+  {
+    OrthancPluginIdentifierConstraint_Equal = 1,           /*!< Equal */
+    OrthancPluginIdentifierConstraint_SmallerOrEqual = 2,  /*!< Less or equal */
+    OrthancPluginIdentifierConstraint_GreaterOrEqual = 3,  /*!< More or equal */
+    OrthancPluginIdentifierConstraint_Wildcard = 4,        /*!< Case-sensitive wildcard matching (with * and ?) */
+
+    _OrthancPluginIdentifierConstraint_INTERNAL = 0x7fffffff
+  } OrthancPluginIdentifierConstraint;
+
+
+  /**
+   * The origin of a DICOM instance that has been received by Orthanc.
+   **/
+  typedef enum
+  {
+    OrthancPluginInstanceOrigin_Unknown = 1,        /*!< Unknown origin */
+    OrthancPluginInstanceOrigin_DicomProtocol = 2,  /*!< Instance received through DICOM protocol */
+    OrthancPluginInstanceOrigin_RestApi = 3,        /*!< Instance received through REST API of Orthanc */
+    OrthancPluginInstanceOrigin_Plugin = 4,         /*!< Instance added to Orthanc by a plugin */
+    OrthancPluginInstanceOrigin_Lua = 5,            /*!< Instance added to Orthanc by a Lua script */
+
+    _OrthancPluginInstanceOrigin_INTERNAL = 0x7fffffff
+  } OrthancPluginInstanceOrigin;
+
+
+  /**
+   * @brief A memory buffer allocated by the core system of Orthanc.
+   *
+   * A memory buffer allocated by the core system of Orthanc. When the
+   * content of the buffer is not useful anymore, it must be free by a
+   * call to ::OrthancPluginFreeMemoryBuffer().
+   **/
+  typedef struct
+  {
+    /**
+     * @brief The content of the buffer.
+     **/
+    void*      data;
+
+    /**
+     * @brief The number of bytes in the buffer.
+     **/
+    uint32_t   size;
+  } OrthancPluginMemoryBuffer;
+
+
+
+
+  /**
+   * @brief Opaque structure that represents the HTTP connection to the client application.
+   * @ingroup Callback
+   **/
+  typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput;
+
+
+
+  /**
+   * @brief Opaque structure that represents a DICOM instance received by Orthanc.
+   **/
+  typedef struct _OrthancPluginDicomInstance_t OrthancPluginDicomInstance;
+
+
+
+  /**
+   * @brief Opaque structure that represents an image that is uncompressed in memory.
+   * @ingroup Images
+   **/
+  typedef struct _OrthancPluginImage_t OrthancPluginImage;
+
+
+
+  /**
+   * @brief Opaque structure that represents the storage area that is actually used by Orthanc.
+   * @ingroup Images
+   **/
+  typedef struct _OrthancPluginStorageArea_t OrthancPluginStorageArea;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents a C-Find query for worklists.
+   * @ingroup DicomCallbacks
+   **/
+  typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents the answers to a C-Find query for worklists.
+   * @ingroup DicomCallbacks
+   **/
+  typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents a C-Find query.
+   * @ingroup DicomCallbacks
+   **/
+  typedef struct _OrthancPluginFindQuery_t OrthancPluginFindQuery;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents the answers to a C-Find query for worklists.
+   * @ingroup DicomCallbacks
+   **/
+  typedef struct _OrthancPluginFindAnswers_t OrthancPluginFindAnswers;
+
+
+
+  /**
+   * @brief Signature of a callback function that answers to a REST request.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginRestCallback) (
+    OrthancPluginRestOutput* output,
+    const char* url,
+    const OrthancPluginHttpRequest* request);
+
+
+
+  /**
+   * @brief Signature of a callback function that is triggered when Orthanc receives a DICOM instance.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginOnStoredInstanceCallback) (
+    OrthancPluginDicomInstance* instance,
+    const char* instanceId);
+
+
+
+  /**
+   * @brief Signature of a callback function that is triggered when a change happens to some DICOM resource.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginOnChangeCallback) (
+    OrthancPluginChangeType changeType,
+    OrthancPluginResourceType resourceType,
+    const char* resourceId);
+
+
+
+  /**
+   * @brief Signature of a callback function to decode a DICOM instance as an image.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginDecodeImageCallback) (
+    OrthancPluginImage** target,
+    const void* dicom,
+    const uint32_t size,
+    uint32_t frameIndex);
+
+
+
+  /**
+   * @brief Signature of a function to free dynamic memory.
+   **/
+  typedef void (*OrthancPluginFree) (void* buffer);
+
+
+
+  /**
+   * @brief Callback for writing to the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc writes a file to the storage area.
+   *
+   * @param uuid The UUID of the file.
+   * @param content The content of the file.
+   * @param size The size of the file.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageCreate) (
+    const char* uuid,
+    const void* content,
+    int64_t size,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback for reading from the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc reads a file from the storage area.
+   *
+   * @param content The content of the file (output).
+   * @param size The size of the file (output).
+   * @param uuid The UUID of the file of interest.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageRead) (
+    void** content,
+    int64_t* size,
+    const char* uuid,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback for removing a file from the storage area.
+   *
+   * Signature of a callback function that is triggered when Orthanc deletes a file from the storage area.
+   *
+   * @param uuid The UUID of the file to be removed.
+   * @param type The content type corresponding to this file. 
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginStorageRemove) (
+    const char* uuid,
+    OrthancPluginContentType type);
+
+
+
+  /**
+   * @brief Callback to handle the C-Find SCP requests for worklists.
+   *
+   * Signature of a callback function that is triggered when Orthanc
+   * receives a C-Find SCP request against modality worklists.
+   *
+   * @param answers The target structure where answers must be stored.
+   * @param query The worklist query.
+   * @param issuerAet The Application Entity Title (AET) of the modality from which the request originates.
+   * @param calledAet The Application Entity Title (AET) of the modality that is called by the request.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) (
+    OrthancPluginWorklistAnswers*     answers,
+    const OrthancPluginWorklistQuery* query,
+    const char*                       issuerAet,
+    const char*                       calledAet);
+
+
+
+  /**
+   * @brief Callback to filter incoming HTTP requests received by Orthanc.
+   *
+   * Signature of a callback function that is triggered whenever
+   * Orthanc receives an HTTP/REST request, and that answers whether
+   * this request should be allowed. If the callback returns "0"
+   * ("false"), the server answers with HTTP status code 403
+   * (Forbidden).
+   *
+   * @param method The HTTP method used by the request.
+   * @param uri The URI of interest.
+   * @param ip The IP address of the HTTP client.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys The keys of the HTTP headers (always converted to low-case).
+   * @param headersValues The values of the HTTP headers.
+   * @return 0 if forbidden access, 1 if allowed access, -1 if error.
+   * @ingroup Callback
+   **/
+  typedef int32_t (*OrthancPluginIncomingHttpRequestFilter) (
+    OrthancPluginHttpMethod  method,
+    const char*              uri,
+    const char*              ip,
+    uint32_t                 headersCount,
+    const char* const*       headersKeys,
+    const char* const*       headersValues);
+
+
+
+  /**
+   * @brief Callback to handle incoming C-Find SCP requests.
+   *
+   * Signature of a callback function that is triggered whenever
+   * Orthanc receives a C-Find SCP request not concerning modality
+   * worklists.
+   *
+   * @param answers The target structure where answers must be stored.
+   * @param query The worklist query.
+   * @param issuerAet The Application Entity Title (AET) of the modality from which the request originates.
+   * @param calledAet The Application Entity Title (AET) of the modality that is called by the request.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginFindCallback) (
+    OrthancPluginFindAnswers*     answers,
+    const OrthancPluginFindQuery* query,
+    const char*                   issuerAet,
+    const char*                   calledAet);
+
+
+
+  /**
+   * @brief Callback to handle incoming C-Move SCP requests.
+   *
+   * Signature of a callback function that is triggered whenever
+   * Orthanc receives a C-Move SCP request. The callback receives the
+   * type of the resource of interest (study, series, instance...)
+   * together with the DICOM tags containing its identifiers. In turn,
+   * the plugin must create a driver object that will be responsible
+   * for driving the successive move suboperations.
+   *
+   * @param resourceType The type of the resource of interest. Note
+   * that this might be set to ResourceType_None if the
+   * QueryRetrieveLevel (0008,0052) tag was not provided by the
+   * issuer.
+   * @param patientId Content of the PatientID (0x0010, 0x0020) tag of the resource of interest. Might be NULL.
+   * @param accessionNumber Content of the AccessionNumber (0x0008, 0x0050) tag. Might be NULL.
+   * @param studyInstanceUid Content of the StudyInstanceUID (0x0020, 0x000d) tag. Might be NULL.
+   * @param seriesInstanceUid Content of the SeriesInstanceUID (0x0020, 0x000e) tag. Might be NULL.
+   * @param sopInstanceUid Content of the SOPInstanceUID (0x0008, 0x0018) tag. Might be NULL.
+   * @param issuerAet The Application Entity Title (AET) of the
+   * modality from which the request originates.
+   * @param sourceAet The Application Entity Title (AET) of the
+   * modality that should send its DICOM files to another modality.
+   * @param targetAet The Application Entity Title (AET) of the
+   * modality that should receive the DICOM files.
+   *
+   * @return The NULL value if the plugin cannot deal with this query,
+   * or a pointer to the driver object that is responsible for
+   * handling the successive move suboperations.
+   * 
+   * @note If targetAet equals sourceAet, this is actually a query/retrieve operation.
+   * @ingroup DicomCallbacks
+   **/
+  typedef void* (*OrthancPluginMoveCallback) (
+    OrthancPluginResourceType  resourceType,
+    const char*                patientId,
+    const char*                accessionNumber,
+    const char*                studyInstanceUid,
+    const char*                seriesInstanceUid,
+    const char*                sopInstanceUid,
+    const char*                issuerAet,
+    const char*                sourceAet,
+    const char*                targetAet,
+    uint16_t                   moveOriginatorId);
+    
+
+  /**
+   * @brief Callback to read the size of a C-Move driver.
+   * 
+   * Signature of a callback function that returns the number of
+   * C-Move suboperations that are to be achieved by the given C-Move
+   * driver. This driver is the return value of a previous call to the
+   * OrthancPluginMoveCallback() callback.
+   *
+   * @param moveDriver The C-Move driver of interest.
+   * @return The number of suboperations. 
+   **/
+  typedef uint32_t (*OrthancPluginGetMoveSize) (void* moveDriver);
+
+
+  /**
+   * @brief Callback to apply one C-Move suboperation.
+   * 
+   * Signature of a callback function that applies the next C-Move
+   * suboperation that os to be achieved by the given C-Move
+   * driver. This driver is the return value of a previous call to the
+   * OrthancPluginMoveCallback() callback.
+   *
+   * @param moveDriver The C-Move driver of interest.
+   * @return 0 if success, or the error code if failure.
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginApplyMove) (void* moveDriver);
+
+
+  /**
+   * @brief Callback to free one C-Move driver.
+   * 
+   * Signature of a callback function that releases the resources
+   * allocated by the given C-Move driver. This driver is the return
+   * value of a previous call to the OrthancPluginMoveCallback()
+   * callback.
+   *
+   * @param moveDriver The C-Move driver of interest.
+   **/
+  typedef void (*OrthancPluginFreeMove) (void* moveDriver);
+
+
+
+  /**
+   * @brief Data structure that contains information about the Orthanc core.
+   **/
+  typedef struct _OrthancPluginContext_t
+  {
+    void*                     pluginsManager;
+    const char*               orthancVersion;
+    OrthancPluginFree         Free;
+    OrthancPluginErrorCode  (*InvokeService) (struct _OrthancPluginContext_t* context,
+                                              _OrthancPluginService service,
+                                              const void* params);
+  } OrthancPluginContext;
+
+
+  
+  /**
+   * @brief An entry in the dictionary of DICOM tags.
+   **/
+  typedef struct
+  {
+    uint16_t                          group;            /*!< The group of the tag */
+    uint16_t                          element;          /*!< The element of the tag */
+    OrthancPluginValueRepresentation  vr;               /*!< The value representation of the tag */
+    uint32_t                          minMultiplicity;  /*!< The minimum multiplicity of the tag */
+    uint32_t                          maxMultiplicity;  /*!< The maximum multiplicity of the tag (0 means arbitrary) */
+  } OrthancPluginDictionaryEntry;
+
+
+
+  /**
+   * @brief Free a string.
+   * 
+   * Free a string that was allocated by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param str The string to be freed.
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeString(
+    OrthancPluginContext* context, 
+    char* str)
+  {
+    if (str != NULL)
+    {
+      context->Free(str);
+    }
+  }
+
+
+  /**
+   * @brief Check the compatibility of the plugin wrt. the version of its hosting Orthanc.
+   * 
+   * This function checks whether the version of this C header is
+   * compatible with the current version of Orthanc. The result of
+   * this function should always be checked in the
+   * OrthancPluginInitialize() entry point of the plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return 1 if and only if the versions are compatible. If the
+   * result is 0, the initialization of the plugin should fail.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginCheckVersion(
+    OrthancPluginContext* context)
+  {
+    int major, minor, revision;
+
+    if (sizeof(int32_t) != sizeof(OrthancPluginErrorCode) ||
+        sizeof(int32_t) != sizeof(OrthancPluginHttpMethod) ||
+        sizeof(int32_t) != sizeof(_OrthancPluginService) ||
+        sizeof(int32_t) != sizeof(_OrthancPluginProperty) ||
+        sizeof(int32_t) != sizeof(OrthancPluginPixelFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginContentType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginResourceType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginChangeType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginCompressionType) ||
+        sizeof(int32_t) != sizeof(OrthancPluginImageFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginValueRepresentation) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFormat) ||
+        sizeof(int32_t) != sizeof(OrthancPluginDicomToJsonFlags) ||
+        sizeof(int32_t) != sizeof(OrthancPluginCreateDicomFlags) ||
+        sizeof(int32_t) != sizeof(OrthancPluginIdentifierConstraint) ||
+        sizeof(int32_t) != sizeof(OrthancPluginInstanceOrigin))
+    {
+      /* Mismatch in the size of the enumerations */
+      return 0;
+    }
+
+    /* Assume compatibility with the mainline */
+    if (!strcmp(context->orthancVersion, "mainline"))
+    {
+      return 1;
+    }
+
+    /* Parse the version of the Orthanc core */
+    if ( 
+#ifdef _MSC_VER
+      sscanf_s
+#else
+      sscanf
+#endif
+      (context->orthancVersion, "%4d.%4d.%4d", &major, &minor, &revision) != 3)
+    {
+      return 0;
+    }
+
+    /* Check the major number of the version */
+
+    if (major > ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER)
+    {
+      return 1;
+    }
+
+    if (major < ORTHANC_PLUGINS_MINIMAL_MAJOR_NUMBER)
+    {
+      return 0;
+    }
+
+    /* Check the minor number of the version */
+
+    if (minor > ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER)
+    {
+      return 1;
+    }
+
+    if (minor < ORTHANC_PLUGINS_MINIMAL_MINOR_NUMBER)
+    {
+      return 0;
+    }
+
+    /* Check the revision number of the version */
+
+    if (revision >= ORTHANC_PLUGINS_MINIMAL_REVISION_NUMBER)
+    {
+      return 1;
+    }
+    else
+    {
+      return 0;
+    }
+  }
+
+
+  /**
+   * @brief Free a memory buffer.
+   * 
+   * Free a memory buffer that was allocated by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer to release.
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeMemoryBuffer(
+    OrthancPluginContext* context, 
+    OrthancPluginMemoryBuffer* buffer)
+  {
+    context->Free(buffer->data);
+  }
+
+
+  /**
+   * @brief Log an error.
+   *
+   * Log an error message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogError(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogError, message);
+  }
+
+
+  /**
+   * @brief Log a warning.
+   *
+   * Log a warning message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogWarning(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogWarning, message);
+  }
+
+
+  /**
+   * @brief Log an information.
+   *
+   * Log an information message using the Orthanc logging system.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param message The message to be logged.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginLogInfo(
+    OrthancPluginContext* context,
+    const char* message)
+  {
+    context->InvokeService(context, _OrthancPluginService_LogInfo, message);
+  }
+
+
+
+  typedef struct
+  {
+    const char* pathRegularExpression;
+    OrthancPluginRestCallback callback;
+  } _OrthancPluginRestCallback;
+
+  /**
+   * @brief Register a REST callback.
+   *
+   * This function registers a REST callback against a regular
+   * expression for a URI. This function must be called during the
+   * initialization of the plugin, i.e. inside the
+   * OrthancPluginInitialize() public function.
+   *
+   * Each REST callback is guaranteed to run in mutual exclusion.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param pathRegularExpression Regular expression for the URI. May contain groups.
+   * @param callback The callback function to handle the REST call.
+   * @see OrthancPluginRegisterRestCallbackNoLock()
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallback(
+    OrthancPluginContext*     context,
+    const char*               pathRegularExpression,
+    OrthancPluginRestCallback callback)
+  {
+    _OrthancPluginRestCallback params;
+    params.pathRegularExpression = pathRegularExpression;
+    params.callback = callback;
+    context->InvokeService(context, _OrthancPluginService_RegisterRestCallback, &params);
+  }
+
+
+
+  /**
+   * @brief Register a REST callback, without locking.
+   *
+   * This function registers a REST callback against a regular
+   * expression for a URI. This function must be called during the
+   * initialization of the plugin, i.e. inside the
+   * OrthancPluginInitialize() public function.
+   *
+   * Contrarily to OrthancPluginRegisterRestCallback(), the callback
+   * will NOT be invoked in mutual exclusion. This can be useful for
+   * high-performance plugins that must handle concurrent requests
+   * (Orthanc uses a pool of threads, one thread being assigned to
+   * each incoming HTTP request). Of course, it is up to the plugin to
+   * implement the required locking mechanisms.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param pathRegularExpression Regular expression for the URI. May contain groups.
+   * @param callback The callback function to handle the REST call.
+   * @see OrthancPluginRegisterRestCallback()
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterRestCallbackNoLock(
+    OrthancPluginContext*     context,
+    const char*               pathRegularExpression,
+    OrthancPluginRestCallback callback)
+  {
+    _OrthancPluginRestCallback params;
+    params.pathRegularExpression = pathRegularExpression;
+    params.callback = callback;
+    context->InvokeService(context, _OrthancPluginService_RegisterRestCallbackNoLock, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginOnStoredInstanceCallback callback;
+  } _OrthancPluginOnStoredInstanceCallback;
+
+  /**
+   * @brief Register a callback for received instances.
+   *
+   * This function registers a callback function that is called
+   * whenever a new DICOM instance is stored into the Orthanc core.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback function.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnStoredInstanceCallback(
+    OrthancPluginContext*                  context,
+    OrthancPluginOnStoredInstanceCallback  callback)
+  {
+    _OrthancPluginOnStoredInstanceCallback params;
+    params.callback = callback;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterOnStoredInstanceCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              answer;
+    uint32_t                 answerSize;
+    const char*              mimeType;
+  } _OrthancPluginAnswerBuffer;
+
+  /**
+   * @brief Answer to a REST request.
+   *
+   * This function answers to a REST request with the content of a memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param answer Pointer to the memory buffer containing the answer.
+   * @param answerSize Number of bytes of the answer.
+   * @param mimeType The MIME type of the answer.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginAnswerBuffer(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              answer,
+    uint32_t                 answerSize,
+    const char*              mimeType)
+  {
+    _OrthancPluginAnswerBuffer params;
+    params.output = output;
+    params.answer = answer;
+    params.answerSize = answerSize;
+    params.mimeType = mimeType;
+    context->InvokeService(context, _OrthancPluginService_AnswerBuffer, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput*  output;
+    OrthancPluginPixelFormat  format;
+    uint32_t                  width;
+    uint32_t                  height;
+    uint32_t                  pitch;
+    const void*               buffer;
+  } _OrthancPluginCompressAndAnswerPngImage;
+
+  typedef struct
+  {
+    OrthancPluginRestOutput*  output;
+    OrthancPluginImageFormat  imageFormat;
+    OrthancPluginPixelFormat  pixelFormat;
+    uint32_t                  width;
+    uint32_t                  height;
+    uint32_t                  pitch;
+    const void*               buffer;
+    uint8_t                   quality;
+  } _OrthancPluginCompressAndAnswerImage;
+
+
+  /**
+   * @brief Answer to a REST request with a PNG image.
+   *
+   * This function answers to a REST request with a PNG image. The
+   * parameters of this function describe a memory buffer that
+   * contains an uncompressed image. The image will be automatically compressed
+   * as a PNG image by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage(
+    OrthancPluginContext*     context,
+    OrthancPluginRestOutput*  output,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    const void*               buffer)
+  {
+    _OrthancPluginCompressAndAnswerImage params;
+    params.output = output;
+    params.imageFormat = OrthancPluginImageFormat_Png;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = 0;  /* No quality for PNG */
+    context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 instanceId;
+  } _OrthancPluginGetDicomForInstance;
+
+  /**
+   * @brief Retrieve a DICOM instance using its Orthanc identifier.
+   * 
+   * Retrieve a DICOM instance using its Orthanc identifier. The DICOM
+   * file is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param instanceId The Orthanc identifier of the DICOM instance of interest.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginGetDicomForInstance(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 instanceId)
+  {
+    _OrthancPluginGetDicomForInstance params;
+    params.target = target;
+    params.instanceId = instanceId;
+    return context->InvokeService(context, _OrthancPluginService_GetDicomForInstance, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+  } _OrthancPluginRestApiGet;
+
+  /**
+   * @brief Make a GET call to the built-in Orthanc REST API.
+   * 
+   * Make a GET call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiGetAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri)
+  {
+    _OrthancPluginRestApiGet params;
+    params.target = target;
+    params.uri = uri;
+    return context->InvokeService(context, _OrthancPluginService_RestApiGet, &params);
+  }
+
+
+
+  /**
+   * @brief Make a GET call to the REST API, as tainted by the plugins.
+   * 
+   * Make a GET call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiGet
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGetAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri)
+  {
+    _OrthancPluginRestApiGet params;
+    params.target = target;
+    params.uri = uri;
+    return context->InvokeService(context, _OrthancPluginService_RestApiGetAfterPlugins, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+    const char*                 body;
+    uint32_t                    bodySize;
+  } _OrthancPluginRestApiPostPut;
+
+  /**
+   * @brief Make a POST call to the built-in Orthanc REST API.
+   * 
+   * Make a POST call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the POST request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiPostAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPost(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPost, &params);
+  }
+
+
+  /**
+   * @brief Make a POST call to the REST API, as tainted by the plugins.
+   * 
+   * Make a POST call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the POST request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiPost
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPostAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPostAfterPlugins, &params);
+  }
+
+
+
+  /**
+   * @brief Make a DELETE call to the built-in Orthanc REST API.
+   * 
+   * Make a DELETE call to the built-in Orthanc REST API.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The URI to delete in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiDeleteAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDelete(
+    OrthancPluginContext*       context,
+    const char*                 uri)
+  {
+    return context->InvokeService(context, _OrthancPluginService_RestApiDelete, uri);
+  }
+
+
+  /**
+   * @brief Make a DELETE call to the REST API, as tainted by the plugins.
+   * 
+   * Make a DELETE call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. 
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The URI to delete in the built-in Orthanc API.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiDelete
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiDeleteAfterPlugins(
+    OrthancPluginContext*       context,
+    const char*                 uri)
+  {
+    return context->InvokeService(context, _OrthancPluginService_RestApiDeleteAfterPlugins, uri);
+  }
+
+
+
+  /**
+   * @brief Make a PUT call to the built-in Orthanc REST API.
+   * 
+   * Make a PUT call to the built-in Orthanc REST API. The result to
+   * the query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the PUT request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiPutAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPut(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPut, &params);
+  }
+
+
+
+  /**
+   * @brief Make a PUT call to the REST API, as tainted by the plugins.
+   * 
+   * Make a PUT call to the Orthanc REST API, after all the plugins
+   * are applied. In other words, if some plugin overrides or adds the
+   * called URI to the built-in Orthanc REST API, this call will
+   * return the result provided by this plugin. The result to the
+   * query is stored into a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param body The body of the PUT request.
+   * @param bodySize The size of the body.
+   * @return 0 if success, or the error code if failure.
+   * @note If the resource is not existing (error 404), the error code will be OrthancPluginErrorCode_UnknownResource.
+   * @see OrthancPluginRestApiPut
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiPutAfterPlugins(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    const char*                 body,
+    uint32_t                    bodySize)
+  {
+    _OrthancPluginRestApiPostPut params;
+    params.target = target;
+    params.uri = uri;
+    params.body = body;
+    params.bodySize = bodySize;
+    return context->InvokeService(context, _OrthancPluginService_RestApiPutAfterPlugins, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              argument;
+  } _OrthancPluginOutputPlusArgument;
+
+  /**
+   * @brief Redirect a REST request.
+   *
+   * This function answers to a REST request by redirecting the user
+   * to another URI using HTTP status 301.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param redirection Where to redirect.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              redirection)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = redirection;
+    context->InvokeService(context, _OrthancPluginService_Redirect, &params);
+  }
+
+
+
+  typedef struct
+  {
+    char**       result;
+    const char*  argument;
+  } _OrthancPluginRetrieveDynamicString;
+
+  /**
+   * @brief Look for a patient.
+   *
+   * Look for a patient stored in Orthanc, using its Patient ID tag (0x0010, 0x0020).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored patients).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param patientID The Patient ID of interest.
+   * @return The NULL value if the patient is non-existent, or a string containing the 
+   * Orthanc ID of the patient. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupPatient(
+    OrthancPluginContext*  context,
+    const char*            patientID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = patientID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupPatient, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a study.
+   *
+   * Look for a study stored in Orthanc, using its Study Instance UID tag (0x0020, 0x000d).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored studies).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param studyUID The Study Instance UID of interest.
+   * @return The NULL value if the study is non-existent, or a string containing the 
+   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudy(
+    OrthancPluginContext*  context,
+    const char*            studyUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = studyUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupStudy, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a study, using the accession number.
+   *
+   * Look for a study stored in Orthanc, using its Accession Number tag (0x0008, 0x0050).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored studies).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param accessionNumber The Accession Number of interest.
+   * @return The NULL value if the study is non-existent, or a string containing the 
+   * Orthanc ID of the study. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupStudyWithAccessionNumber(
+    OrthancPluginContext*  context,
+    const char*            accessionNumber)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = accessionNumber;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupStudyWithAccessionNumber, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for a series.
+   *
+   * Look for a series stored in Orthanc, using its Series Instance UID tag (0x0020, 0x000e).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored series).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param seriesUID The Series Instance UID of interest.
+   * @return The NULL value if the series is non-existent, or a string containing the 
+   * Orthanc ID of the series. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupSeries(
+    OrthancPluginContext*  context,
+    const char*            seriesUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = seriesUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupSeries, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Look for an instance.
+   *
+   * Look for an instance stored in Orthanc, using its SOP Instance UID tag (0x0008, 0x0018).
+   * This function uses the database index to run as fast as possible (it does not loop
+   * over all the stored instances).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param sopInstanceUID The SOP Instance UID of interest.
+   * @return The NULL value if the instance is non-existent, or a string containing the 
+   * Orthanc ID of the instance. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginLookupInstance(
+    OrthancPluginContext*  context,
+    const char*            sopInstanceUID)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = sopInstanceUID;
+
+    if (context->InvokeService(context, _OrthancPluginService_LookupInstance, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    uint16_t                 status;
+  } _OrthancPluginSendHttpStatusCode;
+
+  /**
+   * @brief Send a HTTP status code.
+   *
+   * This function answers to a REST request by sending a HTTP status
+   * code (such as "400 - Bad Request"). Note that:
+   * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer().
+   * - Redirections (status 301) must use ::OrthancPluginRedirect().
+   * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized().
+   * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param status The HTTP status code to be sent.
+   * @ingroup REST
+   * @see OrthancPluginSendHttpStatus()
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatusCode(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    uint16_t                 status)
+  {
+    _OrthancPluginSendHttpStatusCode params;
+    params.output = output;
+    params.status = status;
+    context->InvokeService(context, _OrthancPluginService_SendHttpStatusCode, &params);
+  }
+
+
+  /**
+   * @brief Signal that a REST request is not authorized.
+   *
+   * This function answers to a REST request by signaling that it is
+   * not authorized.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param realm The realm for the authorization process.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendUnauthorized(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              realm)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = realm;
+    context->InvokeService(context, _OrthancPluginService_SendUnauthorized, &params);
+  }
+
+
+  /**
+   * @brief Signal that this URI does not support this HTTP method.
+   *
+   * This function answers to a REST request by signaling that the
+   * queried URI does not support this method.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param allowedMethods The allowed methods for this URI (e.g. "GET,POST" after a PUT or a POST request).
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendMethodNotAllowed(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              allowedMethods)
+  {
+    _OrthancPluginOutputPlusArgument params;
+    params.output = output;
+    params.argument = allowedMethods;
+    context->InvokeService(context, _OrthancPluginService_SendMethodNotAllowed, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              key;
+    const char*              value;
+  } _OrthancPluginSetHttpHeader;
+
+  /**
+   * @brief Set a cookie.
+   *
+   * This function sets a cookie in the HTTP client.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param cookie The cookie to be set.
+   * @param value The value of the cookie.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetCookie(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              cookie,
+    const char*              value)
+  {
+    _OrthancPluginSetHttpHeader params;
+    params.output = output;
+    params.key = cookie;
+    params.value = value;
+    context->InvokeService(context, _OrthancPluginService_SetCookie, &params);
+  }
+
+
+  /**
+   * @brief Set some HTTP header.
+   *
+   * This function sets a HTTP header in the HTTP answer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param key The HTTP header to be set.
+   * @param value The value of the HTTP header.
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetHttpHeader(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              key,
+    const char*              value)
+  {
+    _OrthancPluginSetHttpHeader params;
+    params.output = output;
+    params.key = key;
+    params.value = value;
+    context->InvokeService(context, _OrthancPluginService_SetHttpHeader, &params);
+  }
+
+
+  typedef struct
+  {
+    char**                       resultStringToFree;
+    const char**                 resultString;
+    int64_t*                     resultInt64;
+    const char*                  key;
+    OrthancPluginDicomInstance*  instance;
+    OrthancPluginInstanceOrigin* resultOrigin;   /* New in Orthanc 0.9.5 SDK */
+  } _OrthancPluginAccessDicomInstance;
+
+
+  /**
+   * @brief Get the AET of a DICOM instance.
+   *
+   * This function returns the Application Entity Title (AET) of the
+   * DICOM modality from which a DICOM instance originates.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The AET if success, NULL if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceRemoteAet(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceRemoteAet, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the size of a DICOM file.
+   *
+   * This function returns the number of bytes of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The size of the file, -1 in case of error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int64_t OrthancPluginGetInstanceSize(
+    OrthancPluginContext*       context,
+    OrthancPluginDicomInstance* instance)
+  {
+    int64_t size;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultInt64 = &size;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSize, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return -1;
+    }
+    else
+    {
+      return size;
+    }
+  }
+
+
+  /**
+   * @brief Get the data of a DICOM file.
+   *
+   * This function returns a pointer to the content of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The pointer to the DICOM data, NULL in case of error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceData(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceData, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the DICOM tag hierarchy as a JSON file.
+   *
+   * This function returns a pointer to a newly created string
+   * containing a JSON file. This JSON file encodes the tag hierarchy
+   * of the given DICOM instance.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The NULL value in case of error, or a string containing the JSON file.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceJson(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultStringToFree = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the DICOM tag hierarchy as a JSON file (with simplification).
+   *
+   * This function returns a pointer to a newly created string
+   * containing a JSON file. This JSON file encodes the tag hierarchy
+   * of the given DICOM instance. In contrast with
+   * ::OrthancPluginGetInstanceJson(), the returned JSON file is in
+   * its simplified version.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The NULL value in case of error, or a string containing the JSON file.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetInstanceSimplifiedJson(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance)
+  {
+    char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultStringToFree = &result;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceSimplifiedJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Check whether a DICOM instance is associated with some metadata.
+   *
+   * This function checks whether the DICOM instance of interest is
+   * associated with some metadata. As of Orthanc 0.8.1, in the
+   * callbacks registered by
+   * ::OrthancPluginRegisterOnStoredInstanceCallback(), the only
+   * possibly available metadata are "ReceptionDate", "RemoteAET" and
+   * "IndexInSeries".
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @param metadata The metadata of interest.
+   * @return 1 if the metadata is present, 0 if it is absent, -1 in case of error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginHasInstanceMetadata(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance,
+    const char*                  metadata)
+  {
+    int64_t result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultInt64 = &result;
+    params.instance = instance;
+    params.key = metadata;
+
+    if (context->InvokeService(context, _OrthancPluginService_HasInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return -1;
+    }
+    else
+    {
+      return (result != 0);
+    }
+  }
+
+
+  /**
+   * @brief Get the value of some metadata associated with a given DICOM instance.
+   *
+   * This functions returns the value of some metadata that is associated with the DICOM instance of interest.
+   * Before calling this function, the existence of the metadata must have been checked with
+   * ::OrthancPluginHasInstanceMetadata().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @param metadata The metadata of interest.
+   * @return The metadata value if success, NULL if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetInstanceMetadata(
+    OrthancPluginContext*        context,
+    OrthancPluginDicomInstance*  instance,
+    const char*                  metadata)
+  {
+    const char* result;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultString = &result;
+    params.instance = instance;
+    params.key = metadata;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceMetadata, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginStorageCreate  create;
+    OrthancPluginStorageRead    read;
+    OrthancPluginStorageRemove  remove;
+    OrthancPluginFree           free;
+  } _OrthancPluginRegisterStorageArea;
+
+  /**
+   * @brief Register a custom storage area.
+   *
+   * This function registers a custom storage area, to replace the
+   * built-in way Orthanc stores its files on the filesystem. This
+   * function must be called during the initialization of the plugin,
+   * i.e. inside the OrthancPluginInitialize() public function.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param create The callback function to store a file on the custom storage area.
+   * @param read The callback function to read a file from the custom storage area.
+   * @param remove The callback function to remove a file from the custom storage area.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterStorageArea(
+    OrthancPluginContext*       context,
+    OrthancPluginStorageCreate  create,
+    OrthancPluginStorageRead    read,
+    OrthancPluginStorageRemove  remove)
+  {
+    _OrthancPluginRegisterStorageArea params;
+    params.create = create;
+    params.read = read;
+    params.remove = remove;
+
+#ifdef  __cplusplus
+    params.free = ::free;
+#else
+    params.free = free;
+#endif
+
+    context->InvokeService(context, _OrthancPluginService_RegisterStorageArea, &params);
+  }
+
+
+
+  /**
+   * @brief Return the path to the Orthanc executable.
+   *
+   * This function returns the path to the Orthanc executable.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancPath(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetOrthancPath, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the directory containing the Orthanc.
+   *
+   * This function returns the path to the directory containing the Orthanc executable.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetOrthancDirectory(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetOrthancDirectory, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the path to the configuration file(s).
+   *
+   * This function returns the path to the configuration file(s) that
+   * was specified when starting Orthanc. Since version 0.9.1, this
+   * path can refer to a folder that stores a set of configuration
+   * files. This function is deprecated in favor of
+   * OrthancPluginGetConfiguration().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the path. This string must be freed by
+   * OrthancPluginFreeString().
+   * @see OrthancPluginGetConfiguration()
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfigurationPath(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConfigurationPath, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginOnChangeCallback callback;
+  } _OrthancPluginOnChangeCallback;
+
+  /**
+   * @brief Register a callback to monitor changes.
+   *
+   * This function registers a callback function that is called
+   * whenever a change happens to some DICOM resource.
+   *
+   * @warning If your change callback has to call the REST API of
+   * Orthanc, you should make these calls in a separate thread (with
+   * the events passing through a message queue). Otherwise, this
+   * could result in deadlocks in the presence of other plugins or Lua
+   * scripts.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback function.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRegisterOnChangeCallback(
+    OrthancPluginContext*          context,
+    OrthancPluginOnChangeCallback  callback)
+  {
+    _OrthancPluginOnChangeCallback params;
+    params.callback = callback;
+
+    context->InvokeService(context, _OrthancPluginService_RegisterOnChangeCallback, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const char* plugin;
+    _OrthancPluginProperty property;
+    const char* value;
+  } _OrthancPluginSetPluginProperty;
+
+
+  /**
+   * @brief Set the URI where the plugin provides its Web interface.
+   *
+   * For plugins that come with a Web interface, this function
+   * declares the entry path where to find this interface. This
+   * information is notably used in the "Plugins" page of Orthanc
+   * Explorer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param uri The root URI for this plugin.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetRootUri(
+    OrthancPluginContext*  context,
+    const char*            uri)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_RootUri;
+    params.value = uri;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  /**
+   * @brief Set a description for this plugin.
+   *
+   * Set a description for this plugin. It is displayed in the
+   * "Plugins" page of Orthanc Explorer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param description The description.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSetDescription(
+    OrthancPluginContext*  context,
+    const char*            description)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_Description;
+    params.value = description;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  /**
+   * @brief Extend the JavaScript code of Orthanc Explorer.
+   *
+   * Add JavaScript code to customize the default behavior of Orthanc
+   * Explorer. This can for instance be used to add new buttons.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param javascript The custom JavaScript code.
+   **/ 
+  ORTHANC_PLUGIN_INLINE void OrthancPluginExtendOrthancExplorer(
+    OrthancPluginContext*  context,
+    const char*            javascript)
+  {
+    _OrthancPluginSetPluginProperty params;
+    params.plugin = OrthancPluginGetName();
+    params.property = _OrthancPluginProperty_OrthancExplorer;
+    params.value = javascript;
+
+    context->InvokeService(context, _OrthancPluginService_SetPluginProperty, &params);
+  }
+
+
+  typedef struct
+  {
+    char**       result;
+    int32_t      property;
+    const char*  value;
+  } _OrthancPluginGlobalProperty;
+
+
+  /**
+   * @brief Get the value of a global property.
+   *
+   * Get the value of a global property that is stored in the Orthanc database. Global
+   * properties whose index is below 1024 are reserved by Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param property The global property of interest.
+   * @param defaultValue The value to return, if the global property is unset.
+   * @return The value of the global property, or NULL in the case of an error. This
+   * string must be freed by OrthancPluginFreeString().
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetGlobalProperty(
+    OrthancPluginContext*  context,
+    int32_t                property,
+    const char*            defaultValue)
+  {
+    char* result;
+
+    _OrthancPluginGlobalProperty params;
+    params.result = &result;
+    params.property = property;
+    params.value = defaultValue;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetGlobalProperty, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Set the value of a global property.
+   *
+   * Set the value of a global property into the Orthanc
+   * database. Setting a global property can be used by plugins to
+   * save their internal parameters. Plugins are only allowed to set
+   * properties whose index are above or equal to 1024 (properties
+   * below 1024 are read-only and reserved by Orthanc).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param property The global property of interest.
+   * @param value The value to be set in the global property.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSetGlobalProperty(
+    OrthancPluginContext*  context,
+    int32_t                property,
+    const char*            value)
+  {
+    _OrthancPluginGlobalProperty params;
+    params.result = NULL;
+    params.property = property;
+    params.value = value;
+
+    return context->InvokeService(context, _OrthancPluginService_SetGlobalProperty, &params);
+  }
+
+
+
+  typedef struct
+  {
+    int32_t   *resultInt32;
+    uint32_t  *resultUint32;
+    int64_t   *resultInt64;
+    uint64_t  *resultUint64;
+  } _OrthancPluginReturnSingleValue;
+
+  /**
+   * @brief Get the number of command-line arguments.
+   *
+   * Retrieve the number of command-line arguments that were used to launch Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The number of arguments.
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetCommandLineArgumentsCount(
+    OrthancPluginContext*  context)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginReturnSingleValue params;
+    memset(&params, 0, sizeof(params));
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgumentsCount, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+
+  /**
+   * @brief Get the value of a command-line argument.
+   *
+   * Get the value of one of the command-line arguments that were used
+   * to launch Orthanc. The number of available arguments can be
+   * retrieved by OrthancPluginGetCommandLineArgumentsCount().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param argument The index of the argument.
+   * @return The value of the argument, or NULL in the case of an error. This
+   * string must be freed by OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGetCommandLineArgument(
+    OrthancPluginContext*  context,
+    uint32_t               argument)
+  {
+    char* result;
+
+    _OrthancPluginGlobalProperty params;
+    params.result = &result;
+    params.property = (int32_t) argument;
+    params.value = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetCommandLineArgument, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the expected version of the database schema.
+   *
+   * Retrieve the expected version of the database schema.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The version.
+   * @ingroup Callbacks
+   * @deprecated Please instead use IDatabaseBackend::UpgradeDatabase()
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetExpectedDatabaseVersion(
+    OrthancPluginContext*  context)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginReturnSingleValue params;
+    memset(&params, 0, sizeof(params));
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetExpectedDatabaseVersion, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the content of the configuration file(s).
+   *
+   * This function returns the content of the configuration that is
+   * used by Orthanc, formatted as a JSON string.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the configuration. This string must be freed by
+   * OrthancPluginFreeString().
+   **/
+  ORTHANC_PLUGIN_INLINE char *OrthancPluginGetConfiguration(OrthancPluginContext* context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetConfiguration, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              subType;
+    const char*              contentType;
+  } _OrthancPluginStartMultipartAnswer;
+
+  /**
+   * @brief Start an HTTP multipart answer.
+   *
+   * Initiates a HTTP multipart answer, as the result of a REST request.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param subType The sub-type of the multipart answer ("mixed" or "related").
+   * @param contentType The MIME type of the items in the multipart answer.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginSendMultipartItem(), OrthancPluginSendMultipartItem2()
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginStartMultipartAnswer(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              subType,
+    const char*              contentType)
+  {
+    _OrthancPluginStartMultipartAnswer params;
+    params.output = output;
+    params.subType = subType;
+    params.contentType = contentType;
+    return context->InvokeService(context, _OrthancPluginService_StartMultipartAnswer, &params);
+  }
+
+
+  /**
+   * @brief Send an item as a part of some HTTP multipart answer.
+   *
+   * This function sends an item as a part of some HTTP multipart
+   * answer that was initiated by OrthancPluginStartMultipartAnswer().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param answer Pointer to the memory buffer containing the item.
+   * @param answerSize Number of bytes of the item.
+   * @return 0 if success, or the error code if failure (this notably happens
+   * if the connection is closed by the client).
+   * @see OrthancPluginSendMultipartItem2()
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              answer,
+    uint32_t                 answerSize)
+  {
+    _OrthancPluginAnswerBuffer params;
+    params.output = output;
+    params.answer = answer;
+    params.answerSize = answerSize;
+    params.mimeType = NULL;
+    return context->InvokeService(context, _OrthancPluginService_SendMultipartItem, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*    target;
+    const void*                   source;
+    uint32_t                      size;
+    OrthancPluginCompressionType  compression;
+    uint8_t                       uncompress;
+  } _OrthancPluginBufferCompression;
+
+
+  /**
+   * @brief Compress or decompress a buffer.
+   *
+   * This function compresses or decompresses a buffer, using the
+   * version of the zlib library that is used by the Orthanc core.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param source The source buffer.
+   * @param size The size in bytes of the source buffer.
+   * @param compression The compression algorithm.
+   * @param uncompress If set to "0", the buffer must be compressed. 
+   * If set to "1", the buffer must be uncompressed.
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginBufferCompression(
+    OrthancPluginContext*         context,
+    OrthancPluginMemoryBuffer*    target,
+    const void*                   source,
+    uint32_t                      size,
+    OrthancPluginCompressionType  compression,
+    uint8_t                       uncompress)
+  {
+    _OrthancPluginBufferCompression params;
+    params.target = target;
+    params.source = source;
+    params.size = size;
+    params.compression = compression;
+    params.uncompress = uncompress;
+
+    return context->InvokeService(context, _OrthancPluginService_BufferCompression, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 path;
+  } _OrthancPluginReadFile;
+
+  /**
+   * @brief Read a file.
+   * 
+   * Read the content of a file on the filesystem, and returns it into
+   * a newly allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param path The path of the file to be read.
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginReadFile(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 path)
+  {
+    _OrthancPluginReadFile params;
+    params.target = target;
+    params.path = path;
+    return context->InvokeService(context, _OrthancPluginService_ReadFile, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const char*  path;
+    const void*  data;
+    uint32_t     size;
+  } _OrthancPluginWriteFile;
+
+  /**
+   * @brief Write a file.
+   * 
+   * Write the content of a memory buffer to the filesystem.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param path The path of the file to be written.
+   * @param data The content of the memory buffer.
+   * @param size The size of the memory buffer.
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWriteFile(
+    OrthancPluginContext*  context,
+    const char*            path,
+    const void*            data,
+    uint32_t               size)
+  {
+    _OrthancPluginWriteFile params;
+    params.path = path;
+    params.data = data;
+    params.size = size;
+    return context->InvokeService(context, _OrthancPluginService_WriteFile, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const char**            target;
+    OrthancPluginErrorCode  error;
+  } _OrthancPluginGetErrorDescription;
+
+  /**
+   * @brief Get the description of a given error code.
+   *
+   * This function returns the description of a given error code.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param error The error code of interest.
+   * @return The error description. This is a statically-allocated
+   * string, do not free it.
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetErrorDescription(
+    OrthancPluginContext*    context,
+    OrthancPluginErrorCode   error)
+  {
+    const char* result = NULL;
+
+    _OrthancPluginGetErrorDescription params;
+    params.target = &result;
+    params.error = error;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetErrorDescription, &params) != OrthancPluginErrorCode_Success ||
+        result == NULL)
+    {
+      return "Unknown error code";
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    uint16_t                 status;
+    const char*              body;
+    uint32_t                 bodySize;
+  } _OrthancPluginSendHttpStatus;
+
+  /**
+   * @brief Send a HTTP status, with a custom body.
+   *
+   * This function answers to a HTTP request by sending a HTTP status
+   * code (such as "400 - Bad Request"), together with a body
+   * describing the error. The body will only be returned if the
+   * configuration option "HttpDescribeErrors" of Orthanc is set to "true".
+   * 
+   * Note that:
+   * - Successful requests (status 200) must use ::OrthancPluginAnswerBuffer().
+   * - Redirections (status 301) must use ::OrthancPluginRedirect().
+   * - Unauthorized access (status 401) must use ::OrthancPluginSendUnauthorized().
+   * - Methods not allowed (status 405) must use ::OrthancPluginSendMethodNotAllowed().
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param status The HTTP status code to be sent.
+   * @param body The body of the answer.
+   * @param bodySize The size of the body.
+   * @see OrthancPluginSendHttpStatusCode()
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginSendHttpStatus(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    uint16_t                 status,
+    const char*              body,
+    uint32_t                 bodySize)
+  {
+    _OrthancPluginSendHttpStatus params;
+    params.output = output;
+    params.status = status;
+    params.body = body;
+    params.bodySize = bodySize;
+    context->InvokeService(context, _OrthancPluginService_SendHttpStatus, &params);
+  }
+
+
+
+  typedef struct
+  {
+    const OrthancPluginImage*  image;
+    uint32_t*                  resultUint32;
+    OrthancPluginPixelFormat*  resultPixelFormat;
+    void**                     resultBuffer;
+  } _OrthancPluginGetImageInfo;
+
+
+  /**
+   * @brief Return the pixel format of an image.
+   *
+   * This function returns the type of memory layout for the pixels of the given image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The pixel format.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginPixelFormat  OrthancPluginGetImagePixelFormat(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    OrthancPluginPixelFormat target;
+    
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.resultPixelFormat = &target;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImagePixelFormat, &params) != OrthancPluginErrorCode_Success)
+    {
+      return OrthancPluginPixelFormat_Unknown;
+    }
+    else
+    {
+      return (OrthancPluginPixelFormat) target;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the width of an image.
+   *
+   * This function returns the width of the given image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The width.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t  OrthancPluginGetImageWidth(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    uint32_t width;
+    
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.resultUint32 = &width;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImageWidth, &params) != OrthancPluginErrorCode_Success)
+    {
+      return 0;
+    }
+    else
+    {
+      return width;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the height of an image.
+   *
+   * This function returns the height of the given image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The height.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t  OrthancPluginGetImageHeight(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    uint32_t height;
+    
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.resultUint32 = &height;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImageHeight, &params) != OrthancPluginErrorCode_Success)
+    {
+      return 0;
+    }
+    else
+    {
+      return height;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the pitch of an image.
+   *
+   * This function returns the pitch of the given image. The pitch is
+   * defined as the number of bytes between 2 successive lines of the
+   * image in the memory buffer.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The pitch.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t  OrthancPluginGetImagePitch(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    uint32_t pitch;
+    
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.resultUint32 = &pitch;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImagePitch, &params) != OrthancPluginErrorCode_Success)
+    {
+      return 0;
+    }
+    else
+    {
+      return pitch;
+    }
+  }
+
+
+
+  /**
+   * @brief Return a pointer to the content of an image.
+   *
+   * This function returns a pointer to the memory buffer that
+   * contains the pixels of the image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image of interest.
+   * @return The pointer.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE void*  OrthancPluginGetImageBuffer(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  image)
+  {
+    void* target = NULL;
+
+    _OrthancPluginGetImageInfo params;
+    memset(&params, 0, sizeof(params));
+    params.resultBuffer = &target;
+    params.image = image;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetImageBuffer, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginImage**       target;
+    const void*                data;
+    uint32_t                   size;
+    OrthancPluginImageFormat   format;
+  } _OrthancPluginUncompressImage;
+
+
+  /**
+   * @brief Decode a compressed image.
+   *
+   * This function decodes a compressed image from a memory buffer.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param data Pointer to a memory buffer containing the compressed image.
+   * @param size Size of the memory buffer containing the compressed image.
+   * @param format The file format of the compressed image.
+   * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginUncompressImage(
+    OrthancPluginContext*      context,
+    const void*                data,
+    uint32_t                   size,
+    OrthancPluginImageFormat   format)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginUncompressImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.data = data;
+    params.size = size;
+    params.format = format;
+
+    if (context->InvokeService(context, _OrthancPluginService_UncompressImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginImage*   image;
+  } _OrthancPluginFreeImage;
+
+  /**
+   * @brief Free an image.
+   *
+   * This function frees an image that was decoded with OrthancPluginUncompressImage().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE void  OrthancPluginFreeImage(
+    OrthancPluginContext* context, 
+    OrthancPluginImage*   image)
+  {
+    _OrthancPluginFreeImage params;
+    params.image = image;
+
+    context->InvokeService(context, _OrthancPluginService_FreeImage, &params);
+  }
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer* target;
+    OrthancPluginImageFormat   imageFormat;
+    OrthancPluginPixelFormat   pixelFormat;
+    uint32_t                   width;
+    uint32_t                   height;
+    uint32_t                   pitch;
+    const void*                buffer;
+    uint8_t                    quality;
+  } _OrthancPluginCompressImage;
+
+
+  /**
+   * @brief Encode a PNG image.
+   *
+   * This function compresses the given memory buffer containing an
+   * image using the PNG specification, and stores the result of the
+   * compression into a newly allocated memory buffer.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginCompressAndAnswerPngImage()
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressPngImage(
+    OrthancPluginContext*         context,
+    OrthancPluginMemoryBuffer*    target,
+    OrthancPluginPixelFormat      format,
+    uint32_t                      width,
+    uint32_t                      height,
+    uint32_t                      pitch,
+    const void*                   buffer)
+  {
+    _OrthancPluginCompressImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = target;
+    params.imageFormat = OrthancPluginImageFormat_Png;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = 0;  /* Unused for PNG */
+
+    return context->InvokeService(context, _OrthancPluginService_CompressImage, &params);
+  }
+
+
+  /**
+   * @brief Encode a JPEG image.
+   *
+   * This function compresses the given memory buffer containing an
+   * image using the JPEG specification, and stores the result of the
+   * compression into a newly allocated memory buffer.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @param quality The quality of the JPEG encoding, between 1 (worst
+   * quality, best compression) and 100 (best quality, worst
+   * compression).
+   * @return 0 if success, or the error code if failure.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCompressJpegImage(
+    OrthancPluginContext*         context,
+    OrthancPluginMemoryBuffer*    target,
+    OrthancPluginPixelFormat      format,
+    uint32_t                      width,
+    uint32_t                      height,
+    uint32_t                      pitch,
+    const void*                   buffer,
+    uint8_t                       quality)
+  {
+    _OrthancPluginCompressImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = target;
+    params.imageFormat = OrthancPluginImageFormat_Jpeg;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = quality;
+
+    return context->InvokeService(context, _OrthancPluginService_CompressImage, &params);
+  }
+
+
+
+  /**
+   * @brief Answer to a REST request with a JPEG image.
+   *
+   * This function answers to a REST request with a JPEG image. The
+   * parameters of this function describe a memory buffer that
+   * contains an uncompressed image. The image will be automatically compressed
+   * as a JPEG image by the core system of Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param format The memory layout of the uncompressed image.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer containing the uncompressed image.
+   * @param quality The quality of the JPEG encoding, between 1 (worst
+   * quality, best compression) and 100 (best quality, worst
+   * compression).
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerJpegImage(
+    OrthancPluginContext*     context,
+    OrthancPluginRestOutput*  output,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    const void*               buffer,
+    uint8_t                   quality)
+  {
+    _OrthancPluginCompressAndAnswerImage params;
+    params.output = output;
+    params.imageFormat = OrthancPluginImageFormat_Jpeg;
+    params.pixelFormat = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    params.quality = quality;
+    context->InvokeService(context, _OrthancPluginService_CompressAndAnswerImage, &params);
+  }
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    OrthancPluginHttpMethod     method;
+    const char*                 url;
+    const char*                 username;
+    const char*                 password;
+    const char*                 body;
+    uint32_t                    bodySize;
+  } _OrthancPluginCallHttpClient;
+
+
+  /**
+   * @brief Issue a HTTP GET call.
+   * 
+   * Make a HTTP GET call to the given URL. The result to the query is
+   * stored into a newly allocated memory buffer. Favor
+   * OrthancPluginRestApiGet() if calling the built-in REST API of the
+   * Orthanc instance that hosts this plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param url The URL of interest.
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpGet(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 url,
+    const char*                 username,
+    const char*                 password)
+  {
+    _OrthancPluginCallHttpClient params;
+    memset(&params, 0, sizeof(params));
+
+    params.target = target;
+    params.method = OrthancPluginHttpMethod_Get;
+    params.url = url;
+    params.username = username;
+    params.password = password;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
+  }
+
+
+  /**
+   * @brief Issue a HTTP POST call.
+   * 
+   * Make a HTTP POST call to the given URL. The result to the query
+   * is stored into a newly allocated memory buffer. Favor
+   * OrthancPluginRestApiPost() if calling the built-in REST API of
+   * the Orthanc instance that hosts this plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param url The URL of interest.
+   * @param body The content of the body of the request.
+   * @param bodySize The size of the body of the request.
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpPost(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 url,
+    const char*                 body,
+    uint32_t                    bodySize,
+    const char*                 username,
+    const char*                 password)
+  {
+    _OrthancPluginCallHttpClient params;
+    memset(&params, 0, sizeof(params));
+
+    params.target = target;
+    params.method = OrthancPluginHttpMethod_Post;
+    params.url = url;
+    params.body = body;
+    params.bodySize = bodySize;
+    params.username = username;
+    params.password = password;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
+  }
+
+
+  /**
+   * @brief Issue a HTTP PUT call.
+   * 
+   * Make a HTTP PUT call to the given URL. The result to the query is
+   * stored into a newly allocated memory buffer. Favor
+   * OrthancPluginRestApiPut() if calling the built-in REST API of the
+   * Orthanc instance that hosts this plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param url The URL of interest.
+   * @param body The content of the body of the request.
+   * @param bodySize The size of the body of the request.
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpPut(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 url,
+    const char*                 body,
+    uint32_t                    bodySize,
+    const char*                 username,
+    const char*                 password)
+  {
+    _OrthancPluginCallHttpClient params;
+    memset(&params, 0, sizeof(params));
+
+    params.target = target;
+    params.method = OrthancPluginHttpMethod_Put;
+    params.url = url;
+    params.body = body;
+    params.bodySize = bodySize;
+    params.username = username;
+    params.password = password;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
+  }
+
+
+  /**
+   * @brief Issue a HTTP DELETE call.
+   * 
+   * Make a HTTP DELETE call to the given URL. Favor
+   * OrthancPluginRestApiDelete() if calling the built-in REST API of
+   * the Orthanc instance that hosts this plugin.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param url The URL of interest.
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpDelete(
+    OrthancPluginContext*       context,
+    const char*                 url,
+    const char*                 username,
+    const char*                 password)
+  {
+    _OrthancPluginCallHttpClient params;
+    memset(&params, 0, sizeof(params));
+
+    params.method = OrthancPluginHttpMethod_Delete;
+    params.url = url;
+    params.username = username;
+    params.password = password;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginImage**       target;
+    const OrthancPluginImage*  source;
+    OrthancPluginPixelFormat   targetFormat;
+  } _OrthancPluginConvertPixelFormat;
+
+
+  /**
+   * @brief Change the pixel format of an image.
+   *
+   * This function creates a new image, changing the memory layout of the pixels.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param source The source image.
+   * @param targetFormat The target pixel format.
+   * @return The resulting image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage *OrthancPluginConvertPixelFormat(
+    OrthancPluginContext*      context,
+    const OrthancPluginImage*  source,
+    OrthancPluginPixelFormat   targetFormat)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginConvertPixelFormat params;
+    params.target = &target;
+    params.source = source;
+    params.targetFormat = targetFormat;
+
+    if (context->InvokeService(context, _OrthancPluginService_ConvertPixelFormat, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  /**
+   * @brief Return the number of available fonts.
+   *
+   * This function returns the number of fonts that are built in the
+   * Orthanc core. These fonts can be used to draw texts on images
+   * through OrthancPluginDrawText().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return The number of fonts.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontsCount(
+    OrthancPluginContext*  context)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginReturnSingleValue params;
+    memset(&params, 0, sizeof(params));
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFontsCount, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+
+
+  typedef struct
+  {
+    uint32_t      fontIndex; /* in */
+    const char**  name; /* out */
+    uint32_t*     size; /* out */
+  } _OrthancPluginGetFontInfo;
+
+  /**
+   * @brief Return the name of a font.
+   *
+   * This function returns the name of a font that is built in the Orthanc core.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount().
+   * @return The font name. This is a statically-allocated string, do not free it.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE const char* OrthancPluginGetFontName(
+    OrthancPluginContext*  context,
+    uint32_t               fontIndex)
+  {
+    const char* result = NULL;
+
+    _OrthancPluginGetFontInfo params;
+    memset(&params, 0, sizeof(params));
+    params.name = &result;
+    params.fontIndex = fontIndex;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Return the size of a font.
+   *
+   * This function returns the size of a font that is built in the Orthanc core.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount().
+   * @return The font size.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t OrthancPluginGetFontSize(
+    OrthancPluginContext*  context,
+    uint32_t               fontIndex)
+  {
+    uint32_t result;
+
+    _OrthancPluginGetFontInfo params;
+    memset(&params, 0, sizeof(params));
+    params.size = &result;
+    params.fontIndex = fontIndex;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFontInfo, &params) != OrthancPluginErrorCode_Success)
+    {
+      return 0;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginImage*   image;
+    uint32_t              fontIndex;
+    const char*           utf8Text;
+    int32_t               x;
+    int32_t               y;
+    uint8_t               r;
+    uint8_t               g;
+    uint8_t               b;
+  } _OrthancPluginDrawText;
+
+
+  /**
+   * @brief Draw text on an image.
+   *
+   * This function draws some text on some image.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param image The image upon which to draw the text.
+   * @param fontIndex The index of the font. This value must be less than OrthancPluginGetFontsCount().
+   * @param utf8Text The text to be drawn, encoded as an UTF-8 zero-terminated string.
+   * @param x The X position of the text over the image.
+   * @param y The Y position of the text over the image.
+   * @param r The value of the red color channel of the text.
+   * @param g The value of the green color channel of the text.
+   * @param b The value of the blue color channel of the text.
+   * @return 0 if success, other value if error.
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginDrawText(
+    OrthancPluginContext*  context,
+    OrthancPluginImage*    image,
+    uint32_t               fontIndex,
+    const char*            utf8Text,
+    int32_t                x,
+    int32_t                y,
+    uint8_t                r,
+    uint8_t                g,
+    uint8_t                b)
+  {
+    _OrthancPluginDrawText params;
+    memset(&params, 0, sizeof(params));
+    params.image = image;
+    params.fontIndex = fontIndex;
+    params.utf8Text = utf8Text;
+    params.x = x;
+    params.y = y;
+    params.r = r;
+    params.g = g;
+    params.b = b;
+
+    return context->InvokeService(context, _OrthancPluginService_DrawText, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginStorageArea*   storageArea;
+    const char*                 uuid;
+    const void*                 content;
+    uint64_t                    size;
+    OrthancPluginContentType    type;
+  } _OrthancPluginStorageAreaCreate;
+
+
+  /**
+   * @brief Create a file inside the storage area.
+   *
+   * This function creates a new file inside the storage area that is
+   * currently used by Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param storageArea The storage area.
+   * @param uuid The identifier of the file to be created.
+   * @param content The content to store in the newly created file.
+   * @param size The size of the content.
+   * @param type The type of the file content.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginStorageAreaCreate(
+    OrthancPluginContext*       context,
+    OrthancPluginStorageArea*   storageArea,
+    const char*                 uuid,
+    const void*                 content,
+    uint64_t                    size,
+    OrthancPluginContentType    type)
+  {
+    _OrthancPluginStorageAreaCreate params;
+    params.storageArea = storageArea;
+    params.uuid = uuid;
+    params.content = content;
+    params.size = size;
+    params.type = type;
+
+    return context->InvokeService(context, _OrthancPluginService_StorageAreaCreate, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    OrthancPluginStorageArea*   storageArea;
+    const char*                 uuid;
+    OrthancPluginContentType    type;
+  } _OrthancPluginStorageAreaRead;
+
+
+  /**
+   * @brief Read a file from the storage area.
+   *
+   * This function reads the content of a given file from the storage
+   * area that is currently used by Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param storageArea The storage area.
+   * @param uuid The identifier of the file to be read.
+   * @param type The type of the file content.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginStorageAreaRead(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    OrthancPluginStorageArea*   storageArea,
+    const char*                 uuid,
+    OrthancPluginContentType    type)
+  {
+    _OrthancPluginStorageAreaRead params;
+    params.target = target;
+    params.storageArea = storageArea;
+    params.uuid = uuid;
+    params.type = type;
+
+    return context->InvokeService(context, _OrthancPluginService_StorageAreaRead, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginStorageArea*   storageArea;
+    const char*                 uuid;
+    OrthancPluginContentType    type;
+  } _OrthancPluginStorageAreaRemove;
+
+  /**
+   * @brief Remove a file from the storage area.
+   *
+   * This function removes a given file from the storage area that is
+   * currently used by Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param storageArea The storage area.
+   * @param uuid The identifier of the file to be removed.
+   * @param type The type of the file content.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginStorageAreaRemove(
+    OrthancPluginContext*       context,
+    OrthancPluginStorageArea*   storageArea,
+    const char*                 uuid,
+    OrthancPluginContentType    type)
+  {
+    _OrthancPluginStorageAreaRemove params;
+    params.storageArea = storageArea;
+    params.uuid = uuid;
+    params.type = type;
+
+    return context->InvokeService(context, _OrthancPluginService_StorageAreaRemove, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginErrorCode*  target;
+    int32_t                  code;
+    uint16_t                 httpStatus;
+    const char*              message;
+  } _OrthancPluginRegisterErrorCode;
+  
+  /**
+   * @brief Declare a custom error code for this plugin.
+   *
+   * This function declares a custom error code that can be generated
+   * by this plugin. This declaration is used to enrich the body of
+   * the HTTP answer in the case of an error, and to set the proper
+   * HTTP status code.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param code The error code that is internal to this plugin.
+   * @param httpStatus The HTTP status corresponding to this error.
+   * @param message The description of the error.
+   * @return The error code that has been assigned inside the Orthanc core.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRegisterErrorCode(
+    OrthancPluginContext*    context,
+    int32_t                  code,
+    uint16_t                 httpStatus,
+    const char*              message)
+  {
+    OrthancPluginErrorCode target;
+
+    _OrthancPluginRegisterErrorCode params;
+    params.target = &target;
+    params.code = code;
+    params.httpStatus = httpStatus;
+    params.message = message;
+
+    if (context->InvokeService(context, _OrthancPluginService_RegisterErrorCode, &params) == OrthancPluginErrorCode_Success)
+    {
+      return target;
+    }
+    else
+    {
+      /* There was an error while assigned the error. Use a generic code. */
+      return OrthancPluginErrorCode_Plugin;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    uint16_t                          group;
+    uint16_t                          element;
+    OrthancPluginValueRepresentation  vr;
+    const char*                       name;
+    uint32_t                          minMultiplicity;
+    uint32_t                          maxMultiplicity;
+  } _OrthancPluginRegisterDictionaryTag;
+  
+  /**
+   * @brief Register a new tag into the DICOM dictionary.
+   *
+   * This function declares a new tag in the dictionary of DICOM tags
+   * that are known to Orthanc. This function should be used in the
+   * OrthancPluginInitialize() callback.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param group The group of the tag.
+   * @param element The element of the tag.
+   * @param vr The value representation of the tag.
+   * @param name The nickname of the tag.
+   * @param minMultiplicity The minimum multiplicity of the tag (must be above 0).
+   * @param maxMultiplicity The maximum multiplicity of the tag. A value of 0 means
+   * an arbitrary multiplicity ("<tt>n</tt>").
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRegisterDictionaryTag(
+    OrthancPluginContext*             context,
+    uint16_t                          group,
+    uint16_t                          element,
+    OrthancPluginValueRepresentation  vr,
+    const char*                       name,
+    uint32_t                          minMultiplicity,
+    uint32_t                          maxMultiplicity)
+  {
+    _OrthancPluginRegisterDictionaryTag params;
+    params.group = group;
+    params.element = element;
+    params.vr = vr;
+    params.name = name;
+    params.minMultiplicity = minMultiplicity;
+    params.maxMultiplicity = maxMultiplicity;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterDictionaryTag, &params);
+  }
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginStorageArea*  storageArea;
+    OrthancPluginResourceType  level;
+  } _OrthancPluginReconstructMainDicomTags;
+
+  /**
+   * @brief Reconstruct the main DICOM tags.
+   *
+   * This function requests the Orthanc core to reconstruct the main
+   * DICOM tags of all the resources of the given type. This function
+   * can only be used as a part of the upgrade of a custom database
+   * back-end
+   * (cf. OrthancPlugins::IDatabaseBackend::UpgradeDatabase). A
+   * database transaction will be automatically setup.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param storageArea The storage area.
+   * @param level The type of the resources of interest.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginReconstructMainDicomTags(
+    OrthancPluginContext*      context,
+    OrthancPluginStorageArea*  storageArea,
+    OrthancPluginResourceType  level)
+  {
+    _OrthancPluginReconstructMainDicomTags params;
+    params.level = level;
+    params.storageArea = storageArea;
+
+    return context->InvokeService(context, _OrthancPluginService_ReconstructMainDicomTags, &params);
+  }
+
+
+  typedef struct
+  {
+    char**                          result;
+    const char*                     instanceId;
+    const void*                     buffer;
+    uint32_t                        size;
+    OrthancPluginDicomToJsonFormat  format;
+    OrthancPluginDicomToJsonFlags   flags;
+    uint32_t                        maxStringLength;
+  } _OrthancPluginDicomToJson;
+
+
+  /**
+   * @brief Format a DICOM memory buffer as a JSON string.
+   *
+   * This function takes as input a memory buffer containing a DICOM
+   * file, and outputs a JSON string representing the tags of this
+   * DICOM file.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The memory buffer containing the DICOM file.
+   * @param size The size of the memory buffer.
+   * @param format The output format.
+   * @param flags Flags governing the output.
+   * @param maxStringLength The maximum length of a field. Too long fields will
+   * be output as "null". The 0 value means no maximum length.
+   * @return The NULL value if the case of an error, or the JSON
+   * string. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomInstanceToJson
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomBufferToJson(
+    OrthancPluginContext*           context,
+    const void*                     buffer,
+    uint32_t                        size,
+    OrthancPluginDicomToJsonFormat  format,
+    OrthancPluginDicomToJsonFlags   flags, 
+    uint32_t                        maxStringLength)
+  {
+    char* result;
+
+    _OrthancPluginDicomToJson params;
+    memset(&params, 0, sizeof(params));
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+    params.format = format;
+    params.flags = flags;
+    params.maxStringLength = maxStringLength;
+
+    if (context->InvokeService(context, _OrthancPluginService_DicomBufferToJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Format a DICOM instance as a JSON string.
+   *
+   * This function formats a DICOM instance that is stored in Orthanc,
+   * and outputs a JSON string representing the tags of this DICOM
+   * instance.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instanceId The Orthanc identifier of the instance.
+   * @param format The output format.
+   * @param flags Flags governing the output.
+   * @param maxStringLength The maximum length of a field. Too long fields will
+   * be output as "null". The 0 value means no maximum length.
+   * @return The NULL value if the case of an error, or the JSON
+   * string. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomInstanceToJson
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginDicomInstanceToJson(
+    OrthancPluginContext*           context,
+    const char*                     instanceId,
+    OrthancPluginDicomToJsonFormat  format,
+    OrthancPluginDicomToJsonFlags   flags, 
+    uint32_t                        maxStringLength)
+  {
+    char* result;
+
+    _OrthancPluginDicomToJson params;
+    memset(&params, 0, sizeof(params));
+    params.result = &result;
+    params.instanceId = instanceId;
+    params.format = format;
+    params.flags = flags;
+    params.maxStringLength = maxStringLength;
+
+    if (context->InvokeService(context, _OrthancPluginService_DicomInstanceToJson, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  target;
+    const char*                 uri;
+    uint32_t                    headersCount;
+    const char* const*          headersKeys;
+    const char* const*          headersValues;
+    int32_t                     afterPlugins;
+  } _OrthancPluginRestApiGet2;
+
+  /**
+   * @brief Make a GET call to the Orthanc REST API, with custom HTTP headers.
+   * 
+   * Make a GET call to the Orthanc REST API with extended
+   * parameters. The result to the query is stored into a newly
+   * allocated memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param uri The URI in the built-in Orthanc API.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys Array containing the keys of the HTTP headers (can be <tt>NULL</tt> if no header).
+   * @param headersValues Array containing the values of the HTTP headers (can be <tt>NULL</tt> if no header).
+   * @param afterPlugins If 0, the built-in API of Orthanc is used.
+   * If 1, the API is tainted by the plugins.
+   * @return 0 if success, or the error code if failure.
+   * @see OrthancPluginRestApiGet, OrthancPluginRestApiGetAfterPlugins
+   * @ingroup Orthanc
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginRestApiGet2(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri,
+    uint32_t                    headersCount,
+    const char* const*          headersKeys,
+    const char* const*          headersValues,
+    int32_t                     afterPlugins)
+  {
+    _OrthancPluginRestApiGet2 params;
+    params.target = target;
+    params.uri = uri;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;
+    params.afterPlugins = afterPlugins;
+
+    return context->InvokeService(context, _OrthancPluginService_RestApiGet2, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginWorklistCallback callback;
+  } _OrthancPluginWorklistCallback;
+
+  /**
+   * @brief Register a callback to handle modality worklists requests.
+   *
+   * This function registers a callback to handle C-Find SCP requests
+   * on modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback(
+    OrthancPluginContext*          context,
+    OrthancPluginWorklistCallback  callback)
+  {
+    _OrthancPluginWorklistCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterWorklistCallback, &params);
+  }
+
+
+  
+  typedef struct
+  {
+    OrthancPluginWorklistAnswers*      answers;
+    const OrthancPluginWorklistQuery*  query;
+    const void*                        dicom;
+    uint32_t                           size;
+  } _OrthancPluginWorklistAnswersOperation;
+
+  /**
+   * @brief Add one answer to some modality worklist request.
+   *
+   * This function adds one worklist (encoded as a DICOM file) to the
+   * set of answers corresponding to some C-Find SCP request against
+   * modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @param query The worklist query, as received by the callback.
+   * @param dicom The worklist to answer, encoded as a DICOM file.
+   * @param size The size of the DICOM file.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   * @see OrthancPluginCreateDicom()
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistAddAnswer(
+    OrthancPluginContext*             context,
+    OrthancPluginWorklistAnswers*     answers,
+    const OrthancPluginWorklistQuery* query,
+    const void*                       dicom,
+    uint32_t                          size)
+  {
+    _OrthancPluginWorklistAnswersOperation params;
+    params.answers = answers;
+    params.query = query;
+    params.dicom = dicom;
+    params.size = size;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistAddAnswer, &params);
+  }
+
+
+  /**
+   * @brief Mark the set of worklist answers as incomplete.
+   *
+   * This function marks as incomplete the set of answers
+   * corresponding to some C-Find SCP request against modality
+   * worklists. This must be used if canceling the handling of a
+   * request when too many answers are to be returned.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistMarkIncomplete(
+    OrthancPluginContext*          context,
+    OrthancPluginWorklistAnswers*  answers)
+  {
+    _OrthancPluginWorklistAnswersOperation params;
+    params.answers = answers;
+    params.query = NULL;
+    params.dicom = NULL;
+    params.size = 0;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistMarkIncomplete, &params);
+  }
+
+
+  typedef struct
+  {
+    const OrthancPluginWorklistQuery*  query;
+    const void*                        dicom;
+    uint32_t                           size;
+    int32_t*                           isMatch;
+    OrthancPluginMemoryBuffer*         target;
+  } _OrthancPluginWorklistQueryOperation;
+
+  /**
+   * @brief Test whether a worklist matches the query.
+   *
+   * This function checks whether one worklist (encoded as a DICOM
+   * file) matches the C-Find SCP query against modality
+   * worklists. This function must be called before adding the
+   * worklist as an answer through OrthancPluginWorklistAddAnswer().
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The worklist query, as received by the callback.
+   * @param dicom The worklist to answer, encoded as a DICOM file.
+   * @param size The size of the DICOM file.
+   * @return 1 if the worklist matches the query, 0 otherwise.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE int32_t  OrthancPluginWorklistIsMatch(
+    OrthancPluginContext*              context,
+    const OrthancPluginWorklistQuery*  query,
+    const void*                        dicom,
+    uint32_t                           size)
+  {
+    int32_t isMatch = 0;
+
+    _OrthancPluginWorklistQueryOperation params;
+    params.query = query;
+    params.dicom = dicom;
+    params.size = size;
+    params.isMatch = &isMatch;
+    params.target = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_WorklistIsMatch, &params) == OrthancPluginErrorCode_Success)
+    {
+      return isMatch;
+    }
+    else
+    {
+      /* Error: Assume non-match */
+      return 0;
+    }
+  }
+
+
+  /**
+   * @brief Retrieve the worklist query as a DICOM file.
+   *
+   * This function retrieves the DICOM file that underlies a C-Find
+   * SCP query against modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param query The worklist query, as received by the callback.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistGetDicomQuery(
+    OrthancPluginContext*              context,
+    OrthancPluginMemoryBuffer*         target,
+    const OrthancPluginWorklistQuery*  query)
+  {
+    _OrthancPluginWorklistQueryOperation params;
+    params.query = query;
+    params.dicom = NULL;
+    params.size = 0;
+    params.isMatch = NULL;
+    params.target = target;
+
+    return context->InvokeService(context, _OrthancPluginService_WorklistGetDicomQuery, &params);
+  }
+
+
+  /**
+   * @brief Get the origin of a DICOM file.
+   *
+   * This function returns the origin of a DICOM instance that has been received by Orthanc.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param instance The instance of interest.
+   * @return The origin of the instance.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginInstanceOrigin OrthancPluginGetInstanceOrigin(
+    OrthancPluginContext*       context,
+    OrthancPluginDicomInstance* instance)
+  {
+    OrthancPluginInstanceOrigin origin;
+
+    _OrthancPluginAccessDicomInstance params;
+    memset(&params, 0, sizeof(params));
+    params.resultOrigin = &origin;
+    params.instance = instance;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetInstanceOrigin, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return OrthancPluginInstanceOrigin_Unknown;
+    }
+    else
+    {
+      return origin;
+    }
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*     target;
+    const char*                    json;
+    const OrthancPluginImage*      pixelData;
+    OrthancPluginCreateDicomFlags  flags;
+  } _OrthancPluginCreateDicom;
+
+  /**
+   * @brief Create a DICOM instance from a JSON string and an image.
+   *
+   * This function takes as input a string containing a JSON file
+   * describing the content of a DICOM instance. As an output, it
+   * writes the corresponding DICOM instance to a newly allocated
+   * memory buffer. Additionally, an image to be encoded within the
+   * DICOM instance can also be provided.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target The target memory buffer. It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param json The input JSON file.
+   * @param pixelData The image. Can be NULL, if the pixel data is encoded inside the JSON with the data URI scheme.
+   * @param flags Flags governing the output.
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   * @see OrthancPluginDicomBufferToJson
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginCreateDicom(
+    OrthancPluginContext*          context,
+    OrthancPluginMemoryBuffer*     target,
+    const char*                    json,
+    const OrthancPluginImage*      pixelData,
+    OrthancPluginCreateDicomFlags  flags)
+  {
+    _OrthancPluginCreateDicom params;
+    params.target = target;
+    params.json = json;
+    params.pixelData = pixelData;
+    params.flags = flags;
+
+    return context->InvokeService(context, _OrthancPluginService_CreateDicom, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginDecodeImageCallback callback;
+  } _OrthancPluginDecodeImageCallback;
+
+  /**
+   * @brief Register a callback to handle the decoding of DICOM images.
+   *
+   * This function registers a custom callback to the decoding of
+   * DICOM images, replacing the built-in decoder of Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterDecodeImageCallback(
+    OrthancPluginContext*             context,
+    OrthancPluginDecodeImageCallback  callback)
+  {
+    _OrthancPluginDecodeImageCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterDecodeImageCallback, &params);
+  }
+  
+
+
+  typedef struct
+  {
+    OrthancPluginImage**       target;
+    OrthancPluginPixelFormat   format;
+    uint32_t                   width;
+    uint32_t                   height;
+    uint32_t                   pitch;
+    void*                      buffer;
+    const void*                constBuffer;
+    uint32_t                   bufferSize;
+    uint32_t                   frameIndex;
+  } _OrthancPluginCreateImage;
+
+
+  /**
+   * @brief Create an image.
+   *
+   * This function creates an image of given size and format.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param format The format of the pixels.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImage(
+    OrthancPluginContext*     context,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.format = format;
+    params.width = width;
+    params.height = height;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+  /**
+   * @brief Create an image pointing to a memory buffer.
+   *
+   * This function creates an image whose content points to a memory
+   * buffer managed by the plugin. Note that the buffer is directly
+   * accessed, no memory is allocated and no data is copied.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param format The format of the pixels.
+   * @param width The width of the image.
+   * @param height The height of the image.
+   * @param pitch The pitch of the image (i.e. the number of bytes
+   * between 2 successive lines of the image in the memory buffer).
+   * @param buffer The memory buffer.
+   * @return The newly allocated image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginCreateImageAccessor(
+    OrthancPluginContext*     context,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    void*                     buffer)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.format = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+
+    if (context->InvokeService(context, _OrthancPluginService_CreateImageAccessor, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  /**
+   * @brief Decode one frame from a DICOM instance.
+   *
+   * This function decodes one frame of a DICOM image that is stored
+   * in a memory buffer. This function will give the same result as
+   * OrthancPluginUncompressImage() for single-frame DICOM images.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer Pointer to a memory buffer containing the DICOM image.
+   * @param bufferSize Size of the memory buffer containing the DICOM image.
+   * @param frameIndex The index of the frame of interest in a multi-frame image.
+   * @return The uncompressed image. It must be freed with OrthancPluginFreeImage().
+   * @ingroup Images
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginImage* OrthancPluginDecodeDicomImage(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               bufferSize,
+    uint32_t               frameIndex)
+  {
+    OrthancPluginImage* target = NULL;
+
+    _OrthancPluginCreateImage params;
+    memset(&params, 0, sizeof(params));
+    params.target = &target;
+    params.constBuffer = buffer;
+    params.bufferSize = bufferSize;
+    params.frameIndex = frameIndex;
+
+    if (context->InvokeService(context, _OrthancPluginService_DecodeDicomImage, &params) != OrthancPluginErrorCode_Success)
+    {
+      return NULL;
+    }
+    else
+    {
+      return target;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    char**       result;
+    const void*  buffer;
+    uint32_t     size;
+  } _OrthancPluginComputeHash;
+
+  /**
+   * @brief Compute an MD5 hash.
+   *
+   * This functions computes the MD5 cryptographic hash of the given memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The source memory buffer.
+   * @param size The size in bytes of the source buffer.
+   * @return The NULL value in case of error, or a string containing the cryptographic hash.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeMd5(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size)
+  {
+    char* result;
+
+    _OrthancPluginComputeHash params;
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_ComputeMd5, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Compute a SHA-1 hash.
+   *
+   * This functions computes the SHA-1 cryptographic hash of the given memory buffer.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param buffer The source memory buffer.
+   * @param size The size in bytes of the source buffer.
+   * @return The NULL value in case of error, or a string containing the cryptographic hash.
+   * This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginComputeSha1(
+    OrthancPluginContext*  context,
+    const void*            buffer,
+    uint32_t               size)
+  {
+    char* result;
+
+    _OrthancPluginComputeHash params;
+    params.result = &result;
+    params.buffer = buffer;
+    params.size = size;
+
+    if (context->InvokeService(context, _OrthancPluginService_ComputeSha1, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginDictionaryEntry* target;
+    const char*                   name;
+  } _OrthancPluginLookupDictionary;
+
+  /**
+   * @brief Get information about the given DICOM tag.
+   *
+   * This functions makes a lookup in the dictionary of DICOM tags
+   * that are known to Orthanc, and returns information about this
+   * tag. The tag can be specified using its human-readable name
+   * (e.g. "PatientName") or a set of two hexadecimal numbers
+   * (e.g. "0010-0020").
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param target Where to store the information about the tag.
+   * @param name The name of the DICOM tag.
+   * @return 0 if success, other value if error.
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginLookupDictionary(
+    OrthancPluginContext*          context,
+    OrthancPluginDictionaryEntry*  target,
+    const char*                    name)
+  {
+    _OrthancPluginLookupDictionary params;
+    params.target = target;
+    params.name = name;
+    return context->InvokeService(context, _OrthancPluginService_LookupDictionary, &params);
+  }
+
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              answer;
+    uint32_t                 answerSize;
+    uint32_t                 headersCount;
+    const char* const*       headersKeys;
+    const char* const*       headersValues;
+  } _OrthancPluginSendMultipartItem2;
+
+  /**
+   * @brief Send an item as a part of some HTTP multipart answer, with custom headers.
+   *
+   * This function sends an item as a part of some HTTP multipart
+   * answer that was initiated by OrthancPluginStartMultipartAnswer(). In addition to
+   * OrthancPluginSendMultipartItem(), this function will set HTTP header associated
+   * with the item.
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param output The HTTP connection to the client application.
+   * @param answer Pointer to the memory buffer containing the item.
+   * @param answerSize Number of bytes of the item.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys Array containing the keys of the HTTP headers.
+   * @param headersValues Array containing the values of the HTTP headers.
+   * @return 0 if success, or the error code if failure (this notably happens
+   * if the connection is closed by the client).
+   * @see OrthancPluginSendMultipartItem()
+   * @ingroup REST
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginSendMultipartItem2(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              answer,
+    uint32_t                 answerSize,
+    uint32_t                 headersCount,
+    const char* const*       headersKeys,
+    const char* const*       headersValues)
+  {
+    _OrthancPluginSendMultipartItem2 params;
+    params.output = output;
+    params.answer = answer;
+    params.answerSize = answerSize;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;    
+
+    return context->InvokeService(context, _OrthancPluginService_SendMultipartItem2, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginIncomingHttpRequestFilter callback;
+  } _OrthancPluginIncomingHttpRequestFilter;
+
+  /**
+   * @brief Register a callback to filter incoming HTTP requests.
+   *
+   * This function registers a custom callback to filter incoming HTTP/REST
+   * requests received by the HTTP server of Orthanc.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup Callbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterIncomingHttpRequestFilter(
+    OrthancPluginContext*                   context,
+    OrthancPluginIncomingHttpRequestFilter  callback)
+  {
+    _OrthancPluginIncomingHttpRequestFilter params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterIncomingHttpRequestFilter, &params);
+  }
+  
+
+
+  typedef struct
+  {
+    OrthancPluginMemoryBuffer*  answerBody;
+    OrthancPluginMemoryBuffer*  answerHeaders;
+    uint16_t*                   httpStatus;
+    OrthancPluginHttpMethod     method;
+    const char*                 url;
+    uint32_t                    headersCount;
+    const char* const*          headersKeys;
+    const char* const*          headersValues;
+    const char*                 body;
+    uint32_t                    bodySize;
+    const char*                 username;
+    const char*                 password;
+    uint32_t                    timeout;
+    const char*                 certificateFile;
+    const char*                 certificateKeyFile;
+    const char*                 certificateKeyPassword;
+    uint8_t                     pkcs11;
+  } _OrthancPluginCallHttpClient2;
+
+
+
+  /**
+   * @brief Issue a HTTP call with full flexibility.
+   * 
+   * Make a HTTP call to the given URL. The result to the query is
+   * stored into a newly allocated memory buffer. The HTTP request
+   * will be done accordingly to the global configuration of Orthanc
+   * (in particular, the options "HttpProxy", "HttpTimeout",
+   * "HttpsVerifyPeers", "HttpsCACertificates", and "Pkcs11" will be
+   * taken into account).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answerBody The target memory buffer (out argument).
+   *        It must be freed with OrthancPluginFreeMemoryBuffer().
+   * @param answerHeaders The target memory buffer for the HTTP headers in the answers (out argument). 
+   *        The answer headers are formatted as a JSON object (associative array).
+   *        The buffer must be freed with OrthancPluginFreeMemoryBuffer().
+   *        This argument can be set to NULL if the plugin has no interest in the HTTP headers.
+   * @param httpStatus The HTTP status after the execution of the request (out argument).
+   * @param method HTTP method to be used.
+   * @param url The URL of interest.
+   * @param headersCount The number of HTTP headers.
+   * @param headersKeys Array containing the keys of the HTTP headers (can be <tt>NULL</tt> if no header).
+   * @param headersValues Array containing the values of the HTTP headers (can be <tt>NULL</tt> if no header).
+   * @param username The username (can be <tt>NULL</tt> if no password protection).
+   * @param password The password (can be <tt>NULL</tt> if no password protection).
+   * @param body The body of the POST request.
+   * @param bodySize The size of the body.
+   * @param timeout Timeout in seconds (0 for default timeout).
+   * @param certificateFile Path to the client certificate for HTTPS, in PEM format
+   * (can be <tt>NULL</tt> if no client certificate or if not using HTTPS).
+   * @param certificateKeyFile Path to the key of the client certificate for HTTPS, in PEM format
+   * (can be <tt>NULL</tt> if no client certificate or if not using HTTPS).
+   * @param certificateKeyPassword Password to unlock the key of the client certificate 
+   * (can be <tt>NULL</tt> if no client certificate or if not using HTTPS).
+   * @param pkcs11 Enable PKCS#11 client authentication for hardware security modules and smart cards.
+   * @return 0 if success, or the error code if failure.
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginHttpClient(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  answerBody,
+    OrthancPluginMemoryBuffer*  answerHeaders,
+    uint16_t*                   httpStatus,
+    OrthancPluginHttpMethod     method,
+    const char*                 url,
+    uint32_t                    headersCount,
+    const char* const*          headersKeys,
+    const char* const*          headersValues,
+    const char*                 body,
+    uint32_t                    bodySize,
+    const char*                 username,
+    const char*                 password,
+    uint32_t                    timeout,
+    const char*                 certificateFile,
+    const char*                 certificateKeyFile,
+    const char*                 certificateKeyPassword,
+    uint8_t                     pkcs11)
+  {
+    _OrthancPluginCallHttpClient2 params;
+    memset(&params, 0, sizeof(params));
+
+    params.answerBody = answerBody;
+    params.answerHeaders = answerHeaders;
+    params.httpStatus = httpStatus;
+    params.method = method;
+    params.url = url;
+    params.headersCount = headersCount;
+    params.headersKeys = headersKeys;
+    params.headersValues = headersValues;
+    params.body = body;
+    params.bodySize = bodySize;
+    params.username = username;
+    params.password = password;
+    params.timeout = timeout;
+    params.certificateFile = certificateFile;
+    params.certificateKeyFile = certificateKeyFile;
+    params.certificateKeyPassword = certificateKeyPassword;
+    params.pkcs11 = pkcs11;
+
+    return context->InvokeService(context, _OrthancPluginService_CallHttpClient2, &params);
+  }
+
+
+  /**
+   * @brief Generate an UUID.
+   *
+   * Generate a random GUID/UUID (globally unique identifier).
+   * 
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @return NULL in the case of an error, or a newly allocated string
+   * containing the UUID. This string must be freed by OrthancPluginFreeString().
+   * @ingroup Toolbox
+   **/
+  ORTHANC_PLUGIN_INLINE char* OrthancPluginGenerateUuid(
+    OrthancPluginContext*  context)
+  {
+    char* result;
+
+    _OrthancPluginRetrieveDynamicString params;
+    params.result = &result;
+    params.argument = NULL;
+
+    if (context->InvokeService(context, _OrthancPluginService_GenerateUuid, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+
+
+  typedef struct
+  {
+    OrthancPluginFindCallback callback;
+  } _OrthancPluginFindCallback;
+
+  /**
+   * @brief Register a callback to handle C-Find requests.
+   *
+   * This function registers a callback to handle C-Find SCP requests
+   * that are not related to modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterFindCallback(
+    OrthancPluginContext*      context,
+    OrthancPluginFindCallback  callback)
+  {
+    _OrthancPluginFindCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterFindCallback, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginFindAnswers      *answers;
+    const OrthancPluginFindQuery  *query;
+    const void                    *dicom;
+    uint32_t                       size;
+    uint32_t                       index;
+    uint32_t                      *resultUint32;
+    uint16_t                      *resultGroup;
+    uint16_t                      *resultElement;
+    char                         **resultString;
+  } _OrthancPluginFindOperation;
+
+  /**
+   * @brief Add one answer to some C-Find request.
+   *
+   * This function adds one answer (encoded as a DICOM file) to the
+   * set of answers corresponding to some C-Find SCP request that is
+   * not related to modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @param dicom The answer to be added, encoded as a DICOM file.
+   * @param size The size of the DICOM file.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   * @see OrthancPluginCreateDicom()
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginFindAddAnswer(
+    OrthancPluginContext*      context,
+    OrthancPluginFindAnswers*  answers,
+    const void*                dicom,
+    uint32_t                   size)
+  {
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.answers = answers;
+    params.dicom = dicom;
+    params.size = size;
+
+    return context->InvokeService(context, _OrthancPluginService_FindAddAnswer, &params);
+  }
+
+
+  /**
+   * @brief Mark the set of C-Find answers as incomplete.
+   *
+   * This function marks as incomplete the set of answers
+   * corresponding to some C-Find SCP request that is not related to
+   * modality worklists. This must be used if canceling the handling
+   * of a request when too many answers are to be returned.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginFindMarkIncomplete(
+    OrthancPluginContext*      context,
+    OrthancPluginFindAnswers*  answers)
+  {
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.answers = answers;
+
+    return context->InvokeService(context, _OrthancPluginService_FindMarkIncomplete, &params);
+  }
+
+
+
+  /**
+   * @brief Get the number of tags in a C-Find query.
+   *
+   * This function returns the number of tags that are contained in
+   * the given C-Find query.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The C-Find query.
+   * @return The number of tags.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t  OrthancPluginGetFindQuerySize(
+    OrthancPluginContext*          context,
+    const OrthancPluginFindQuery*  query)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.query = query;
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFindQuerySize, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+  /**
+   * @brief Get one tag in a C-Find query.
+   *
+   * This function returns the group and the element of one DICOM tag
+   * in the given C-Find query.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param group The group of the tag (output).
+   * @param element The element of the tag (output).
+   * @param query The C-Find query.
+   * @param index The index of the tag of interest.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginGetFindQueryTag(
+    OrthancPluginContext*          context,
+    uint16_t*                      group,
+    uint16_t*                      element,
+    const OrthancPluginFindQuery*  query,
+    uint32_t                       index)
+  {
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.query = query;
+    params.index = index;
+    params.resultGroup = group;
+    params.resultElement = element;
+
+    return context->InvokeService(context, _OrthancPluginService_GetFindQueryTag, &params);
+  }
+
+
+  /**
+   * @brief Get the symbolic name of one tag in a C-Find query.
+   *
+   * This function returns the symbolic name of one DICOM tag in the
+   * given C-Find query.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The C-Find query.
+   * @param index The index of the tag of interest.
+   * @return The NULL value in case of error, or a string containing the name of the tag.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE char*  OrthancPluginGetFindQueryTagName(
+    OrthancPluginContext*          context,
+    const OrthancPluginFindQuery*  query,
+    uint32_t                       index)
+  {
+    char* result;
+
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.query = query;
+    params.index = index;
+    params.resultString = &result;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFindQueryTagName, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the value associated with one tag in a C-Find query.
+   *
+   * This function returns the value associated with one tag in the
+   * given C-Find query.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The C-Find query.
+   * @param index The index of the tag of interest.
+   * @return The NULL value in case of error, or a string containing the value of the tag.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE char*  OrthancPluginGetFindQueryValue(
+    OrthancPluginContext*          context,
+    const OrthancPluginFindQuery*  query,
+    uint32_t                       index)
+  {
+    char* result;
+
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.query = query;
+    params.index = index;
+    params.resultString = &result;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFindQueryValue, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+ 
+
+
+
+  typedef struct
+  {
+    OrthancPluginMoveCallback   callback;
+    OrthancPluginGetMoveSize    getMoveSize;
+    OrthancPluginApplyMove      applyMove;
+    OrthancPluginFreeMove       freeMove;
+  } _OrthancPluginMoveCallback;
+
+  /**
+   * @brief Register a callback to handle C-Move requests.
+   *
+   * This function registers a callback to handle C-Move SCP requests.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The main callback.
+   * @param getMoveSize Callback to read the number of C-Move suboperations.
+   * @param applyMove Callback to apply one C-Move suboperations.
+   * @param freeMove Callback to free the C-Move driver.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterMoveCallback(
+    OrthancPluginContext*       context,
+    OrthancPluginMoveCallback   callback,
+    OrthancPluginGetMoveSize    getMoveSize,
+    OrthancPluginApplyMove      applyMove,
+    OrthancPluginFreeMove       freeMove)
+  {
+    _OrthancPluginMoveCallback params;
+    params.callback = callback;
+    params.getMoveSize = getMoveSize;
+    params.applyMove = applyMove;
+    params.freeMove = freeMove;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterMoveCallback, &params);
+  }
+
+
+
+
+#ifdef  __cplusplus
+}
+#endif
+
+
+/** @} */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/SyncOrthancFolder.py	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,102 @@
+#!/usr/bin/python
+
+#
+# This maintenance script updates the content of the "Orthanc" folder
+# to match the latest version of the Orthanc source code.
+#
+
+import multiprocessing
+import os
+import stat
+import urllib2
+
+TARGET = os.path.join(os.path.dirname(__file__), 'Orthanc')
+PLUGIN_SDK_VERSION = '1.1.0'  # TODO Switch to 1.2.1 when available
+REPOSITORY = 'https://bitbucket.org/sjodogne/orthanc/raw'
+
+FILES = [
+    'Core/Cache/LeastRecentlyUsedIndex.h',
+    'Core/Endianness.h',
+    'Core/Enumerations.cpp',
+    'Core/Enumerations.h',
+    'Core/Logging.cpp',
+    'Core/Logging.h',
+    'Core/OrthancException.h',
+    'Core/PrecompiledHeaders.cpp',
+    'Core/PrecompiledHeaders.h',
+    'Core/Toolbox.cpp',
+    'Core/Toolbox.h',
+    'Plugins/Samples/Common/ExportedSymbols.list',
+    'Plugins/Samples/Common/OrthancPluginCppWrapper.cpp',
+    'Plugins/Samples/Common/OrthancPluginCppWrapper.h',
+    'Plugins/Samples/Common/OrthancPluginException.h',
+    'Plugins/Samples/Common/VersionScript.map',
+    'Resources/CMake/AutoGeneratedCode.cmake',
+    'Resources/CMake/BoostConfiguration.cmake',
+    'Resources/CMake/Compiler.cmake',
+    'Resources/CMake/DownloadPackage.cmake',
+    'Resources/CMake/JsonCppConfiguration.cmake',
+    'Resources/EmbedResources.py',
+    'Resources/MinGW-W64-Toolchain32.cmake',
+    'Resources/MinGW-W64-Toolchain64.cmake',
+    'Resources/MinGWToolchain.cmake',
+    'Resources/ThirdParty/VisualStudio/stdint.h',
+    'Resources/ThirdParty/base64/base64.cpp',
+    'Resources/ThirdParty/base64/base64.h',
+    'Resources/WindowsResources.py',
+    'Resources/WindowsResources.rc',
+]
+
+SDK = [
+    'orthanc/OrthancCPlugin.h',
+]   
+
+EXE = [
+    'Resources/EmbedResources.py',
+    'Resources/WindowsResources.py',
+]
+
+
+def Download(x):
+    branch = x[0]
+    source = x[1]
+    target = os.path.join(TARGET, x[2])
+    print target
+
+    try:
+        os.makedirs(os.path.dirname(target))
+    except:
+        pass
+
+    url = '%s/%s/%s' % (REPOSITORY, branch, source)
+
+    try:
+        with open(target, 'w') as f:
+            f.write(urllib2.urlopen(url).read())
+    except:
+        print 'Cannot download %s' % url
+        raise
+
+
+commands = []
+
+for f in FILES:
+    commands.append([ 'default', f, f ])
+
+for f in SDK:
+    commands.append([ 
+        'Orthanc-%s' % PLUGIN_SDK_VERSION, 
+        'Plugins/Include/%s' % f,
+        'Sdk-%s/%s' % (PLUGIN_SDK_VERSION, f) 
+    ])
+
+
+pool = multiprocessing.Pool(10)  # simultaneous downloads
+pool.map(Download, commands)
+
+
+for exe in EXE:
+    path = os.path.join(TARGET, exe)
+    st = os.stat(path)
+    os.chmod(path, st.st_mode | stat.S_IEXEC)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/TestService.js	Wed Mar 22 16:13:52 2017 +0100
@@ -0,0 +1,46 @@
+var port = 8000;
+
+var http = require('http');
+var url = require('url');
+
+function GrantAccess(response, granted, validity) {
+  var answer = {
+    granted: granted
+  }
+
+  if (typeof validity == 'number') {
+    answer.validity = validity;
+  }
+  
+  response.writeHead(200, { 'Content-Type' : 'application/json' });
+  response.end(JSON.stringify(answer));
+}
+
+var server = http.createServer(function(request, response) {
+  if (request.method == 'POST') {
+    var body = '';
+
+    request.on('data', function (data) {
+      body += data;
+    });
+
+    request.on('end', function () {
+      console.log('Received authentication request: ' + body);
+      console.log('HTTP headers: ' + JSON.stringify(request.headers));
+
+      var query = JSON.parse(body);
+
+      //GrantAccess(response, query["level"] != "system", 5);
+      GrantAccess(response, true, 5);
+      //GrantAccess(response, false, 5);
+    });
+    
+  } else {
+    response.writeHead(405);
+    response.end();
+  }
+});
+
+
+console.log('The demo is running at http://localhost:' + port + '/');
+server.listen(port);