changeset 1023:226cfef3822e templating

integration mainline->templating
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 10 Jul 2014 11:42:32 +0200
parents 427a1f996b7b (current diff) 211acef628a1 (diff)
children a93867a94011
files CMakeLists.txt Resources/VisualStudio/stdint.h Resources/base64/base64.cpp Resources/base64/base64.h Resources/md5/md5.c Resources/md5/md5.h Resources/minizip/NOTES Resources/minizip/crypt.h Resources/minizip/ioapi.c Resources/minizip/ioapi.h Resources/minizip/zip.c Resources/minizip/zip.h UnitTestsSources/DicomMap.cpp UnitTestsSources/FileStorage.cpp UnitTestsSources/FromDcmtk.cpp UnitTestsSources/JpegLossless.cpp UnitTestsSources/Lua.cpp UnitTestsSources/MemoryCache.cpp UnitTestsSources/MultiThreading.cpp UnitTestsSources/Png.cpp UnitTestsSources/RestApi.cpp UnitTestsSources/SQLite.cpp UnitTestsSources/SQLiteChromium.cpp UnitTestsSources/Versions.cpp UnitTestsSources/Zip.cpp
diffstat 173 files changed, 16678 insertions(+), 7580 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Wed Jun 25 11:56:48 2014 +0200
+++ b/CMakeLists.txt	Thu Jul 10 11:42:32 2014 +0200
@@ -84,6 +84,9 @@
   Core/HttpServer/MongooseServer.cpp
   Core/HttpServer/HttpFileSender.cpp
   Core/HttpServer/FilesystemHttpSender.cpp
+  Core/RestApi/RestApiCall.cpp
+  Core/RestApi/RestApiGetCall.cpp
+  Core/RestApi/RestApiHierarchy.cpp
   Core/RestApi/RestApiPath.cpp
   Core/RestApi/RestApiOutput.cpp
   Core/RestApi/RestApi.cpp
@@ -91,6 +94,7 @@
   Core/MultiThreading/BagOfRunnablesBySteps.cpp
   Core/MultiThreading/Mutex.cpp
   Core/MultiThreading/ReaderWriterLock.cpp
+  Core/MultiThreading/Semaphore.cpp
   Core/MultiThreading/SharedMessageQueue.cpp
   Core/MultiThreading/ThreadedCommandProcessor.cpp
   Core/ImageFormats/ImageAccessor.cpp
@@ -114,6 +118,10 @@
   OrthancCppClient/Series.cpp
   OrthancCppClient/Instance.cpp
   OrthancCppClient/Patient.cpp
+
+  Plugins/Engine/SharedLibrary.cpp
+  Plugins/Engine/PluginsManager.cpp
+  Plugins/Engine/PluginsHttpHandler.cpp
   )
 
 
@@ -148,27 +156,37 @@
   OrthancServer/ServerToolbox.cpp
   OrthancServer/OrthancFindRequestHandler.cpp
   OrthancServer/OrthancMoveRequestHandler.cpp
+
+  # From "lua-scripting" branch
+  OrthancServer/DicomInstanceToStore.cpp
+  OrthancServer/Scheduler/DeleteInstanceCommand.cpp
+  OrthancServer/Scheduler/ModifyInstanceCommand.cpp
+  OrthancServer/Scheduler/ServerCommandInstance.cpp
+  OrthancServer/Scheduler/ServerJob.cpp
+  OrthancServer/Scheduler/ServerScheduler.cpp
+  OrthancServer/Scheduler/StorePeerCommand.cpp
+  OrthancServer/Scheduler/StoreScuCommand.cpp
   )
 
 
 set(ORTHANC_UNIT_TESTS_SOURCES
-  UnitTestsSources/DicomMap.cpp
-  UnitTestsSources/FileStorage.cpp
-  UnitTestsSources/FromDcmtk.cpp
-  UnitTestsSources/MemoryCache.cpp
-  UnitTestsSources/Png.cpp
-  UnitTestsSources/RestApi.cpp
-  UnitTestsSources/SQLite.cpp
-  UnitTestsSources/SQLiteChromium.cpp
+  UnitTestsSources/DicomMapTests.cpp
+  UnitTestsSources/FileStorageTests.cpp
+  UnitTestsSources/FromDcmtkTests.cpp
+  UnitTestsSources/MemoryCacheTests.cpp
+  UnitTestsSources/PngTests.cpp
+  UnitTestsSources/RestApiTests.cpp
+  UnitTestsSources/SQLiteTests.cpp
+  UnitTestsSources/SQLiteChromiumTests.cpp
   UnitTestsSources/ServerIndexTests.cpp
-  UnitTestsSources/Versions.cpp
-  UnitTestsSources/Zip.cpp
-  UnitTestsSources/Lua.cpp
-  UnitTestsSources/MultiThreading.cpp
+  UnitTestsSources/VersionsTests.cpp
+  UnitTestsSources/ZipTests.cpp
+  UnitTestsSources/LuaTests.cpp
+  UnitTestsSources/MultiThreadingTests.cpp
   UnitTestsSources/UnitTestsMain.cpp
   UnitTestsSources/ImageProcessingTests.cpp
-  UnitTestsSources/JpegLossless.cpp
-
+  UnitTestsSources/JpegLosslessTests.cpp
+  UnitTestsSources/PluginsTests.cpp
   UnitTestsSources/Plustache.cpp
   )
 
@@ -186,8 +204,8 @@
 
 # Prepare the third-party dependencies
 SET(THIRD_PARTY_SOURCES
-  ${CMAKE_SOURCE_DIR}/Resources/md5/md5.c
-  ${CMAKE_SOURCE_DIR}/Resources/base64/base64.cpp
+  ${CMAKE_SOURCE_DIR}/Resources/ThirdParty/md5/md5.c
+  ${CMAKE_SOURCE_DIR}/Resources/ThirdParty/base64/base64.cpp
   )
 
 include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfiguration.cmake)
@@ -389,8 +407,8 @@
   add_library(OrthancClient SHARED
     ${ORTHANC_ROOT}/OrthancCppClient/OrthancCppClient.cpp
     ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/SharedLibrary.cpp
-    ${ORTHANC_ROOT}/Resources/md5/md5.c
-    ${ORTHANC_ROOT}/Resources/base64/base64.cpp
+    ${ORTHANC_ROOT}/Resources/ThirdParty/md5/md5.c
+    ${ORTHANC_ROOT}/Resources/ThirdParty/base64/base64.cpp
     ${ORTHANC_CPP_CLIENT_AUX}
     ${THIRD_PARTY_SOURCES}
     ${CURL_SOURCES}
@@ -464,8 +482,15 @@
     ${CMAKE_CURRENT_BINARY_DIR}/Orthanc.doxygen
     @ONLY)
 
+  configure_file(
+    ${CMAKE_SOURCE_DIR}/Resources/OrthancPlugin.doxygen
+    ${CMAKE_CURRENT_BINARY_DIR}/OrthancPlugin.doxygen
+    @ONLY)
+
   add_custom_target(doc
     ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Orthanc.doxygen
+    COMMAND
+    ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/OrthancPlugin.doxygen
     WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
     COMMENT "Generating internal documentation with Doxygen" VERBATIM
     )
--- a/Core/Compression/ZipWriter.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/Compression/ZipWriter.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -38,7 +38,7 @@
 
 #include "ZipWriter.h"
 
-#include "../../Resources/minizip/zip.h"
+#include "../../Resources/ThirdParty/minizip/zip.h"
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <limits>
 
--- a/Core/DicomFormat/DicomTag.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/DicomFormat/DicomTag.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -116,4 +116,132 @@
 
     return "";
   }
+
+
+  void DicomTag::GetTagsForModule(std::set<DicomTag>& target,
+                                  ResourceType module)
+  {
+    // REFERENCE: 11_03pu.pdf, DICOM PS 3.3 2011 - Information Object Definitions
+    target.clear();
+
+    switch (module)
+    {
+      case ResourceType_Patient:
+        // This is Table C.7-1 "Patient Module Attributes" (p. 373)
+        target.insert(DicomTag(0x0010, 0x0010));   // Patient's name
+        target.insert(DicomTag(0x0010, 0x0020));   // Patient ID
+        target.insert(DicomTag(0x0010, 0x0030));   // Patient's birth date
+        target.insert(DicomTag(0x0010, 0x0040));   // Patient's sex
+        target.insert(DicomTag(0x0008, 0x1120));   // Referenced patient sequence
+        target.insert(DicomTag(0x0010, 0x0032));   // Patient's birth time
+        target.insert(DicomTag(0x0010, 0x1000));   // Other patient IDs
+        target.insert(DicomTag(0x0010, 0x1002));   // Other patient IDs sequence
+        target.insert(DicomTag(0x0010, 0x1001));   // Other patient names
+        target.insert(DicomTag(0x0010, 0x2160));   // Ethnic group
+        target.insert(DicomTag(0x0010, 0x4000));   // Patient comments
+        target.insert(DicomTag(0x0010, 0x2201));   // Patient species description
+        target.insert(DicomTag(0x0010, 0x2202));   // Patient species code sequence
+        target.insert(DicomTag(0x0010, 0x2292));   // Patient breed description
+        target.insert(DicomTag(0x0010, 0x2293));   // Patient breed code sequence
+        target.insert(DicomTag(0x0010, 0x2294));   // Breed registration sequence
+        target.insert(DicomTag(0x0010, 0x2297));   // Responsible person
+        target.insert(DicomTag(0x0010, 0x2298));   // Responsible person role
+        target.insert(DicomTag(0x0010, 0x2299));   // Responsible organization
+        target.insert(DicomTag(0x0012, 0x0062));   // Patient identity removed
+        target.insert(DicomTag(0x0012, 0x0063));   // De-identification method
+        target.insert(DicomTag(0x0012, 0x0064));   // De-identification method code sequence
+
+        // Table 10-18 ISSUER OF PATIENT ID MACRO (p. 112)
+        target.insert(DicomTag(0x0010, 0x0021));   // Issuer of Patient ID
+        target.insert(DicomTag(0x0010, 0x0024));   // Issuer of Patient ID qualifiers sequence
+        break;
+
+      case ResourceType_Study:
+        // This is Table C.7-3 "General Study Module Attributes" (p. 378)
+        target.insert(DicomTag(0x0020, 0x000d));   // Study instance UID
+        target.insert(DicomTag(0x0008, 0x0020));   // Study date
+        target.insert(DicomTag(0x0008, 0x0030));   // Study time
+        target.insert(DicomTag(0x0008, 0x0090));   // Referring physician's name
+        target.insert(DicomTag(0x0008, 0x0096));   // Referring physician identification sequence
+        target.insert(DicomTag(0x0020, 0x0010));   // Study ID
+        target.insert(DicomTag(0x0008, 0x0050));   // Accession number
+        target.insert(DicomTag(0x0008, 0x0051));   // Issuer of accession number sequence
+        target.insert(DicomTag(0x0008, 0x1030));   // Study description
+        target.insert(DicomTag(0x0008, 0x1048));   // Physician(s) of record
+        target.insert(DicomTag(0x0008, 0x1049));   // Physician(s) of record identification sequence
+        target.insert(DicomTag(0x0008, 0x1060));   // Name of physician(s) reading study
+        target.insert(DicomTag(0x0008, 0x1062));   // Physician(s) reading study identification sequence
+        target.insert(DicomTag(0x0032, 0x1034));   // Requesting service code sequence
+        target.insert(DicomTag(0x0008, 0x1110));   // Referenced study sequence
+        target.insert(DicomTag(0x0008, 0x1032));   // Procedure code sequence
+        target.insert(DicomTag(0x0040, 0x1012));   // Reason for performed procedure code sequence
+        break;
+
+      case ResourceType_Series:
+        // This is Table C.7-5 "General Series Module Attributes" (p. 385)
+        target.insert(DicomTag(0x0008, 0x0060));   // Modality 
+        target.insert(DicomTag(0x0020, 0x000e));   // Series Instance UID 
+        target.insert(DicomTag(0x0020, 0x0011));   // Series Number 
+        target.insert(DicomTag(0x0020, 0x0060));   // Laterality 
+        target.insert(DicomTag(0x0008, 0x0021));   // Series Date 
+        target.insert(DicomTag(0x0008, 0x0031));   // Series Time 
+        target.insert(DicomTag(0x0008, 0x1050));   // Performing Physicians’ Name 
+        target.insert(DicomTag(0x0008, 0x1052));   // Performing Physician Identification Sequence 
+        target.insert(DicomTag(0x0018, 0x1030));   // Protocol Name
+        target.insert(DicomTag(0x0008, 0x103e));   // Series Description 
+        target.insert(DicomTag(0x0008, 0x103f));   // Series Description Code Sequence 
+        target.insert(DicomTag(0x0008, 0x1070));   // Operators' Name 
+        target.insert(DicomTag(0x0008, 0x1072));   // Operator Identification Sequence 
+        target.insert(DicomTag(0x0008, 0x1111));   // Referenced Performed Procedure Step Sequence
+        target.insert(DicomTag(0x0008, 0x1250));   // Related Series Sequence
+        target.insert(DicomTag(0x0018, 0x0015));   // Body Part Examined
+        target.insert(DicomTag(0x0018, 0x5100));   // Patient Position
+        target.insert(DicomTag(0x0028, 0x0108));   // Smallest Pixel Value in Series 
+        target.insert(DicomTag(0x0029, 0x0109));   // Largest Pixel Value in Series 
+        target.insert(DicomTag(0x0040, 0x0275));   // Request Attributes Sequence 
+        target.insert(DicomTag(0x0010, 0x2210));   // Anatomical Orientation Type
+
+        // Table 10-16 PERFORMED PROCEDURE STEP SUMMARY MACRO ATTRIBUTES
+        target.insert(DicomTag(0x0040, 0x0253));   // Performed Procedure Step ID 
+        target.insert(DicomTag(0x0040, 0x0244));   // Performed Procedure Step Start Date 
+        target.insert(DicomTag(0x0040, 0x0245));   // Performed Procedure Step Start Time 
+        target.insert(DicomTag(0x0040, 0x0254));   // Performed Procedure Step Description 
+        target.insert(DicomTag(0x0040, 0x0260));   // Performed Protocol Code Sequence 
+        target.insert(DicomTag(0x0040, 0x0280));   // Comments on the Performed Procedure Step
+        break;
+
+      case ResourceType_Instance:
+        // This is Table C.12-1 "SOP Common Module Attributes" (p. 1207)
+        target.insert(DicomTag(0x0008, 0x0016));   // SOP Class UID
+        target.insert(DicomTag(0x0008, 0x0018));   // SOP Instance UID 
+        target.insert(DicomTag(0x0008, 0x0005));   // Specific Character Set 
+        target.insert(DicomTag(0x0008, 0x0012));   // Instance Creation Date 
+        target.insert(DicomTag(0x0008, 0x0013));   // Instance Creation Time 
+        target.insert(DicomTag(0x0008, 0x0014));   // Instance Creator UID 
+        target.insert(DicomTag(0x0008, 0x001a));   // Related General SOP Class UID 
+        target.insert(DicomTag(0x0008, 0x001b));   // Original Specialized SOP Class UID 
+        target.insert(DicomTag(0x0008, 0x0110));   // Coding Scheme Identification Sequence 
+        target.insert(DicomTag(0x0008, 0x0201));   // Timezone Offset From UTC 
+        target.insert(DicomTag(0x0018, 0xa001));   // Contributing Equipment Sequence
+        target.insert(DicomTag(0x0020, 0x0013));   // Instance Number 
+        target.insert(DicomTag(0x0100, 0x0410));   // SOP Instance Status 
+        target.insert(DicomTag(0x0100, 0x0420));   // SOP Authorization DateTime 
+        target.insert(DicomTag(0x0100, 0x0424));   // SOP Authorization Comment 
+        target.insert(DicomTag(0x0100, 0x0426));   // Authorization Equipment Certification Number
+        target.insert(DicomTag(0x0400, 0x0500));   // Encrypted Attributes Sequence
+        target.insert(DicomTag(0x0400, 0x0561));   // Original Attributes Sequence 
+        target.insert(DicomTag(0x0040, 0xa390));   // HL7 Structured Document Reference Sequence
+        target.insert(DicomTag(0x0028, 0x0303));   // Longitudinal Temporal Information Modified 
+
+        // Table C.12-6 "DIGITAL SIGNATURES MACRO ATTRIBUTES" (p. 1216)
+        target.insert(DicomTag(0x4ffe, 0x0001));   // MAC Parameters sequence
+        target.insert(DicomTag(0xfffa, 0xfffa));   // Digital signatures sequence
+        break;
+
+        // TODO IMAGE MODULE?
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
 }
--- a/Core/DicomFormat/DicomTag.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/DicomFormat/DicomTag.h	Thu Jul 10 11:42:32 2014 +0200
@@ -33,8 +33,10 @@
 #pragma once
 
 #include <string>
+#include <set>
 #include <stdint.h>
 
+#include "../Enumerations.h"
 
 namespace Orthanc
 {
@@ -81,6 +83,9 @@
     std::string Format() const;
 
     friend std::ostream& operator<< (std::ostream& o, const DicomTag& tag);
+
+    static void GetTagsForModule(std::set<DicomTag>& target,
+                                 ResourceType module);
   };
 
   // Aliases for the most useful tags
--- a/Core/Enumerations.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/Enumerations.h	Thu Jul 10 11:42:32 2014 +0200
@@ -71,7 +71,8 @@
     ErrorCode_InexistentTag,
     ErrorCode_ReadOnly,
     ErrorCode_IncompatibleImageFormat,
-    ErrorCode_IncompatibleImageSize
+    ErrorCode_IncompatibleImageSize,
+    ErrorCode_SharedLibrary
   };
 
   /**
@@ -228,6 +229,14 @@
   };
 
 
+  enum Encoding
+  {
+    Encoding_Ascii,
+    Encoding_Utf8,
+    Encoding_Latin1
+  };
+
+
   /**
    * WARNING: Do not change the explicit values in the enumerations
    * below this point. This would result in incompatible databases
--- a/Core/HttpServer/BufferHttpSender.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/HttpServer/BufferHttpSender.h	Thu Jul 10 11:42:32 2014 +0200
@@ -49,7 +49,9 @@
     virtual bool SendData(HttpOutput& output)
     {
       if (buffer_.size())
-        output.Send(&buffer_[0], buffer_.size());
+      {
+        output.SendBodyData(&buffer_[0], buffer_.size());
+      }
 
       return true;
     }
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/HttpServer/EmbeddedResourceHttpHandler.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -51,13 +51,7 @@
   }
 
 
-  bool EmbeddedResourceHttpHandler::IsServedUri(const UriComponents& uri)
-  {
-    return Toolbox::IsChildUri(baseUri_, uri);
-  }
-
-
-  void EmbeddedResourceHttpHandler::Handle(
+  bool EmbeddedResourceHttpHandler::Handle(
     HttpOutput& output,
     HttpMethod method,
     const UriComponents& uri,
@@ -65,10 +59,16 @@
     const Arguments& arguments,
     const std::string&)
   {
+    if (!Toolbox::IsChildUri(baseUri_, uri))
+    {
+      // This URI is not served by this handler
+      return false;
+    }
+
     if (method != HttpMethod_Get)
     {
       output.SendMethodNotAllowedError("GET");
-      return;
+      return true;
     }
 
     std::string resourcePath = Toolbox::FlattenUri(uri, baseUri_.size());
@@ -85,5 +85,7 @@
       LOG(WARNING) << "Unable to find HTTP resource: " << resourcePath;
       output.SendHeader(HttpStatus_404_NotFound);
     }
+
+    return true;
   } 
 }
--- a/Core/HttpServer/EmbeddedResourceHttpHandler.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/HttpServer/EmbeddedResourceHttpHandler.h	Thu Jul 10 11:42:32 2014 +0200
@@ -50,9 +50,7 @@
       const std::string& baseUri,
       EmbeddedResources::DirectoryResourceId resourceId);
 
-    virtual bool IsServedUri(const UriComponents& uri);
-
-    virtual void Handle(
+    virtual bool Handle(
       HttpOutput& output,
       HttpMethod method,
       const UriComponents& uri,
--- a/Core/HttpServer/FilesystemHttpHandler.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/HttpServer/FilesystemHttpHandler.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -56,15 +56,15 @@
     namespace fs = boost::filesystem;
 
     output.SendOkHeader("text/html", false, 0, NULL);
-    output.SendString("<html>");
-    output.SendString("  <body>");
-    output.SendString("    <h1>Subdirectories</h1>");
-    output.SendString("    <ul>");
+    output.SendBodyString("<html>");
+    output.SendBodyString("  <body>");
+    output.SendBodyString("    <h1>Subdirectories</h1>");
+    output.SendBodyString("    <ul>");
 
     if (uri.size() > 0)
     {
       std::string h = Toolbox::FlattenUri(uri) + "/..";
-      output.SendString("<li><a href=\"" + h + "\">..</a></li>");
+      output.SendBodyString("<li><a href=\"" + h + "\">..</a></li>");
     }
 
     fs::directory_iterator end;
@@ -78,12 +78,12 @@
 
       std::string h = Toolbox::FlattenUri(uri) + "/" + f;
       if (fs::is_directory(it->status()))
-        output.SendString("<li><a href=\"" + h + "\">" + f + "</a></li>");
+        output.SendBodyString("<li><a href=\"" + h + "\">" + f + "</a></li>");
     }      
 
-    output.SendString("    </ul>");      
-    output.SendString("    <h1>Files</h1>");
-    output.SendString("    <ul>");
+    output.SendBodyString("    </ul>");      
+    output.SendBodyString("    <h1>Files</h1>");
+    output.SendBodyString("    <ul>");
 
     for (fs::directory_iterator it(p) ; it != end; ++it)
     {
@@ -95,12 +95,12 @@
 
       std::string h = Toolbox::FlattenUri(uri) + "/" + f;
       if (fs::is_regular_file(it->status()))
-        output.SendString("<li><a href=\"" + h + "\">" + f + "</a></li>");
+        output.SendBodyString("<li><a href=\"" + h + "\">" + f + "</a></li>");
     }      
 
-    output.SendString("    </ul>");
-    output.SendString("  </body>");
-    output.SendString("</html>");
+    output.SendBodyString("    </ul>");
+    output.SendBodyString("  </body>");
+    output.SendBodyString("</html>");
   }
 
 
@@ -120,13 +120,7 @@
   }
 
 
-  bool FilesystemHttpHandler::IsServedUri(const UriComponents& uri)
-  {
-    return Toolbox::IsChildUri(pimpl_->baseUri_, uri);
-  }
-
-
-  void FilesystemHttpHandler::Handle(
+  bool FilesystemHttpHandler::Handle(
     HttpOutput& output,
     HttpMethod method,
     const UriComponents& uri,
@@ -134,10 +128,16 @@
     const Arguments& arguments,
     const std::string&)
   {
+    if (!Toolbox::IsChildUri(pimpl_->baseUri_, uri))
+    {
+      // This URI is not served by this handler
+      return false;
+    }
+
     if (method != HttpMethod_Get)
     {
       output.SendMethodNotAllowedError("GET");
-      return;
+      return true;
     }
 
     namespace fs = boost::filesystem;
@@ -164,5 +164,7 @@
     {
       output.SendHeader(HttpStatus_404_NotFound);
     }
+
+    return true;
   } 
 }
--- a/Core/HttpServer/FilesystemHttpHandler.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/HttpServer/FilesystemHttpHandler.h	Thu Jul 10 11:42:32 2014 +0200
@@ -52,9 +52,7 @@
     FilesystemHttpHandler(const std::string& baseUri,
                           const std::string& root);
 
-    virtual bool IsServedUri(const UriComponents& uri);
-
-    virtual void Handle(
+    virtual bool Handle(
       HttpOutput& output,
       HttpMethod method,
       const UriComponents& uri,
--- a/Core/HttpServer/FilesystemHttpSender.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/HttpServer/FilesystemHttpSender.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -73,7 +73,7 @@
       }
       else
       {
-        output.Send(&buffer[0], nbytes);
+        output.SendBodyData(&buffer[0], nbytes);
       }
     }
 
--- a/Core/HttpServer/HttpHandler.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/HttpServer/HttpHandler.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -34,6 +34,8 @@
 #include "HttpHandler.h"
 
 #include <string.h>
+#include <iostream>
+
 
 namespace Orthanc
 {
@@ -62,7 +64,7 @@
   }
 
 
-  void HttpHandler::ParseGetQuery(HttpHandler::Arguments& result, const char* query)
+  void HttpHandler::ParseGetArguments(HttpHandler::Arguments& result, const char* query)
   {
     const char* pos = query;
 
@@ -84,6 +86,24 @@
   }
 
 
+  void  HttpHandler::ParseGetQuery(UriComponents& uri,
+                                   HttpHandler::Arguments& getArguments, 
+                                   const char* query)
+  {
+    const char *questionMark = ::strchr(query, '?');
+    if (questionMark == NULL)
+    {
+      // No question mark in the string
+      Toolbox::SplitUriComponents(uri, query);
+      getArguments.clear();
+    }
+    else
+    {
+      Toolbox::SplitUriComponents(uri, std::string(query, questionMark));
+      HttpHandler::ParseGetArguments(getArguments, questionMark + 1);
+    }    
+  }
+
 
   std::string HttpHandler::GetArgument(const Arguments& getArguments,
                                        const std::string& name,
--- a/Core/HttpServer/HttpHandler.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/HttpServer/HttpHandler.h	Thu Jul 10 11:42:32 2014 +0200
@@ -50,16 +50,18 @@
     {
     }
 
-    virtual bool IsServedUri(const UriComponents& uri) = 0;
-
-    virtual void Handle(HttpOutput& output,
+    virtual bool Handle(HttpOutput& output,
                         HttpMethod method,
                         const UriComponents& uri,
                         const Arguments& headers,
                         const Arguments& getArguments,
                         const std::string& postData) = 0;
 
-    static void ParseGetQuery(HttpHandler::Arguments& result, 
+    static void ParseGetArguments(HttpHandler::Arguments& result, 
+                                  const char* query);
+
+    static void ParseGetQuery(UriComponents& uri,
+                              HttpHandler::Arguments& getArguments, 
                               const char* query);
 
     static std::string GetArgument(const Arguments& getArguments,
--- a/Core/HttpServer/HttpOutput.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/HttpServer/HttpOutput.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -42,14 +42,71 @@
 
 namespace Orthanc
 {
-  void HttpOutput::SendString(const std::string& s)
+  void HttpOutput::StateMachine::SendHttpStatus(HttpStatus status)
   {
-    if (s.size() > 0)
+    if (state_ != State_WaitingHttpStatus)
     {
-      Send(&s[0], s.size());
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    stream_.OnHttpStatusReceived(status);
+    state_ = State_WritingHeader;
+
+    std::string s = "HTTP/1.1 " + 
+      boost::lexical_cast<std::string>(status) +
+      " " + std::string(EnumerationToString(status)) +
+      "\r\n";
+
+    stream_.Send(true, &s[0], s.size());
+  }
+
+  void HttpOutput::StateMachine::SendHeaderData(const void* buffer, size_t length)
+  {
+    if (state_ != State_WritingHeader)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    stream_.Send(true, buffer, length);
+  }
+
+  void HttpOutput::StateMachine::SendHeaderString(const std::string& str)
+  {
+    if (str.size() > 0)
+    {
+      SendHeaderData(&str[0], str.size());
     }
   }
 
+  void HttpOutput::StateMachine::SendBodyData(const void* buffer, size_t length)
+  {
+    if (state_ == State_WaitingHttpStatus)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    if (state_ == State_WritingHeader)
+    {
+      // Close the HTTP header before writing the body
+      stream_.Send(true, "\r\n", 2);
+      state_ = State_WritingBody;
+    }
+
+    if (length > 0)
+    {
+      stream_.Send(false, buffer, length);
+    }
+  }
+
+  void HttpOutput::StateMachine::SendBodyString(const std::string& str)
+  {
+    if (str.size() > 0)
+    {
+      SendBodyData(&str[0], str.size());
+    }
+  }
+
+
   void HttpOutput::PrepareOkHeader(Header& header,
                                    const char* contentType,
                                    bool hasContentLength,
@@ -87,49 +144,37 @@
 
   void HttpOutput::SendOkHeader(const Header& header)
   {
-    std::string s = "HTTP/1.1 200 OK\r\n";
+    stateMachine_.SendHttpStatus(HttpStatus_200_Ok);
 
+    std::string s;
     for (Header::const_iterator 
            it = header.begin(); it != header.end(); ++it)
     {
       s += it->first + ": " + it->second + "\r\n";
     }
 
-    s += "\r\n";
-
-    Send(&s[0], s.size());
+    stateMachine_.SendHeaderString(s);
   }
 
 
   void HttpOutput::SendMethodNotAllowedError(const std::string& allowed)
   {
-    std::string s = 
-      "HTTP/1.1 405 " + std::string(EnumerationToString(HttpStatus_405_MethodNotAllowed)) +
-      "\r\nAllow: " + allowed + 
-      "\r\n\r\n";
-    Send(&s[0], s.size());
+    stateMachine_.SendHttpStatus(HttpStatus_405_MethodNotAllowed);
+    stateMachine_.SendHeaderString("Allow: " + allowed + "\r\n");
   }
 
 
   void HttpOutput::SendHeader(HttpStatus status)
   {
     if (status == HttpStatus_200_Ok ||
+        status == HttpStatus_301_MovedPermanently ||
+        status == HttpStatus_401_Unauthorized ||
         status == HttpStatus_405_MethodNotAllowed)
     {
       throw OrthancException("Please use the dedicated methods to this HTTP status code in HttpOutput");
     }
     
-    SendHeaderInternal(status);
-  }
-
-
-  void HttpOutput::SendHeaderInternal(HttpStatus status)
-  {
-    std::string s = "HTTP/1.1 " + 
-      boost::lexical_cast<std::string>(status) +
-      " " + std::string(EnumerationToString(status)) +
-      "\r\n\r\n";
-    Send(&s[0], s.size());
+    stateMachine_.SendHttpStatus(status);
   }
 
 
@@ -137,7 +182,7 @@
                                                const std::string& contentType)
   {
     SendOkHeader(contentType.c_str(), true, buffer.size(), NULL);
-    SendString(buffer);
+    SendBodyString(buffer);
   }
 
 
@@ -160,7 +205,7 @@
     PrepareOkHeader(header, contentType.c_str(), true, buffer.size(), NULL);
     PrepareCookies(header, cookies);
     SendOkHeader(header);
-    SendString(buffer);
+    SendBodyString(buffer);
   }
 
 
@@ -169,7 +214,7 @@
                                                const std::string& contentType)
   {
     SendOkHeader(contentType.c_str(), true, size, NULL);
-    Send(buffer, size);
+    SendBodyData(buffer, size);
   }
 
 
@@ -182,17 +227,21 @@
     PrepareOkHeader(header, contentType.c_str(), true, size, NULL);
     PrepareCookies(header, cookies);
     SendOkHeader(header);
-    Send(buffer, size);
+    SendBodyData(buffer, size);
   }
 
 
-
   void HttpOutput::Redirect(const std::string& path)
   {
-    std::string s = 
-      "HTTP/1.1 301 " + std::string(EnumerationToString(HttpStatus_301_MovedPermanently)) + 
-      "\r\nLocation: " + path +
-      "\r\n\r\n";
-    Send(&s[0], s.size());  
+    stateMachine_.SendHttpStatus(HttpStatus_301_MovedPermanently);
+    stateMachine_.SendHeaderString("Location: " + path + "\r\n");
   }
+
+
+  void HttpOutput::SendUnauthorized(const std::string& realm)
+  {
+    stateMachine_.SendHttpStatus(HttpStatus_401_Unauthorized);
+    stateMachine_.SendHeaderString("WWW-Authenticate: Basic realm=\"" + realm + "\"\r\n");
+  }
+
 }
--- a/Core/HttpServer/HttpOutput.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/HttpServer/HttpOutput.h	Thu Jul 10 11:42:32 2014 +0200
@@ -36,16 +36,46 @@
 #include <string>
 #include <stdint.h>
 #include "../Enumerations.h"
+#include "IHttpOutputStream.h"
 #include "HttpHandler.h"
 
 namespace Orthanc
 {
-  class HttpOutput
+  class HttpOutput : public boost::noncopyable
   {
   private:
     typedef std::list< std::pair<std::string, std::string> >  Header;
 
-    void SendHeaderInternal(HttpStatus status);
+    class StateMachine : public boost::noncopyable
+    {
+    private:
+      enum State
+      {
+        State_WaitingHttpStatus,
+        State_WritingHeader,      
+        State_WritingBody
+      };
+
+      IHttpOutputStream& stream_;
+      State state_;
+
+    public:
+      StateMachine(IHttpOutputStream& stream) : 
+        stream_(stream),
+        state_(State_WaitingHttpStatus)
+      {
+      }
+
+      void SendHttpStatus(HttpStatus status);
+
+      void SendHeaderData(const void* buffer, size_t length);
+
+      void SendHeaderString(const std::string& str);
+
+      void SendBodyData(const void* buffer, size_t length);
+
+      void SendBodyString(const std::string& str);
+    };
 
     void PrepareOkHeader(Header& header,
                          const char* contentType,
@@ -58,19 +88,27 @@
     void PrepareCookies(Header& header,
                         const HttpHandler::Arguments& cookies);
 
+    StateMachine stateMachine_;
+
   public:
-    virtual ~HttpOutput()
+    HttpOutput(IHttpOutputStream& stream) : stateMachine_(stream)
     {
     }
 
-    virtual void Send(const void* buffer, size_t length) = 0;
-
     void SendOkHeader(const char* contentType,
                       bool hasContentLength,
                       uint64_t contentLength,
                       const char* contentFilename);
 
-    void SendString(const std::string& s);
+    void SendBodyData(const void* buffer, size_t length)
+    {
+      stateMachine_.SendBodyData(buffer, length);
+    }
+
+    void SendBodyString(const std::string& str)
+    {
+      stateMachine_.SendBodyString(str);
+    }
 
     void SendMethodNotAllowedError(const std::string& allowed);
 
@@ -78,6 +116,8 @@
 
     void Redirect(const std::string& path);
 
+    void SendUnauthorized(const std::string& realm);
+
     // Higher-level constructs to send entire buffers ----------------------------
 
     void AnswerBufferWithContentType(const std::string& buffer,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/HttpServer/IHttpOutputStream.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,53 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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 "../Enumerations.h"
+
+#include <string>
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class IHttpOutputStream : public boost::noncopyable
+  {
+  public:
+    virtual ~IHttpOutputStream()
+    {
+    }
+
+    virtual void OnHttpStatusReceived(HttpStatus status) = 0;
+
+    virtual void Send(bool isHeader, const void* buffer, size_t length) = 0;
+  };
+}
--- a/Core/HttpServer/MongooseServer.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/HttpServer/MongooseServer.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -68,23 +68,28 @@
   namespace
   {
     // Anonymous namespace to avoid clashes between compilation modules
-    class MongooseOutput : public HttpOutput
+    class MongooseOutputStream : public IHttpOutputStream
     {
     private:
       struct mg_connection* connection_;
 
     public:
-      MongooseOutput(struct mg_connection* connection) : connection_(connection)
+      MongooseOutputStream(struct mg_connection* connection) : connection_(connection)
       {
       }
 
-      virtual void Send(const void* buffer, size_t length)
+      virtual void Send(bool isHeader, const void* buffer, size_t length)
       {
         if (length > 0)
         {
           mg_write(connection_, buffer, length);
         }
       }
+
+      virtual void OnHttpStatusReceived(HttpStatus status)
+      {
+        // Ignore this
+      }
     };
 
 
@@ -255,23 +260,6 @@
 
 
 
-  HttpHandler* MongooseServer::FindHandler(const UriComponents& forUri) const
-  {
-    for (Handlers::const_iterator it = 
-           handlers_.begin(); it != handlers_.end(); ++it) 
-    {
-      if ((*it)->IsServedUri(forUri))
-      {
-        return *it;
-      }
-    }
-
-    return NULL;
-  }
-
-
-
-
   static PostDataStatus ReadBody(std::string& postData,
                                  struct mg_connection *connection,
                                  const HttpHandler::Arguments& headers)
@@ -421,15 +409,6 @@
   }
 
 
-  static void SendUnauthorized(HttpOutput& output)
-  {
-    std::string s = "HTTP/1.1 401 Unauthorized\r\n" 
-      "WWW-Authenticate: Basic realm=\"" ORTHANC_REALM "\""
-      "\r\n\r\n";
-    output.Send(&s[0], s.size());
-  }
-
-
   static bool Authorize(const MongooseServer& that,
                         const HttpHandler::Arguments& headers,
                         HttpOutput& output)
@@ -449,7 +428,7 @@
 
     if (!granted)
     {
-      SendUnauthorized(output);
+      output.SendUnauthorized(ORTHANC_REALM);
       return false;
     }
     else
@@ -576,13 +555,14 @@
     if (event == MG_NEW_REQUEST) 
     {
       MongooseServer* that = reinterpret_cast<MongooseServer*>(request->user_data);
-      MongooseOutput output(connection);
+      MongooseOutputStream stream(connection);
+      HttpOutput output(stream);
 
       // Check remote calls
       if (!that->IsRemoteAccessAllowed() &&
           request->remote_ip != LOCALHOST)
       {
-        SendUnauthorized(output);
+        output.SendUnauthorized(ORTHANC_REALM);
         return (void*) "";
       }
 
@@ -601,7 +581,7 @@
       HttpHandler::Arguments argumentsGET;
       if (!strcmp(request->request_method, "GET"))
       {
-        HttpHandler::ParseGetQuery(argumentsGET, request->query_string);
+        HttpHandler::ParseGetArguments(argumentsGET, request->query_string);
       }
 
 
@@ -637,7 +617,7 @@
 
         if (!filter->IsAllowed(method, request->uri, remoteIp, username.c_str()))
         {
-          SendUnauthorized(output);
+          output.SendUnauthorized(ORTHANC_REALM);
           return (void*) "";
         }
       }
@@ -690,7 +670,7 @@
       }
 
 
-      // Call the proper handler for this URI
+      // Decompose the URI into its components
       UriComponents uri;
       try
       {
@@ -703,31 +683,36 @@
       }
 
 
-      HttpHandler* handler = that->FindHandler(uri);
-      if (handler)
+      // Loop over the candidate handlers for this URI
+      LOG(INFO) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri);
+      bool found = false;
+
+      for (MongooseServer::Handlers::const_iterator it = 
+             that->GetHandlers().begin(); it != that->GetHandlers().end() && !found; ++it) 
       {
         try
         {
-          LOG(INFO) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri);
-          handler->Handle(output, method, uri, headers, argumentsGET, body);
+          found = (*it)->Handle(output, method, uri, headers, argumentsGET, body);
         }
         catch (OrthancException& e)
         {
-          LOG(ERROR) << "MongooseServer Exception [" << e.What() << "]";
-          output.SendHeader(HttpStatus_500_InternalServerError);        
+          // Using this candidate handler results in an exception
+          LOG(ERROR) << "Exception in the HTTP handler: " << e.What();
+          return (void*) "";
         }
         catch (boost::bad_lexical_cast&)
         {
-          LOG(ERROR) << "MongooseServer Exception: Bad lexical cast";
-          output.SendHeader(HttpStatus_400_BadRequest);
+          LOG(ERROR) << "Exception in the HTTP handler: Bad lexical cast";
+          return (void*) "";
         }
         catch (std::runtime_error&)
         {
-          LOG(ERROR) << "MongooseServer Exception: Presumably a bad JSON request";
-          output.SendHeader(HttpStatus_400_BadRequest);
+          LOG(ERROR) << "Exception in the HTTP handler: Presumably a bad JSON request";
+          return (void*) "";
         }
       }
-      else
+      
+      if (!found)
       {
         output.SendHeader(HttpStatus_404_NotFound);
       }
@@ -818,23 +803,17 @@
   }
 
 
-  void MongooseServer::RegisterHandler(HttpHandler* handler)
+  void MongooseServer::RegisterHandler(HttpHandler& handler)
   {
     Stop();
 
-    handlers_.push_back(handler);
+    handlers_.push_back(&handler);
   }
 
 
   void MongooseServer::ClearHandlers()
   {
     Stop();
-
-    for (Handlers::iterator it = 
-           handlers_.begin(); it != handlers_.end(); ++it)
-    {
-      delete *it;
-    }
   }
 
 
--- a/Core/HttpServer/MongooseServer.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/HttpServer/MongooseServer.h	Thu Jul 10 11:42:32 2014 +0200
@@ -59,12 +59,14 @@
 
   class MongooseServer
   {
+  public:
+    typedef std::list<HttpHandler*> Handlers;
+
   private:
     // http://stackoverflow.com/questions/311166/stdauto-ptr-or-boostshared-ptr-for-pimpl-idiom
     struct PImpl;
     boost::shared_ptr<PImpl> pimpl_;
 
-    typedef std::list<HttpHandler*> Handlers;
     Handlers handlers_;
 
     typedef std::set<std::string> RegisteredUsers;
@@ -100,7 +102,7 @@
     void RegisterUser(const char* username,
                       const char* password);
 
-    void RegisterHandler(HttpHandler* handler);  // This takes the ownership
+    void RegisterHandler(HttpHandler& handler);
 
     bool IsAuthenticationEnabled() const
     {
@@ -139,11 +141,13 @@
 
     void ClearHandlers();
 
-    // Can return NULL if no handler is associated to this URI
-    HttpHandler* FindHandler(const UriComponents& forUri) const;
-
     ChunkStore& GetChunkStore();
 
     bool IsValidBasicHttpAuthentication(const std::string& basic) const;
+
+    const Handlers& GetHandlers() const
+    {
+      return handlers_;
+    }
   };
 }
--- a/Core/ImageFormats/ImageAccessor.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/ImageFormats/ImageAccessor.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -168,7 +168,7 @@
     pitch_ = pitch;
     buffer_ = const_cast<void*>(buffer);
 
-    assert(GetBytesPerPixel(format_) * width_ <= pitch_);
+    assert(GetBytesPerPixel() * width_ <= pitch_);
   }
 
 
@@ -185,7 +185,7 @@
     pitch_ = pitch;
     buffer_ = buffer;
 
-    assert(GetBytesPerPixel(format_) * width_ <= pitch_);
+    assert(GetBytesPerPixel() * width_ <= pitch_);
   }
 
 
--- a/Core/ImageFormats/ImageAccessor.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/ImageFormats/ImageAccessor.h	Thu Jul 10 11:42:32 2014 +0200
@@ -62,6 +62,11 @@
       return format_;
     }
 
+    unsigned int GetBytesPerPixel() const
+    {
+      return ::Orthanc::GetBytesPerPixel(format_);
+    }
+
     unsigned int GetWidth() const
     {
       return width_;
--- a/Core/ImageFormats/ImageProcessing.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/ImageFormats/ImageProcessing.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -75,6 +75,44 @@
   }
 
 
+  template <typename TargetType>
+  static void ConvertColorToGrayscale(ImageAccessor& target,
+                              const ImageAccessor& source)
+  {
+    assert(source.GetFormat() == PixelFormat_RGB24);
+
+    const TargetType minValue = std::numeric_limits<TargetType>::min();
+    const TargetType maxValue = std::numeric_limits<TargetType>::max();
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      TargetType* t = reinterpret_cast<TargetType*>(target.GetRow(y));
+      const uint8_t* s = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
+
+      for (unsigned int x = 0; x < source.GetWidth(); x++, t++, s += 3)
+      {
+        // Y = 0.2126 R + 0.7152 G + 0.0722 B
+        int32_t v = (2126 * static_cast<int32_t>(s[0]) +
+                     7152 * static_cast<int32_t>(s[1]) +
+                     0722 * static_cast<int32_t>(s[2])) / 1000;
+        
+        if (static_cast<int32_t>(v) < static_cast<int32_t>(minValue))
+        {
+          *t = minValue;
+        }
+        else if (static_cast<int32_t>(v) > static_cast<int32_t>(maxValue))
+        {
+          *t = maxValue;
+        }
+        else
+        {
+          *t = static_cast<TargetType>(v);
+        }
+      }
+    }
+  }
+
+
   template <typename PixelType>
   static void SetInternal(ImageAccessor& image,
                           int64_t constant)
@@ -319,6 +357,27 @@
       return;
     }
 
+    if (target.GetFormat() == PixelFormat_Grayscale8 &&
+        source.GetFormat() == PixelFormat_RGB24)
+    {
+      ConvertColorToGrayscale<uint8_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_Grayscale16 &&
+        source.GetFormat() == PixelFormat_RGB24)
+    {
+      ConvertColorToGrayscale<uint16_t>(target, source);
+      return;
+    }
+
+    if (target.GetFormat() == PixelFormat_SignedGrayscale16 &&
+        source.GetFormat() == PixelFormat_RGB24)
+    {
+      ConvertColorToGrayscale<int16_t>(target, source);
+      return;
+    }
+
     throw OrthancException(ErrorCode_NotImplemented);
   }
 
--- a/Core/Lua/LuaContext.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/Lua/LuaContext.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -34,6 +34,7 @@
 #include "LuaContext.h"
 
 #include <glog/logging.h>
+#include <cassert>
 
 extern "C" 
 {
@@ -78,7 +79,7 @@
       lua_pop(state, 1);
     }
 
-    LOG(INFO) << "Lua says: " << result;         
+    LOG(WARNING) << "Lua says: " << result;         
     that->log_.append(result);
     that->log_.append("\n");
 
@@ -111,8 +112,6 @@
   void LuaContext::Execute(std::string* output,
                            const std::string& command)
   {
-    boost::mutex::scoped_lock lock(mutex_);
-
     log_.clear();
     int error = (luaL_loadbuffer(lua_, command.c_str(), command.size(), "line") ||
                  lua_pcall(lua_, 0, 0, 0));
@@ -143,7 +142,6 @@
 
   bool LuaContext::IsExistingFunction(const char* name)
   {
-    boost::mutex::scoped_lock lock(mutex_);
     lua_settop(lua_, 0);
     lua_getglobal(lua_, name);
     return lua_type(lua_, -1) == LUA_TFUNCTION;
--- a/Core/Lua/LuaContext.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/Lua/LuaContext.h	Thu Jul 10 11:42:32 2014 +0200
@@ -34,8 +34,6 @@
 
 #include "LuaException.h"
 
-#include <boost/thread.hpp>
-
 extern "C" 
 {
 #include <lua.h>
@@ -43,6 +41,7 @@
 
 #include <EmbeddedResources.h>
 
+#include <boost/noncopyable.hpp>
 
 namespace Orthanc
 {
@@ -52,7 +51,6 @@
     friend class LuaFunctionCall;
 
     lua_State *lua_;
-    boost::mutex mutex_;
     std::string log_;
 
     static int PrintToLog(lua_State *L);
--- a/Core/Lua/LuaFunctionCall.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/Lua/LuaFunctionCall.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -33,6 +33,10 @@
 #include "../PrecompiledHeaders.h"
 #include "LuaFunctionCall.h"
 
+#include <cassert>
+#include <stdio.h>
+#include <boost/lexical_cast.hpp>
+#include <glog/logging.h>
 
 namespace Orthanc
 {
@@ -47,7 +51,6 @@
   LuaFunctionCall::LuaFunctionCall(LuaContext& context,
                                    const char* functionName) : 
     context_(context),
-    lock_(context.mutex_),
     isExecuted_(false)
   {
     // Clear the stack to fulfill the invariant
@@ -79,7 +82,7 @@
     lua_pushnumber(context_.lua_, value);
   }
 
-  void LuaFunctionCall::PushJSON(const Json::Value& value)
+  void LuaFunctionCall::PushJson(const Json::Value& value)
   {
     CheckAlreadyExecuted();
 
@@ -118,7 +121,7 @@
         lua_pushnumber(context_.lua_, i + 1);
 
         // Push the value of the cell
-        PushJSON(value[i]);
+        PushJson(value[i]);
 
         // Stores the pair in the table
         lua_rawset(context_.lua_, -3);
@@ -137,7 +140,7 @@
         lua_pushstring(context_.lua_, it->c_str());
 
         // Push the value of the cell
-        PushJSON(value[*it]);
+        PushJson(value[*it]);
 
         // Stores the pair in the table
         lua_rawset(context_.lua_, -3);
@@ -149,7 +152,7 @@
     }
   }
 
-  void LuaFunctionCall::Execute(int numOutputs)
+  void LuaFunctionCall::ExecuteInternal(int numOutputs)
   {
     CheckAlreadyExecuted();
 
@@ -176,13 +179,8 @@
 
   bool LuaFunctionCall::ExecutePredicate()
   {
-    Execute(1);
-        
-    if (lua_gettop(context_.lua_) == 0)
-    {
-      throw LuaException("No output was provided by the function");
-    }
-
+    ExecuteInternal(1);
+    
     if (!lua_isboolean(context_.lua_, 1))
     {
       throw LuaException("The function is not a predicate (only true/false outputs allowed)");
@@ -190,4 +188,99 @@
 
     return lua_toboolean(context_.lua_, 1) != 0;
   }
+
+
+  static void PopJson(Json::Value& result,
+                      lua_State* lua,
+                      int top)
+  {
+    if (lua_istable(lua, top))
+    {
+      Json::Value tmp = Json::objectValue;
+      bool isArray = true;
+      size_t size = 0;
+
+      // http://stackoverflow.com/a/6142700/881731
+      
+      // Push another reference to the table on top of the stack (so we know
+      // where it is, and this function can work for negative, positive and
+      // pseudo indices
+      lua_pushvalue(lua, top);
+      // stack now contains: -1 => table
+      lua_pushnil(lua);
+      // stack now contains: -1 => nil; -2 => table
+      while (lua_next(lua, -2))
+      {
+        // stack now contains: -1 => value; -2 => key; -3 => table
+        // copy the key so that lua_tostring does not modify the original
+        lua_pushvalue(lua, -2);
+        // stack now contains: -1 => key; -2 => value; -3 => key; -4 => table
+        std::string key(lua_tostring(lua, -1));
+        Json::Value v;
+        PopJson(v, lua, -2);
+
+        tmp[key] = v;
+
+        size += 1;
+        try
+        {
+          if (boost::lexical_cast<size_t>(key) != size)
+          {
+            isArray = false;
+          }
+        }
+        catch (boost::bad_lexical_cast&)
+        {
+          isArray = false;
+        }
+        
+        // pop value + copy of key, leaving original key
+        lua_pop(lua, 2);
+        // stack now contains: -1 => key; -2 => table
+      }
+      // stack now contains: -1 => table (when lua_next returns 0 it pops the key
+      // but does not push anything.)
+      // Pop table
+      lua_pop(lua, 1);
+
+      // Stack is now the same as it was on entry to this function
+
+      if (isArray)
+      {
+        result = Json::arrayValue;
+        for (size_t i = 0; i < size; i++)
+        {
+          result.append(tmp[boost::lexical_cast<std::string>(i + 1)]);
+        }
+      }
+      else
+      {
+        result = tmp;
+      }
+    }
+    else if (lua_isnumber(lua, top))
+    {
+      result = static_cast<float>(lua_tonumber(lua, top));
+    }
+    else if (lua_isstring(lua, top))
+    {
+      result = std::string(lua_tostring(lua, top));
+    }
+    else if (lua_isboolean(lua, top))
+    {
+      result = static_cast<bool>(lua_toboolean(lua, top));
+    }
+    else
+    {
+      LOG(WARNING) << "Unsupported Lua type when returning Json";
+      result = Json::nullValue;
+    }
+  }
+
+
+  void LuaFunctionCall::ExecuteToJson(Json::Value& result)
+  {
+    ExecuteInternal(1);
+    PopJson(result, context_.lua_, lua_gettop(context_.lua_));
+  }
 }
--- a/Core/Lua/LuaFunctionCall.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/Lua/LuaFunctionCall.h	Thu Jul 10 11:42:32 2014 +0200
@@ -36,18 +36,18 @@
 
 #include <json/json.h>
 
-
 namespace Orthanc
 {
   class LuaFunctionCall : public boost::noncopyable
   {
   private:
     LuaContext& context_;
-    boost::mutex::scoped_lock lock_;
     bool isExecuted_;
 
     void CheckAlreadyExecuted();
 
+    void ExecuteInternal(int numOutputs);
+
   public:
     LuaFunctionCall(LuaContext& context,
                     const char* functionName);
@@ -60,10 +60,15 @@
 
     void PushDouble(double value);
 
-    void PushJSON(const Json::Value& value);
+    void PushJson(const Json::Value& value);
 
-    void Execute(int numOutputs = 0);
+    void Execute()
+    {
+      ExecuteInternal(0);
+    }
 
     bool ExecutePredicate();
+
+    void ExecuteToJson(Json::Value& result);                    
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/MultiThreading/Semaphore.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,67 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "Semaphore.h"
+
+#include "../OrthancException.h"
+
+
+namespace Orthanc
+{
+  Semaphore::Semaphore(unsigned int count) : count_(count)
+  {
+    if (count == 0)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+  void Semaphore::Release()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    count_++;
+    condition_.notify_one(); 
+  }
+
+  void Semaphore::Acquire()
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    while (count_ == 0)
+    {
+      condition_.wait(lock);
+    }
+
+    count_++;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/MultiThreading/Semaphore.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,54 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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 <boost/noncopyable.hpp>
+#include <boost/thread.hpp>
+
+namespace Orthanc
+{
+  class Semaphore : public boost::noncopyable
+  {
+  private:
+    unsigned int count_;
+    boost::mutex mutex_;
+    boost::condition_variable condition_;
+
+  public:
+    explicit Semaphore(unsigned int count);
+
+    void Release();
+
+    void Acquire();
+  };
+}
--- a/Core/MultiThreading/SharedMessageQueue.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/MultiThreading/SharedMessageQueue.h	Thu Jul 10 11:42:32 2014 +0200
@@ -40,7 +40,7 @@
 
 namespace Orthanc
 {
-  class SharedMessageQueue
+  class SharedMessageQueue : public boost::noncopyable
   {
   private:
     typedef std::list<IDynamicObject*>  Queue;
@@ -52,8 +52,8 @@
     boost::condition_variable emptied_;
 
   public:
-    SharedMessageQueue(unsigned int maxSize = 0);
-
+    explicit SharedMessageQueue(unsigned int maxSize = 0);
+    
     ~SharedMessageQueue();
 
     // This transfers the ownership of the message
--- a/Core/OrthancException.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/OrthancException.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -121,6 +121,9 @@
       case ErrorCode_IncompatibleImageFormat:
         return "Incompatible format of the images";
 
+      case ErrorCode_SharedLibrary:
+        return "Error while using a shared library (plugin)";
+
       case ErrorCode_Custom:
       default:
         return "???";
--- a/Core/RestApi/RestApi.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/RestApi/RestApi.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -36,86 +36,89 @@
 #include <stdlib.h>   // To define "_exit()" under Windows
 #include <glog/logging.h>
 
+#include <stdio.h>
+
 namespace Orthanc
 {
-  bool RestApi::Call::ParseJsonRequestInternal(Json::Value& result,
-                                               const char* request)
+  namespace
   {
-    result.clear();
-    Json::Reader reader;
-    return reader.parse(request, result);
-  }
+    // Anonymous namespace to avoid clashes between compilation modules
+    class HttpHandlerVisitor : public RestApiHierarchy::IVisitor
+    {
+    private:
+      RestApi& api_;
+      HttpOutput& output_;
+      HttpMethod method_;
+      const HttpHandler::Arguments& headers_;
+      const HttpHandler::Arguments& getArguments_;
+      const std::string& postData_;
 
+    public:
+      HttpHandlerVisitor(RestApi& api,
+                         HttpOutput& output,
+                         HttpMethod method,
+                         const HttpHandler::Arguments& headers,
+                         const HttpHandler::Arguments& getArguments,
+                         const std::string& postData) :
+        api_(api),
+        output_(output),
+        method_(method),
+        headers_(headers),
+        getArguments_(getArguments),
+        postData_(postData)
+      {
+      }
+
+      virtual bool Visit(const RestApiHierarchy::Resource& resource,
+                         const UriComponents& uri,
+                         const HttpHandler::Arguments& components,
+                         const UriComponents& trailing)
+      {
+        if (resource.HasHandler(method_))
+        {
+          RestApiOutput output(output_);
 
-  bool RestApi::GetCall::ParseJsonRequest(Json::Value& result) const
-  {
-    result.clear();
+          switch (method_)
+          {
+            case HttpMethod_Get:
+            {
+              RestApiGetCall call(output, api_, headers_, components, trailing, uri, getArguments_);
+              resource.Handle(call);
+              return true;
+            }
+
+            case HttpMethod_Post:
+            {
+              RestApiPostCall call(output, api_, headers_, components, trailing, uri, postData_);
+              resource.Handle(call);
+              return true;
+            }
 
-    for (HttpHandler::Arguments::const_iterator 
-           it = getArguments_.begin(); it != getArguments_.end(); ++it)
-    {
-      result[it->first] = it->second;
-    }
+            case HttpMethod_Delete:
+            {
+              RestApiDeleteCall call(output, api_, headers_, components, trailing, uri);
+              resource.Handle(call);
+              return true;
+            }
 
-    return true;
+            case HttpMethod_Put:
+            {
+              RestApiPutCall call(output, api_, headers_, components, trailing, uri, postData_);
+              resource.Handle(call);
+              return true;
+            }
+
+            default:
+              return false;
+          }
+        }
+
+        return false;
+      }
+    };
   }
 
 
-  bool RestApi::IsGetAccepted(const UriComponents& uri)
-  {
-    for (GetHandlers::const_iterator it = getHandlers_.begin();
-         it != getHandlers_.end(); ++it)
-    {
-      if (it->first->Match(uri))
-      {
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-  bool RestApi::IsPutAccepted(const UriComponents& uri)
-  {
-    for (PutHandlers::const_iterator it = putHandlers_.begin();
-         it != putHandlers_.end(); ++it)
-    {
-      if (it->first->Match(uri))
-      {
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-  bool RestApi::IsPostAccepted(const UriComponents& uri)
-  {
-    for (PostHandlers::const_iterator it = postHandlers_.begin();
-         it != postHandlers_.end(); ++it)
-    {
-      if (it->first->Match(uri))
-      {
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-  bool RestApi::IsDeleteAccepted(const UriComponents& uri)
-  {
-    for (DeleteHandlers::const_iterator it = deleteHandlers_.begin();
-         it != deleteHandlers_.end(); ++it)
-    {
-      if (it->first->Match(uri))
-      {
-        return true;
-      }
-    }
-
-    return false;
-  }
 
   static void AddMethod(std::string& target,
                         const std::string& method)
@@ -126,158 +129,96 @@
       target = method;
   }
 
-  std::string  RestApi::GetAcceptedMethods(const UriComponents& uri)
+  static std::string  MethodsToString(const std::set<HttpMethod>& methods)
   {
     std::string s;
 
-    if (IsGetAccepted(uri))
+    if (methods.find(HttpMethod_Get) != methods.end())
+    {
       AddMethod(s, "GET");
+    }
 
-    if (IsPutAccepted(uri))
-      AddMethod(s, "PUT");
+    if (methods.find(HttpMethod_Post) != methods.end())
+    {
+      AddMethod(s, "POST");
+    }
 
-    if (IsPostAccepted(uri))
-      AddMethod(s, "POST");
+    if (methods.find(HttpMethod_Put) != methods.end())
+    {
+      AddMethod(s, "PUT");
+    }
 
-    if (IsDeleteAccepted(uri))
+    if (methods.find(HttpMethod_Delete) != methods.end())
+    {
       AddMethod(s, "DELETE");
+    }
 
     return s;
   }
 
-  RestApi::~RestApi()
-  {
-    for (GetHandlers::iterator it = getHandlers_.begin(); 
-         it != getHandlers_.end(); ++it)
-    {
-      delete it->first;
-    } 
 
-    for (PutHandlers::iterator it = putHandlers_.begin(); 
-         it != putHandlers_.end(); ++it)
-    {
-      delete it->first;
-    } 
 
-    for (PostHandlers::iterator it = postHandlers_.begin(); 
-         it != postHandlers_.end(); ++it)
-    {
-      delete it->first;
-    } 
-
-    for (DeleteHandlers::iterator it = deleteHandlers_.begin(); 
-         it != deleteHandlers_.end(); ++it)
-    {
-      delete it->first;
-    } 
-  }
-
-  bool RestApi::IsServedUri(const UriComponents& uri)
-  {
-    return (IsGetAccepted(uri) ||
-            IsPutAccepted(uri) ||
-            IsPostAccepted(uri) ||
-            IsDeleteAccepted(uri));
-  }
-
-  void RestApi::Handle(HttpOutput& output,
+  bool RestApi::Handle(HttpOutput& output,
                        HttpMethod method,
                        const UriComponents& uri,
                        const Arguments& headers,
                        const Arguments& getArguments,
                        const std::string& postData)
   {
-    bool ok = false;
-    RestApiOutput restOutput(output);
-    RestApiPath::Components components;
-    UriComponents trailing;
+    HttpHandlerVisitor visitor(*this, output, method, headers, getArguments, postData);
 
-    if (method == HttpMethod_Get)
-    {
-      for (GetHandlers::const_iterator it = getHandlers_.begin();
-           it != getHandlers_.end(); ++it)
-      {
-        if (it->first->Match(components, trailing, uri))
-        {
-          //LOG(INFO) << "REST GET call on: " << Toolbox::FlattenUri(uri);
-          ok = true;
-          GetCall call(restOutput, *this, headers, components, trailing, uri, getArguments);
-          it->second(call);
-        }
-      }
-    }
-    else if (method == HttpMethod_Put)
+    if (root_.LookupResource(uri, visitor))
     {
-      for (PutHandlers::const_iterator it = putHandlers_.begin();
-           it != putHandlers_.end(); ++it)
-      {
-        if (it->first->Match(components, trailing, uri))
-        {
-          //LOG(INFO) << "REST PUT call on: " << Toolbox::FlattenUri(uri);
-          ok = true;
-          PutCall call(restOutput, *this, headers, components, trailing, uri, postData);
-          it->second(call);
-        }
-      }
-    }
-    else if (method == HttpMethod_Post)
-    {
-      for (PostHandlers::const_iterator it = postHandlers_.begin();
-           it != postHandlers_.end(); ++it)
-      {
-        if (it->first->Match(components, trailing, uri))
-        {
-          //LOG(INFO) << "REST POST call on: " << Toolbox::FlattenUri(uri);
-          ok = true;
-          PostCall call(restOutput, *this, headers, components, trailing, uri, postData);
-          it->second(call);
-        }
-      }
-    }
-    else if (method == HttpMethod_Delete)
-    {
-      for (DeleteHandlers::const_iterator it = deleteHandlers_.begin();
-           it != deleteHandlers_.end(); ++it)
-      {
-        if (it->first->Match(components, trailing, uri))
-        {
-          //LOG(INFO) << "REST DELETE call on: " << Toolbox::FlattenUri(uri);
-          ok = true;
-          DeleteCall call(restOutput, *this, headers, components, trailing, uri);
-          it->second(call);
-        }
-      }
+      return true;
     }
 
-    if (!ok)
+    Json::Value directory;
+    if (root_.GetDirectory(directory, uri))
+    {
+      RestApiOutput tmp(output);
+      tmp.AnswerJson(directory);
+      return true;
+    }
+
+    std::set<HttpMethod> methods;
+    root_.GetAcceptedMethods(methods, uri);
+
+    if (methods.empty())
+    {
+      return false;  // This URI is not served by this REST API
+    }
+    else
     {
       LOG(INFO) << "REST method " << EnumerationToString(method) 
                 << " not allowed on: " << Toolbox::FlattenUri(uri);
-      output.SendMethodNotAllowedError(GetAcceptedMethods(uri));
+
+      output.SendMethodNotAllowedError(MethodsToString(methods));
+
+      return true;
     }
   }
 
   void RestApi::Register(const std::string& path,
-                         GetHandler handler)
+                         RestApiGetCall::Handler handler)
   {
-    getHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
+    root_.Register(path, handler);
   }
 
   void RestApi::Register(const std::string& path,
-                         PutHandler handler)
+                         RestApiPutCall::Handler handler)
   {
-    putHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
+    root_.Register(path, handler);
   }
 
   void RestApi::Register(const std::string& path,
-                         PostHandler handler)
+                         RestApiPostCall::Handler handler)
   {
-    postHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
+    root_.Register(path, handler);
   }
 
   void RestApi::Register(const std::string& path,
-                         DeleteHandler handler)
+                         RestApiDeleteCall::Handler handler)
   {
-    deleteHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
+    root_.Register(path, handler);
   }
 }
--- a/Core/RestApi/RestApi.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/RestApi/RestApi.h	Thu Jul 10 11:42:32 2014 +0200
@@ -32,9 +32,7 @@
 
 #pragma once
 
-#include "../HttpServer/HttpHandler.h"
-#include "RestApiPath.h"
-#include "RestApiOutput.h"
+#include "RestApiHierarchy.h"
 
 #include <list>
 
@@ -42,238 +40,11 @@
 {
   class RestApi : public HttpHandler
   {
-  public:
-    class Call
-    {
-      friend class RestApi;
-
-    private:
-      RestApiOutput& output_;
-      RestApi& context_;
-      const HttpHandler::Arguments& httpHeaders_;
-      const RestApiPath::Components& uriComponents_;
-      const UriComponents& trailing_;
-      const UriComponents& fullUri_;
-
-      Call(RestApiOutput& output,
-           RestApi& context,
-           const HttpHandler::Arguments& httpHeaders,
-           const RestApiPath::Components& uriComponents,
-           const UriComponents& trailing,
-           const UriComponents& fullUri) :
-        output_(output),
-        context_(context),
-        httpHeaders_(httpHeaders),
-        uriComponents_(uriComponents),
-        trailing_(trailing),
-        fullUri_(fullUri)
-      {
-      }
-
-    protected:
-      static bool ParseJsonRequestInternal(Json::Value& result,
-                                           const char* request);
-
-    public:
-      RestApiOutput& GetOutput()
-      {
-        return output_;
-      }
-
-      RestApi& GetContext()
-      {
-        return context_;
-      }
-    
-      const UriComponents& GetFullUri() const
-      {
-        return fullUri_;
-      }
-    
-      const UriComponents& GetTrailingUri() const
-      {
-        return trailing_;
-      }
-
-      std::string GetUriComponent(const std::string& name,
-                                  const std::string& defaultValue) const
-      {
-        return HttpHandler::GetArgument(uriComponents_, name, defaultValue);
-      }
-
-      std::string GetHttpHeader(const std::string& name,
-                                const std::string& defaultValue) const
-      {
-        return HttpHandler::GetArgument(httpHeaders_, name, defaultValue);
-      }
-
-      const HttpHandler::Arguments& GetHttpHeaders() const
-      {
-        return httpHeaders_;
-      }
-
-      void ParseCookies(HttpHandler::Arguments& result) const
-      {
-        HttpHandler::ParseCookies(result, httpHeaders_);
-      }
-
-      virtual bool ParseJsonRequest(Json::Value& result) const = 0;
-    };
-
- 
-    class GetCall : public Call
-    {
-      friend class RestApi;
-
-    private:
-      const HttpHandler::Arguments& getArguments_;
-
-    public:
-      GetCall(RestApiOutput& output,
-              RestApi& context,
-              const HttpHandler::Arguments& httpHeaders,
-              const RestApiPath::Components& uriComponents,
-              const UriComponents& trailing,
-              const UriComponents& fullUri,
-              const HttpHandler::Arguments& getArguments) :
-        Call(output, context, httpHeaders, uriComponents, trailing, fullUri),
-        getArguments_(getArguments)
-      {
-      }
-
-      std::string GetArgument(const std::string& name,
-                              const std::string& defaultValue) const
-      {
-        return HttpHandler::GetArgument(getArguments_, name, defaultValue);
-      }
-
-      bool HasArgument(const std::string& name) const
-      {
-        return getArguments_.find(name) != getArguments_.end();
-      }
-
-      virtual bool ParseJsonRequest(Json::Value& result) const;
-    };
-
-
-    class PutCall : public Call
-    {
-      friend class RestApi;
-
-    private:
-      const std::string& data_;
-
-    public:
-      PutCall(RestApiOutput& output,
-              RestApi& context,
-              const HttpHandler::Arguments& httpHeaders,
-              const RestApiPath::Components& uriComponents,
-              const UriComponents& trailing,
-              const UriComponents& fullUri,
-              const std::string& data) :
-        Call(output, context, httpHeaders, uriComponents, trailing, fullUri),
-        data_(data)
-      {
-      }
-
-      const std::string& GetPutBody() const
-      {
-        return data_;
-      }
-
-      virtual bool ParseJsonRequest(Json::Value& result) const
-      {
-        return ParseJsonRequestInternal(result, GetPutBody().c_str());
-      }      
-    };
-
-    class PostCall : public Call
-    {
-      friend class RestApi;
-
-    private:
-      const std::string& data_;
-
-    public:
-      PostCall(RestApiOutput& output,
-               RestApi& context,
-               const HttpHandler::Arguments& httpHeaders,
-               const RestApiPath::Components& uriComponents,
-               const UriComponents& trailing,
-               const UriComponents& fullUri,
-               const std::string& data) :
-        Call(output, context, httpHeaders, uriComponents, trailing, fullUri),
-        data_(data)
-      {
-      }
-
-      const std::string& GetPostBody() const
-      {
-        return data_;
-      }
-
-      virtual bool ParseJsonRequest(Json::Value& result) const
-      {
-        return ParseJsonRequestInternal(result, GetPostBody().c_str());
-      }      
-    };
-
-    class DeleteCall : public Call
-    {
-    public:
-      DeleteCall(RestApiOutput& output,
-                 RestApi& context,
-                 const HttpHandler::Arguments& httpHeaders,
-                 const RestApiPath::Components& uriComponents,
-                 const UriComponents& trailing,
-                 const UriComponents& fullUri) :
-        Call(output, context, httpHeaders, uriComponents, trailing, fullUri)
-      {
-      }
-
-      virtual bool ParseJsonRequest(Json::Value& result) const
-      {
-        result.clear();
-        return true;
-      }
-    };
-
-    typedef void (*GetHandler) (GetCall& call);
-    
-    typedef void (*DeleteHandler) (DeleteCall& call);
-    
-    typedef void (*PutHandler) (PutCall& call);
-    
-    typedef void (*PostHandler) (PostCall& call);
-    
   private:
-    typedef std::list< std::pair<RestApiPath*, GetHandler> > GetHandlers;
-    typedef std::list< std::pair<RestApiPath*, PutHandler> > PutHandlers;
-    typedef std::list< std::pair<RestApiPath*, PostHandler> > PostHandlers;
-    typedef std::list< std::pair<RestApiPath*, DeleteHandler> > DeleteHandlers;
-
-    GetHandlers  getHandlers_;
-    PutHandlers  putHandlers_;
-    PostHandlers  postHandlers_;
-    DeleteHandlers  deleteHandlers_;
-
-    bool IsGetAccepted(const UriComponents& uri);
-    bool IsPutAccepted(const UriComponents& uri);
-    bool IsPostAccepted(const UriComponents& uri);
-    bool IsDeleteAccepted(const UriComponents& uri);
-
-    std::string  GetAcceptedMethods(const UriComponents& uri);
+    RestApiHierarchy root_;
 
   public:
-    RestApi()
-    {
-    }
-
-    ~RestApi();
-
-    virtual bool IsServedUri(const UriComponents& uri);
-
-    virtual void Handle(HttpOutput& output,
+    virtual bool Handle(HttpOutput& output,
                         HttpMethod method,
                         const UriComponents& uri,
                         const Arguments& headers,
@@ -281,15 +52,15 @@
                         const std::string& postData);
 
     void Register(const std::string& path,
-                  GetHandler handler);
+                  RestApiGetCall::Handler handler);
 
     void Register(const std::string& path,
-                  PutHandler handler);
+                  RestApiPutCall::Handler handler);
 
     void Register(const std::string& path,
-                  PostHandler handler);
+                  RestApiPostCall::Handler handler);
 
     void Register(const std::string& path,
-                  DeleteHandler handler);
+                  RestApiDeleteCall::Handler handler);
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/RestApi/RestApiCall.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,44 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "RestApiCall.h"
+
+namespace Orthanc
+{
+  bool RestApiCall::ParseJsonRequestInternal(Json::Value& result,
+                                             const char* request)
+  {
+    result.clear();
+    Json::Reader reader;
+    return reader.parse(request, result);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/RestApi/RestApiCall.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,119 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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 "../HttpServer/HttpHandler.h"
+#include "RestApiPath.h"
+#include "RestApiOutput.h"
+
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class RestApi;
+
+  class RestApiCall : public boost::noncopyable
+  {
+  private:
+    RestApiOutput& output_;
+    RestApi& context_;
+    const HttpHandler::Arguments& httpHeaders_;
+    const HttpHandler::Arguments& uriComponents_;
+    const UriComponents& trailing_;
+    const UriComponents& fullUri_;
+
+  protected:
+    static bool ParseJsonRequestInternal(Json::Value& result,
+                                         const char* request);
+
+  public:
+    RestApiCall(RestApiOutput& output,
+                RestApi& context,
+                const HttpHandler::Arguments& httpHeaders,
+                const HttpHandler::Arguments& uriComponents,
+                const UriComponents& trailing,
+                const UriComponents& fullUri) :
+      output_(output),
+      context_(context),
+      httpHeaders_(httpHeaders),
+      uriComponents_(uriComponents),
+      trailing_(trailing),
+      fullUri_(fullUri)
+    {
+    }
+
+    RestApiOutput& GetOutput()
+    {
+      return output_;
+    }
+
+    RestApi& GetContext()
+    {
+      return context_;
+    }
+    
+    const UriComponents& GetFullUri() const
+    {
+      return fullUri_;
+    }
+    
+    const UriComponents& GetTrailingUri() const
+    {
+      return trailing_;
+    }
+
+    std::string GetUriComponent(const std::string& name,
+                                const std::string& defaultValue) const
+    {
+      return HttpHandler::GetArgument(uriComponents_, name, defaultValue);
+    }
+
+    std::string GetHttpHeader(const std::string& name,
+                              const std::string& defaultValue) const
+    {
+      return HttpHandler::GetArgument(httpHeaders_, name, defaultValue);
+    }
+
+    const HttpHandler::Arguments& GetHttpHeaders() const
+    {
+      return httpHeaders_;
+    }
+
+    void ParseCookies(HttpHandler::Arguments& result) const
+    {
+      HttpHandler::ParseCookies(result, httpHeaders_);
+    }
+
+    virtual bool ParseJsonRequest(Json::Value& result) const = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/RestApi/RestApiDeleteCall.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,60 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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 "RestApiCall.h"
+
+namespace Orthanc
+{
+  class RestApiDeleteCall : public RestApiCall
+  {
+  public:
+    typedef void (*Handler) (RestApiDeleteCall& call);
+    
+    RestApiDeleteCall(RestApiOutput& output,
+                      RestApi& context,
+                      const HttpHandler::Arguments& httpHeaders,
+                      const HttpHandler::Arguments& uriComponents,
+                      const UriComponents& trailing,
+                      const UriComponents& fullUri) :
+      RestApiCall(output, context, httpHeaders, uriComponents, trailing, fullUri)
+    {
+    }
+
+    virtual bool ParseJsonRequest(Json::Value& result) const
+    {
+      result.clear();
+      return true;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/RestApi/RestApiGetCall.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,49 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "RestApiGetCall.h"
+
+namespace Orthanc
+{
+  bool RestApiGetCall::ParseJsonRequest(Json::Value& result) const
+  {
+    result.clear();
+
+    for (HttpHandler::Arguments::const_iterator 
+           it = getArguments_.begin(); it != getArguments_.end(); ++it)
+    {
+      result[it->first] = it->second;
+    }
+
+    return true;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/RestApi/RestApiGetCall.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,72 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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 "RestApiCall.h"
+
+namespace Orthanc
+{
+  class RestApiGetCall : public RestApiCall
+  {
+  private:
+    const HttpHandler::Arguments& getArguments_;
+
+  public:
+    typedef void (*Handler) (RestApiGetCall& call);   
+
+    RestApiGetCall(RestApiOutput& output,
+                   RestApi& context,
+                   const HttpHandler::Arguments& httpHeaders,
+                   const HttpHandler::Arguments& uriComponents,
+                   const UriComponents& trailing,
+                   const UriComponents& fullUri,
+                   const HttpHandler::Arguments& getArguments) :
+      RestApiCall(output, context, httpHeaders, uriComponents, trailing, fullUri),
+      getArguments_(getArguments)
+    {
+    }
+
+    std::string GetArgument(const std::string& name,
+                            const std::string& defaultValue) const
+    {
+      return HttpHandler::GetArgument(getArguments_, name, defaultValue);
+    }
+
+    bool HasArgument(const std::string& name) const
+    {
+      return getArguments_.find(name) != getArguments_.end();
+    }
+
+    virtual bool ParseJsonRequest(Json::Value& result) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/RestApi/RestApiHierarchy.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,473 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "RestApiHierarchy.h"
+
+#include "../OrthancException.h"
+
+#include <cassert>
+
+namespace Orthanc
+{
+  RestApiHierarchy::Resource::Resource() : 
+    getHandler_(NULL), 
+    postHandler_(NULL),
+    putHandler_(NULL), 
+    deleteHandler_(NULL)
+  {
+  }
+
+
+  bool RestApiHierarchy::Resource::HasHandler(HttpMethod method) const
+  {
+    switch (method)
+    {
+      case HttpMethod_Get:
+        return getHandler_ != NULL;
+
+      case HttpMethod_Post:
+        return postHandler_ != NULL;
+
+      case HttpMethod_Put:
+        return putHandler_ != NULL;
+
+      case HttpMethod_Delete:
+        return deleteHandler_ != NULL;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  bool RestApiHierarchy::Resource::IsEmpty() const
+  {
+    return (getHandler_ == NULL &&
+            postHandler_ == NULL &&
+            putHandler_ == NULL &&
+            deleteHandler_ == NULL);
+  }
+
+
+  RestApiHierarchy& RestApiHierarchy::AddChild(Children& children,
+                                               const std::string& name)
+  {
+    Children::iterator it = children.find(name);
+
+    if (it == children.end())
+    {
+      // Create new child
+      RestApiHierarchy *child = new RestApiHierarchy;
+      children[name] = child;
+      return *child;
+    }
+    else
+    {
+      return *it->second;
+    }
+  }
+
+
+
+  bool RestApiHierarchy::Resource::Handle(RestApiGetCall& call) const
+  {
+    if (getHandler_ != NULL)
+    {
+      getHandler_(call);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool RestApiHierarchy::Resource::Handle(RestApiPutCall& call) const
+  {
+    if (putHandler_ != NULL)
+    {
+      putHandler_(call);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool RestApiHierarchy::Resource::Handle(RestApiPostCall& call) const
+  {
+    if (postHandler_ != NULL)
+    {
+      postHandler_(call);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+  bool RestApiHierarchy::Resource::Handle(RestApiDeleteCall& call) const
+  {
+    if (deleteHandler_ != NULL)
+    {
+      deleteHandler_(call);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+
+  void RestApiHierarchy::DeleteChildren(Children& children)
+  {
+    for (Children::iterator it = children.begin();
+         it != children.end(); it++)
+    {
+      delete it->second;
+    }
+  }
+
+
+  template <typename Handler>
+  void RestApiHierarchy::RegisterInternal(const RestApiPath& path,
+                                          Handler handler,
+                                          size_t level)
+  {
+    if (path.GetLevelCount() == level)
+    {
+      if (path.IsUniversalTrailing())
+      {
+        universalHandlers_.Register(handler);
+      }
+      else
+      {
+        handlers_.Register(handler);
+      }
+    }
+    else
+    {
+      RestApiHierarchy* child;
+      if (path.IsWildcardLevel(level))
+      {
+        child = &AddChild(wildcardChildren_, path.GetWildcardName(level));
+      }
+      else
+      {
+        child = &AddChild(children_, path.GetLevelName(level));
+      }
+
+      child->RegisterInternal(path, handler, level + 1);
+    }
+  }
+
+
+  bool RestApiHierarchy::LookupResource(HttpHandler::Arguments& components,
+                                       const UriComponents& uri,
+                                       IVisitor& visitor,
+                                       size_t level)
+  {
+    if (uri.size() != 0 &&
+        level > uri.size())
+    {
+      return false;
+    }
+
+    UriComponents trailing;
+
+    // Look for an exact match on the resource of interest
+      if (uri.size() == 0 ||
+          level == uri.size())
+    {
+      if (!handlers_.IsEmpty() &&
+          visitor.Visit(handlers_, uri, components, trailing))
+      {
+        return true;
+      }
+    }
+
+
+    if (level < uri.size())  // A recursive call is possible
+    {
+      // Try and go down in the hierarchy, using an exact match for the child
+      Children::const_iterator child = children_.find(uri[level]);
+      if (child != children_.end())
+      {
+        if (child->second->LookupResource(components, uri, visitor, level + 1))
+        {
+          return true;
+        }
+      }
+
+      // Try and go down in the hierarchy, using wildcard rules for children
+      for (child = wildcardChildren_.begin();
+           child != wildcardChildren_.end(); child++)
+      {
+        HttpHandler::Arguments subComponents = components;
+        subComponents[child->first] = uri[level];
+
+        if (child->second->LookupResource(subComponents, uri, visitor, level + 1))
+        {
+          return true;
+        }        
+      }
+    }
+
+
+    // As a last resort, call the universal handlers, if any
+    if (!universalHandlers_.IsEmpty())
+    {
+      trailing.resize(uri.size() - level);
+      size_t pos = 0;
+      for (size_t i = level; i < uri.size(); i++, pos++)
+      {
+        trailing[pos] = uri[i];
+      }
+
+      assert(pos == trailing.size());
+
+      if (visitor.Visit(universalHandlers_, uri, components, trailing))
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+
+  bool RestApiHierarchy::CanGenerateDirectory() const
+  {
+    return (!handlers_.HasHandler(HttpMethod_Get) && 
+            universalHandlers_.IsEmpty() &&
+            wildcardChildren_.size() == 0);
+  }
+
+
+  bool RestApiHierarchy::GetDirectory(Json::Value& result,
+                                      const UriComponents& uri,
+                                      size_t level)
+  {
+    if (uri.size() == level)
+    {
+      if (CanGenerateDirectory())
+      {
+        result = Json::arrayValue;
+
+        for (Children::const_iterator it = children_.begin();
+             it != children_.end(); it++)
+        {
+          result.append(it->first);
+        }
+
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+
+    Children::const_iterator child = children_.find(uri[level]);
+    if (child != children_.end())
+    {
+      if (child->second->GetDirectory(result, uri, level + 1))
+      {
+        return true;
+      }
+    }
+
+    for (child = wildcardChildren_.begin(); 
+         child != wildcardChildren_.end(); child++)
+    {
+      if (child->second->GetDirectory(result, uri, level + 1))
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+                       
+
+  RestApiHierarchy::~RestApiHierarchy()
+  {
+    DeleteChildren(children_);
+    DeleteChildren(wildcardChildren_);
+  }
+
+  void RestApiHierarchy::Register(const std::string& uri,
+                                  RestApiGetCall::Handler handler)
+  {
+    RestApiPath path(uri);
+    RegisterInternal(path, handler, 0);
+  }
+
+  void RestApiHierarchy::Register(const std::string& uri,
+                                  RestApiPutCall::Handler handler)
+  {
+    RestApiPath path(uri);
+    RegisterInternal(path, handler, 0);
+  }
+
+  void RestApiHierarchy::Register(const std::string& uri,
+                                  RestApiPostCall::Handler handler)
+  {
+    RestApiPath path(uri);
+    RegisterInternal(path, handler, 0);
+  }
+
+  void RestApiHierarchy::Register(const std::string& uri,
+                                  RestApiDeleteCall::Handler handler)
+  {
+    RestApiPath path(uri);
+    RegisterInternal(path, handler, 0);
+  }
+
+  void RestApiHierarchy::CreateSiteMap(Json::Value& target) const
+  {
+    target = Json::objectValue;
+
+    /*std::string s = " ";
+      if (handlers_.HasHandler(HttpMethod_Get))
+      {
+      s += "GET ";
+      }
+
+      if (handlers_.HasHandler(HttpMethod_Post))
+      {
+      s += "POST ";
+      }
+
+      if (handlers_.HasHandler(HttpMethod_Put))
+      {
+      s += "PUT ";
+      }
+
+      if (handlers_.HasHandler(HttpMethod_Delete))
+      {
+      s += "DELETE ";
+      }
+
+      target = s;*/
+      
+    for (Children::const_iterator it = children_.begin();
+         it != children_.end(); it++)
+    {
+      it->second->CreateSiteMap(target[it->first]);
+    }
+      
+    for (Children::const_iterator it = wildcardChildren_.begin();
+         it != wildcardChildren_.end(); it++)
+    {
+      it->second->CreateSiteMap(target["<" + it->first + ">"]);
+    }
+  }
+
+
+  bool RestApiHierarchy::LookupResource(const UriComponents& uri,
+                                        IVisitor& visitor)
+  {
+    HttpHandler::Arguments components;
+    return LookupResource(components, uri, visitor, 0);
+  }    
+
+
+
+  namespace
+  {
+    // Anonymous namespace to avoid clashes between compilation modules
+
+    class AcceptedMethodsVisitor : public RestApiHierarchy::IVisitor
+    {
+    private:
+      std::set<HttpMethod>& methods_;
+
+    public:
+      AcceptedMethodsVisitor(std::set<HttpMethod>& methods) : methods_(methods)
+      {
+      }
+
+      virtual bool Visit(const RestApiHierarchy::Resource& resource,
+                         const UriComponents& uri,
+                         const HttpHandler::Arguments& components,
+                         const UriComponents& trailing)
+      {
+        if (trailing.size() == 0)  // Ignore universal handlers
+        {
+          if (resource.HasHandler(HttpMethod_Get))
+          {
+            methods_.insert(HttpMethod_Get);
+          }
+
+          if (resource.HasHandler(HttpMethod_Post))
+          {
+            methods_.insert(HttpMethod_Post);
+          }
+
+          if (resource.HasHandler(HttpMethod_Put))
+          {
+            methods_.insert(HttpMethod_Put);
+          }
+
+          if (resource.HasHandler(HttpMethod_Delete))
+          {
+            methods_.insert(HttpMethod_Delete);
+          }
+        }
+
+        return false;  // Continue to check all the possible ways to access this URI
+      }
+    };
+  }
+
+  void RestApiHierarchy::GetAcceptedMethods(std::set<HttpMethod>& methods,
+                                            const UriComponents& uri)
+  {
+    HttpHandler::Arguments components;
+    AcceptedMethodsVisitor visitor(methods);
+    LookupResource(components, uri, visitor, 0);
+
+    Json::Value d;
+    if (GetDirectory(d, uri))
+    {
+      methods.insert(HttpMethod_Get);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/RestApi/RestApiHierarchy.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,164 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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 "RestApiGetCall.h"
+#include "RestApiPostCall.h"
+#include "RestApiPutCall.h"
+#include "RestApiDeleteCall.h"
+
+#include <set>
+
+namespace Orthanc
+{
+  class RestApiHierarchy : public boost::noncopyable
+  {
+  public:
+    class Resource : public boost::noncopyable
+    {
+    private:
+      RestApiGetCall::Handler     getHandler_;
+      RestApiPostCall::Handler    postHandler_;
+      RestApiPutCall::Handler     putHandler_;
+      RestApiDeleteCall::Handler  deleteHandler_;
+
+    public:
+      Resource();
+
+      bool HasHandler(HttpMethod method) const;
+
+      void Register(RestApiGetCall::Handler handler)
+      {
+        getHandler_ = handler;
+      }
+
+      void Register(RestApiPutCall::Handler handler)
+      {
+        putHandler_ = handler;
+      }
+
+      void Register(RestApiPostCall::Handler handler)
+      {
+        postHandler_ = handler;
+      }
+
+      void Register(RestApiDeleteCall::Handler handler)
+      {
+        deleteHandler_ = handler;
+      }
+
+      bool IsEmpty() const;
+
+      bool Handle(RestApiGetCall& call) const;
+
+      bool Handle(RestApiPutCall& call) const;
+
+      bool Handle(RestApiPostCall& call) const;
+
+      bool Handle(RestApiDeleteCall& call) const;
+    };
+
+
+    class IVisitor : public boost::noncopyable
+    {
+    public:
+      virtual ~IVisitor()
+      {
+      }
+
+      virtual bool Visit(const Resource& resource,
+                         const UriComponents& uri,
+                         const HttpHandler::Arguments& components,
+                         const UriComponents& trailing) = 0;
+    };
+
+
+  private:
+    typedef std::map<std::string, RestApiHierarchy*>  Children;
+
+    Resource  handlers_;
+    Children  children_;
+    Children  wildcardChildren_;
+    Resource  universalHandlers_;
+
+    static RestApiHierarchy& AddChild(Children& children,
+                                      const std::string& name);
+
+    static void DeleteChildren(Children& children);
+
+    template <typename Handler>
+    void RegisterInternal(const RestApiPath& path,
+                          Handler handler,
+                          size_t level);
+
+    bool CanGenerateDirectory() const;
+
+    bool LookupResource(HttpHandler::Arguments& components,
+                        const UriComponents& uri,
+                        IVisitor& visitor,
+                        size_t level);
+
+    bool GetDirectory(Json::Value& result,
+                      const UriComponents& uri,
+                      size_t level);
+
+  public:
+    ~RestApiHierarchy();
+
+    void Register(const std::string& uri,
+                  RestApiGetCall::Handler handler);
+
+    void Register(const std::string& uri,
+                  RestApiPutCall::Handler handler);
+
+    void Register(const std::string& uri,
+                  RestApiPostCall::Handler handler);
+
+    void Register(const std::string& uri,
+                  RestApiDeleteCall::Handler handler);
+
+    void CreateSiteMap(Json::Value& target) const;
+
+    bool GetDirectory(Json::Value& result,
+                      const UriComponents& uri)
+    {
+      return GetDirectory(result, uri, 0);
+    }
+
+    bool LookupResource(const UriComponents& uri,
+                        IVisitor& visitor);
+
+    void GetAcceptedMethods(std::set<HttpMethod>& methods,
+                            const UriComponents& uri);
+  };
+}
--- a/Core/RestApi/RestApiPath.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/RestApi/RestApiPath.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -33,6 +33,8 @@
 #include "../PrecompiledHeaders.h"
 #include "RestApiPath.h"
 
+#include "../OrthancException.h"
+
 #include <cassert>
 
 namespace Orthanc
@@ -76,7 +78,7 @@
     }
   }
 
-  bool RestApiPath::Match(Components& components,
+  bool RestApiPath::Match(HttpHandler::Arguments& components,
                           UriComponents& trailing,
                           const std::string& uriRaw) const
   {
@@ -85,10 +87,12 @@
     return Match(components, trailing, uri);
   }
 
-  bool RestApiPath::Match(Components& components,
+  bool RestApiPath::Match(HttpHandler::Arguments& components,
                           UriComponents& trailing,
                           const UriComponents& uri) const
   {
+    assert(uri_.size() == components_.size());
+
     if (uri.size() < uri_.size())
     {
       return false;
@@ -131,8 +135,46 @@
 
   bool RestApiPath::Match(const UriComponents& uri) const
   {
-    Components components;
+    HttpHandler::Arguments components;
     UriComponents trailing;
     return Match(components, trailing, uri);
   }
+
+
+  bool RestApiPath::IsWildcardLevel(size_t level) const
+  {
+    assert(uri_.size() == components_.size());
+
+    if (level >= uri_.size())
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    return uri_[level].length() == 0;
+  }
+
+  const std::string& RestApiPath::GetWildcardName(size_t level) const
+  {
+    assert(uri_.size() == components_.size());
+
+    if (!IsWildcardLevel(level))
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+
+    return components_[level];
+  }
+
+  const std::string& RestApiPath::GetLevelName(size_t level) const
+  {
+    assert(uri_.size() == components_.size());
+
+    if (IsWildcardLevel(level))
+    {
+      throw OrthancException(ErrorCode_BadParameterType);
+    }
+
+    return uri_[level];
+  }
 }
+
--- a/Core/RestApi/RestApiPath.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/RestApi/RestApiPath.h	Thu Jul 10 11:42:32 2014 +0200
@@ -33,6 +33,8 @@
 #pragma once
 
 #include "../Toolbox.h"
+#include "../HttpServer/HttpHandler.h"
+
 #include <map>
 
 namespace Orthanc
@@ -45,19 +47,34 @@
     std::vector<std::string> components_;
 
   public:
-    typedef std::map<std::string, std::string> Components;
-
     RestApiPath(const std::string& uri);
 
     // This version is slower
-    bool Match(Components& components,
+    bool Match(HttpHandler::Arguments& components,
                UriComponents& trailing,
                const std::string& uriRaw) const;
 
-    bool Match(Components& components,
+    bool Match(HttpHandler::Arguments& components,
                UriComponents& trailing,
                const UriComponents& uri) const;
 
     bool Match(const UriComponents& uri) const;
+
+    size_t GetLevelCount() const
+    {
+      return uri_.size();
+    }
+
+    bool IsWildcardLevel(size_t level) const;
+
+    bool IsUniversalTrailing() const
+    {
+      return hasTrailing_;
+    }
+
+    const std::string& GetWildcardName(size_t level) const;
+
+    const std::string& GetLevelName(size_t level) const;
+
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/RestApi/RestApiPostCall.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,69 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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 "RestApiCall.h"
+
+namespace Orthanc
+{
+  class RestApiPostCall : public RestApiCall
+  {
+  private:
+    const std::string& data_;
+
+  public:
+    typedef void (*Handler) (RestApiPostCall& call);
+    
+    RestApiPostCall(RestApiOutput& output,
+                    RestApi& context,
+                    const HttpHandler::Arguments& httpHeaders,
+                    const HttpHandler::Arguments& uriComponents,
+                    const UriComponents& trailing,
+                    const UriComponents& fullUri,
+                    const std::string& data) :
+      RestApiCall(output, context, httpHeaders, uriComponents, trailing, fullUri),
+      data_(data)
+    {
+    }
+
+    const std::string& GetPostBody() const
+    {
+      return data_;
+    }
+
+    virtual bool ParseJsonRequest(Json::Value& result) const
+    {
+      return ParseJsonRequestInternal(result, GetPostBody().c_str());
+    }      
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/RestApi/RestApiPutCall.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,69 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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 "RestApiCall.h"
+
+namespace Orthanc
+{
+  class RestApiPutCall : public RestApiCall
+  {
+  private:
+    const std::string& data_;
+
+  public:
+    typedef void (*Handler) (RestApiPutCall& call);
+    
+    RestApiPutCall(RestApiOutput& output,
+                   RestApi& context,
+                   const HttpHandler::Arguments& httpHeaders,
+                   const HttpHandler::Arguments& uriComponents,
+                   const UriComponents& trailing,
+                   const UriComponents& fullUri,
+                   const std::string& data) :
+      RestApiCall(output, context, httpHeaders, uriComponents, trailing, fullUri),
+      data_(data)
+    {
+    }
+
+    const std::string& GetPutBody() const
+    {
+      return data_;
+    }
+
+    virtual bool ParseJsonRequest(Json::Value& result) const
+    {
+      return ParseJsonRequestInternal(result, GetPutBody().c_str());
+    }      
+  };
+}
--- a/Core/Toolbox.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/Toolbox.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -66,8 +66,8 @@
 
 #include <boost/locale.hpp>
 
-#include "../Resources/md5/md5.h"
-#include "../Resources/base64/base64.h"
+#include "../Resources/ThirdParty/md5/md5.h"
+#include "../Resources/ThirdParty/base64/base64.h"
 
 
 #ifdef _MSC_VER
@@ -284,6 +284,28 @@
   }
 
 
+  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)
   {
@@ -495,11 +517,30 @@
 
 
   std::string Toolbox::ConvertToUtf8(const std::string& source,
-                                     const char* fromEncoding)
+                                     const Encoding sourceEncoding)
   {
+    const char* encoding;
+
+    switch (sourceEncoding)
+    {
+      case Encoding_Utf8:
+        // Already in UTF-8: No conversion is required
+        return source;
+
+      case Encoding_Ascii:
+        return ConvertToAscii(source);;
+
+      case Encoding_Latin1:
+        encoding = "ISO-8859-1";
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+
     try
     {
-      return boost::locale::conv::to_utf<char>(source, fromEncoding);
+      return boost::locale::conv::to_utf<char>(source, encoding);
     }
     catch (std::runtime_error&)
     {
@@ -776,5 +817,12 @@
       }
     }
   }
+
+
+  bool Toolbox::IsExistingFile(const std::string& path)
+  {
+    return boost::filesystem::exists(path);
+  }
+
 }
 
--- a/Core/Toolbox.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/Core/Toolbox.h	Thu Jul 10 11:42:32 2014 +0200
@@ -73,6 +73,10 @@
     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);
 
@@ -106,7 +110,7 @@
     std::string GetDirectoryOfExecutable();
 
     std::string ConvertToUtf8(const std::string& source,
-                              const char* fromEncoding);
+                              const Encoding sourceEncoding);
 
     std::string ConvertToAscii(const std::string& source);
 
@@ -130,5 +134,7 @@
                              const std::string& source);
 
     void CreateDirectory(const std::string& path);
+
+    bool IsExistingFile(const std::string& path);
   }
 }
--- a/DarwinCompilation.txt	Wed Jun 25 11:56:48 2014 +0200
+++ b/DarwinCompilation.txt	Thu Jul 10 11:42:32 2014 +0200
@@ -28,7 +28,7 @@
 ----------------------------
 
 # cd ~/OrthancBuild
-# cmake -GXcode -DCMAKE_OSX_DEPLOYMENT_TARGET=10.8 -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON ..
+# cmake -GXcode -DCMAKE_OSX_DEPLOYMENT_TARGET=10.8 -DSTATIC_BUILD=ON -DSTANDALONE_BUILD=ON ~/Orthanc
 
 NB: Adapt the value of "CMAKE_OSX_DEPLOYMENT_TARGET" with respect to
 your version of XCode.
--- a/NEWS	Wed Jun 25 11:56:48 2014 +0200
+++ b/NEWS	Thu Jul 10 11:42:32 2014 +0200
@@ -1,9 +1,22 @@
 Pending changes in the mainline
 ===============================
 
+Major changes
+-------------
+
+* Routing images with Lua scripts
+* Introduction of the Orthanc Plugin SDK
 * Official support of OS X (Darwin)
+
+Minor changes
+-------------
+
+* Extraction of tags for the patient/study/series/instance DICOM modules
+* Extraction of the tags shared by all the instances of a patient/study/series
 * Options to limit the number of results for an incoming C-FIND query
 * Support of kFreeBSD
+* Several code refactorings
+* Fix OrthancCppClient::GetVoxelSizeZ()
 
 
 Version 0.7.6 (2014/06/11)
--- a/OrthancCppClient/Series.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancCppClient/Series.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -53,11 +53,12 @@
         /**
          * Compute the slice normal from Image Orientation Patient.
          * http://nipy.sourceforge.net/nibabel/dicom/dicom_orientation.html#dicom-z-from-slice
+         * http://dicomiseasy.blogspot.be/2013/06/getting-oriented-using-image-plane.html
          * http://www.itk.org/pipermail/insight-users/2003-September/004762.html
          **/
 
         std::vector<float> cosines;
-        someSlice.SplitVectorOfFloats(cosines, "ImageOrientationPatient");
+        someSlice.SplitVectorOfFloats(cosines, "ImageOrientationPatient");  // 0020-0037
 
         if (cosines.size() != 6)
         {
@@ -76,7 +77,7 @@
       float ComputeSliceLocation(Instance& instance) const
       {
         std::vector<float> ipp;
-        instance.SplitVectorOfFloats(ipp, "ImagePositionPatient");
+        instance.SplitVectorOfFloats(ipp, "ImagePositionPatient");  // 0020-0032
         if (ipp.size() != 3)
         {
           throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat);
@@ -129,7 +130,7 @@
 
           if (instance_.GetPixelFormat() == format_)
           {
-            memcpy(p, instance_.GetBuffer(y), 2 * instance_.GetWidth());
+            memcpy(p, instance_.GetBuffer(y), GetBytesPerPixel(instance_.GetPixelFormat()) * instance_.GetWidth());
           }
           else if (instance_.GetPixelFormat() == PixelFormat_Grayscale8 &&
                    format_ == PixelFormat_RGB24)
@@ -212,32 +213,77 @@
     {
       if (GetInstanceCount() == 0)
       {
+        // Empty image, use some default value (should never happen)
+        voxelSizeX_ = 1;
+        voxelSizeY_ = 1;
+        voxelSizeZ_ = 1;
+        sliceThickness_ = 1;
+
         return true;
       }
 
-      Instance& i1 = GetInstance(0);
+      // Choose a reference slice
+      Instance& reference = GetInstance(0);
 
+      // Check that all the child instances share the same 3D parameters
       for (unsigned int i = 0; i < GetInstanceCount(); i++)
       {
         Instance& i2 = GetInstance(i);
 
-        if (std::string(i1.GetTagAsString("Columns")) != std::string(i2.GetTagAsString("Columns")) ||
-            std::string(i1.GetTagAsString("Rows")) != std::string(i2.GetTagAsString("Rows")) ||
-            std::string(i1.GetTagAsString("ImageOrientationPatient")) != std::string(i2.GetTagAsString("ImageOrientationPatient")) ||
-            std::string(i1.GetTagAsString("SliceThickness")) != std::string(i2.GetTagAsString("SliceThickness")) ||
-            std::string(i1.GetTagAsString("PixelSpacing")) != std::string(i2.GetTagAsString("PixelSpacing")))
+        if (std::string(reference.GetTagAsString("Columns")) != std::string(i2.GetTagAsString("Columns")) ||
+            std::string(reference.GetTagAsString("Rows")) != std::string(i2.GetTagAsString("Rows")) ||
+            std::string(reference.GetTagAsString("ImageOrientationPatient")) != std::string(i2.GetTagAsString("ImageOrientationPatient")) ||
+            std::string(reference.GetTagAsString("SliceThickness")) != std::string(i2.GetTagAsString("SliceThickness")) ||
+            std::string(reference.GetTagAsString("PixelSpacing")) != std::string(i2.GetTagAsString("PixelSpacing")))
         {
           return false;
         }              
       }
 
-      SliceLocator locator(GetInstance(0));
+
+      // Extract X/Y voxel size and slice thickness
+      std::string s = GetInstance(0).GetTagAsString("PixelSpacing");  // 0028-0030
+      size_t pos = s.find('\\');
+      assert(pos != std::string::npos);
+      std::string sy = s.substr(0, pos);
+      std::string sx = s.substr(pos + 1);
+
+      try
+      {
+        voxelSizeX_ = boost::lexical_cast<float>(sx);
+        voxelSizeY_ = boost::lexical_cast<float>(sy);
+      }
+      catch (boost::bad_lexical_cast)
+      {
+        throw OrthancClientException(Orthanc::ErrorCode_BadFileFormat);
+      }
+
+      sliceThickness_ = GetInstance(0).GetTagAsFloat("SliceThickness");  // 0018-0050
+
+
+      // Compute the location of each slice to extract the voxel size along Z
+      voxelSizeZ_ = std::numeric_limits<float>::infinity();
+
+      SliceLocator locator(reference);
+      float referenceSliceLocation = locator.ComputeSliceLocation(reference);
+
       std::set<float> l;
       for (unsigned int i = 0; i < GetInstanceCount(); i++)
       {
-        l.insert(locator.ComputeSliceLocation(GetInstance(i)));
+        float location = locator.ComputeSliceLocation(GetInstance(i));
+        float distanceToReferenceSlice = fabs(location - referenceSliceLocation);
+
+        l.insert(location);
+
+        if (distanceToReferenceSlice > std::numeric_limits<float>::epsilon() &&
+            distanceToReferenceSlice < voxelSizeZ_)
+        {
+          voxelSizeZ_ = distanceToReferenceSlice;
+        }
       }
 
+
+      // Make sure that 2 slices do not share the same Z location
       return l.size() == GetInstanceCount();
     }
     catch (OrthancClientException)
@@ -275,10 +321,10 @@
     status_ = Status3DImage_NotTested;
     url_ = std::string(connection_.GetOrthancUrl()) + "/series/" + id_;
 
-    isVoxelSizeRead_ = false;
     voxelSizeX_ = 0;
     voxelSizeY_ = 0;
     voxelSizeZ_ = 0;
+    sliceThickness_ = 0;
 
     instances_.SetThreadCount(connection.GetThreadCount());
   }
@@ -324,46 +370,6 @@
       return GetInstance(0).GetTagAsInt("Rows");
   }
 
-  void Series::LoadVoxelSize()
-  {
-    if (isVoxelSizeRead_)
-    {
-      return;
-    }
-
-    Check3DImage();
-
-    if (GetInstanceCount() == 0)
-    {
-      // Empty image, use some default value
-      voxelSizeX_ = 1;
-      voxelSizeY_ = 1;
-      voxelSizeZ_ = 1;
-    }
-    else
-    {
-      try
-      {
-        std::string s = GetInstance(0).GetTagAsString("PixelSpacing");
-        size_t pos = s.find('\\');
-        assert(pos != std::string::npos);
-        std::string sy = s.substr(0, pos);
-        std::string sx = s.substr(pos + 1);
-
-        voxelSizeX_ = boost::lexical_cast<float>(sx);
-        voxelSizeY_ = boost::lexical_cast<float>(sy);
-        voxelSizeZ_ = GetInstance(0).GetTagAsFloat("SliceThickness");
-      }
-      catch (boost::bad_lexical_cast)
-      {
-        throw OrthancClientException(Orthanc::ErrorCode_NotImplemented);
-      }
-    }
-
-    isVoxelSizeRead_ = true;
-  }
-
-
   const char* Series::GetMainDicomTag(const char* tag, const char* defaultValue) const
   {
     if (series_["MainDicomTags"].isMember(tag))
@@ -486,22 +492,28 @@
 
   float Series::GetVoxelSizeX()
   {
-    LoadVoxelSize();
+    Check3DImage();   // Is3DImageInternal() will compute the voxel sizes
     return voxelSizeX_;
   }
 
   float Series::GetVoxelSizeY()
   {
-    LoadVoxelSize();
+    Check3DImage();   // Is3DImageInternal() will compute the voxel sizes
     return voxelSizeY_;
   }
 
   float Series::GetVoxelSizeZ()
   {
-    LoadVoxelSize();
+    Check3DImage();   // Is3DImageInternal() will compute the voxel sizes
     return voxelSizeZ_;
   }
 
+  float Series::GetSliceThickness()
+  {
+    Check3DImage();   // Is3DImageInternal() will compute the voxel sizes
+    return sliceThickness_;
+  }
+
   void Series::Load3DImage(void* target,
                            Orthanc::PixelFormat format,
                            int64_t lineStride,
--- a/OrthancCppClient/Series.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancCppClient/Series.h	Thu Jul 10 11:42:32 2014 +0200
@@ -62,11 +62,11 @@
     Orthanc::ArrayFilledByThreads  instances_;
     Status3DImage status_;
 
-    bool isVoxelSizeRead_;
     float voxelSizeX_;
     float voxelSizeY_;
     float voxelSizeZ_;
-  
+    float sliceThickness_;
+
     void Check3DImage();
 
     bool Is3DImageInternal();
@@ -86,8 +86,6 @@
                              size_t stackStride,
                              Orthanc::ThreadedCommandProcessor::IListener* listener);
 
-    void LoadVoxelSize();  
-
   public:
     /**
      * {summary}{Create a connection to some series.}
@@ -189,6 +187,13 @@
      **/
     float GetVoxelSizeZ();
 
+    /**
+     * {summary}{Get the slice thickness.}
+     * {description}{Get the slice thickness. This call is only valid if this series corresponds to a 3D image.}
+     * {returns}{The slice thickness.}
+     **/
+    float GetSliceThickness();
+
     LAAW_API_INTERNAL void Load3DImage(void* target,
                                        Orthanc::PixelFormat format,
                                        int64_t lineStride,
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -795,6 +795,29 @@
         }
       }
 
+      LAAW_EXPORT_DLL_API char* LAAW_CALL_CONVENTION LAAW_EXTERNC_2be452e7af5bf7dfd8c5021842674497(void* thisObject, float* result)
+      {
+        try
+        {
+          #ifdef LAAW_EXTERNC_START_FUNCTION
+          LAAW_EXTERNC_START_FUNCTION;
+          #endif
+
+          OrthancClient::Series* this_ = static_cast<OrthancClient::Series*>(thisObject);
+*result = this_->GetSliceThickness();
+
+          return NULL;
+        }
+        catch (::Laaw::LaawException& e)
+        {
+          return LAAW_EXTERNC_CopyString(e.What());
+        }
+        catch (...)
+        {
+          return LAAW_EXTERNC_CopyString("...");
+        }
+      }
+
       LAAW_EXPORT_DLL_API char* LAAW_CALL_CONVENTION LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5(void* thisObject, void* arg0, int32_t arg1, int64_t arg2, int64_t arg3)
       {
         try
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/OrthancCppClient.h	Thu Jul 10 11:42:32 2014 +0200
@@ -203,7 +203,7 @@
   {
   private:
     LAAW_ORTHANC_CLIENT_HANDLE_TYPE  handle_;
-    LAAW_ORTHANC_CLIENT_FUNCTION_TYPE  functionsIndex_[62 + 1];
+    LAAW_ORTHANC_CLIENT_FUNCTION_TYPE  functionsIndex_[63 + 1];
 
 
 
@@ -239,7 +239,7 @@
     void FreeString(char* str)
     {
       typedef void (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (char*);
-      Function function = (Function) GetFunction(62);
+      Function function = (Function) GetFunction(63);
       function(str);
     }
 
@@ -302,7 +302,15 @@
     {
       if (handle_ != LAAW_ORTHANC_CLIENT_HANDLE_NULL)
       {
+#if 0
+        /**
+         * Do not explicitly unload the shared library, as it might
+         * interfere with the destruction of static objects declared
+         * inside the library (e.g. this is the case of gflags that is
+         * internally used by googlelog).
+         **/
         LAAW_ORTHANC_CLIENT_CLOSER(handle_);
+#endif
         handle_ = LAAW_ORTHANC_CLIENT_HANDLE_NULL;
       }
     }
@@ -391,7 +399,7 @@
     throw ::OrthancClient::OrthancClientException("Mismatch between the C++ header and the library version");
   }
 
-  functionsIndex_[62] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_FreeString", "4");
+  functionsIndex_[63] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_FreeString", "4");
   functionsIndex_[3] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_557aee7b61817292a0f31269d3c35db7", "8");
   functionsIndex_[4] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_0b8dff0ce67f10954a49b059e348837e", "8");
   functionsIndex_[5] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_e05097c153f676e5a5ee54dcfc78256f", "4");
@@ -423,40 +431,41 @@
   functionsIndex_[30] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_b794f5cd3dad7d7b575dd1fd902afdd0", "8");
   functionsIndex_[31] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_8ee2e50dd9df8f66a3c1766090dd03ab", "8");
   functionsIndex_[32] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_046aed35bbe4751691f4c34cc249a61d", "8");
-  functionsIndex_[33] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5", "28");
-  functionsIndex_[34] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_b2601a161c24ad0a1d3586246f87452c", "32");
+  functionsIndex_[33] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_2be452e7af5bf7dfd8c5021842674497", "8");
+  functionsIndex_[34] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5", "28");
+  functionsIndex_[35] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_b2601a161c24ad0a1d3586246f87452c", "32");
   functionsIndex_[19] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_193599b9e345384fcdfcd47c29c55342", "12");
   functionsIndex_[20] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_7c97f17063a357d38c5fab1136ad12a0", "4");
-  functionsIndex_[37] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_e65b20b7e0170b67544cd6664a4639b7", "4");
-  functionsIndex_[38] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_470e981b0e41f17231ba0ae6f3033321", "8");
-  functionsIndex_[39] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_04cefd138b6ea15ad909858f2a0a8f05", "12");
-  functionsIndex_[40] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_aee5b1f6f0c082f2c3b0986f9f6a18c7", "8");
-  functionsIndex_[41] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_93965682bace75491413e1f0b8d5a654", "16");
-  functionsIndex_[35] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_b01c6003238eb46c8db5dc823d7ca678", "12");
-  functionsIndex_[36] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_0147007fb99bad8cd95a139ec8795376", "4");
-  functionsIndex_[44] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_236ee8b403bc99535a8a4695c0cd45cb", "8");
-  functionsIndex_[45] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_2a437b7aba6bb01e81113835be8f0146", "8");
-  functionsIndex_[46] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_2bcbcb850934ae0bb4c6f0cc940e6cda", "8");
-  functionsIndex_[47] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_8d415c3a78a48e7e61d9fd24e7c79484", "12");
-  functionsIndex_[48] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_70d2f8398bbc63b5f792b69b4ad5fecb", "12");
-  functionsIndex_[49] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1729a067d902771517388eedd7346b23", "12");
-  functionsIndex_[50] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_72e2aeee66cd3abd8ab7e987321c3745", "8");
-  functionsIndex_[51] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1ea3df5a1ac1a1a687fe7325adddb6f0", "8");
-  functionsIndex_[52] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_99b4f370e4f532d8b763e2cb49db92f8", "8");
-  functionsIndex_[53] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_c41c742b68617f1c0590577a0a5ebc0c", "8");
-  functionsIndex_[54] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_142dd2feba0fc1d262bbd0baeb441a8b", "8");
-  functionsIndex_[55] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_5f5c9f81a4dff8daa6c359f1d0488fef", "12");
-  functionsIndex_[56] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_9ca979fffd08fa256306d4e68d8b0e91", "8");
-  functionsIndex_[57] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_6f2d77a26edc91c28d89408dbc3c271e", "8");
-  functionsIndex_[58] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_c0f494b80d4ff8b232df7a75baa0700a", "4");
-  functionsIndex_[59] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_d604f44bd5195e082e745e9cbc164f4c", "4");
-  functionsIndex_[60] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1710299d1c5f3b1f2b7cf3962deebbfd", "8");
-  functionsIndex_[61] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_bb55aaf772ddceaadee36f4e54136bcb", "8");
-  functionsIndex_[42] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_6c5ad02f91b583e29cebd0bd319ce21d", "12");
-  functionsIndex_[43] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_4068241c44a9c1367fe0e57be523f207", "4");
+  functionsIndex_[38] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_e65b20b7e0170b67544cd6664a4639b7", "4");
+  functionsIndex_[39] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_470e981b0e41f17231ba0ae6f3033321", "8");
+  functionsIndex_[40] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_04cefd138b6ea15ad909858f2a0a8f05", "12");
+  functionsIndex_[41] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_aee5b1f6f0c082f2c3b0986f9f6a18c7", "8");
+  functionsIndex_[42] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_93965682bace75491413e1f0b8d5a654", "16");
+  functionsIndex_[36] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_b01c6003238eb46c8db5dc823d7ca678", "12");
+  functionsIndex_[37] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_0147007fb99bad8cd95a139ec8795376", "4");
+  functionsIndex_[45] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_236ee8b403bc99535a8a4695c0cd45cb", "8");
+  functionsIndex_[46] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_2a437b7aba6bb01e81113835be8f0146", "8");
+  functionsIndex_[47] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_2bcbcb850934ae0bb4c6f0cc940e6cda", "8");
+  functionsIndex_[48] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_8d415c3a78a48e7e61d9fd24e7c79484", "12");
+  functionsIndex_[49] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_70d2f8398bbc63b5f792b69b4ad5fecb", "12");
+  functionsIndex_[50] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1729a067d902771517388eedd7346b23", "12");
+  functionsIndex_[51] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_72e2aeee66cd3abd8ab7e987321c3745", "8");
+  functionsIndex_[52] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1ea3df5a1ac1a1a687fe7325adddb6f0", "8");
+  functionsIndex_[53] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_99b4f370e4f532d8b763e2cb49db92f8", "8");
+  functionsIndex_[54] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_c41c742b68617f1c0590577a0a5ebc0c", "8");
+  functionsIndex_[55] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_142dd2feba0fc1d262bbd0baeb441a8b", "8");
+  functionsIndex_[56] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_5f5c9f81a4dff8daa6c359f1d0488fef", "12");
+  functionsIndex_[57] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_9ca979fffd08fa256306d4e68d8b0e91", "8");
+  functionsIndex_[58] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_6f2d77a26edc91c28d89408dbc3c271e", "8");
+  functionsIndex_[59] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_c0f494b80d4ff8b232df7a75baa0700a", "4");
+  functionsIndex_[60] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_d604f44bd5195e082e745e9cbc164f4c", "4");
+  functionsIndex_[61] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_1710299d1c5f3b1f2b7cf3962deebbfd", "8");
+  functionsIndex_[62] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_bb55aaf772ddceaadee36f4e54136bcb", "8");
+  functionsIndex_[43] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_6c5ad02f91b583e29cebd0bd319ce21d", "12");
+  functionsIndex_[44] = LAAW_ORTHANC_CLIENT_GET_FUNCTION(handle_, "LAAW_EXTERNC_4068241c44a9c1367fe0e57be523f207", "4");
   
   /* Check whether the functions were properly loaded */
-  for (unsigned int i = 0; i <= 62; i++)
+  for (unsigned int i = 0; i <= 63; i++)
   {
     if (functionsIndex_[i] == (LAAW_ORTHANC_CLIENT_FUNCTION_TYPE) NULL)
     {
@@ -704,6 +713,7 @@
     inline float GetVoxelSizeX();
     inline float GetVoxelSizeY();
     inline float GetVoxelSizeZ();
+    inline float GetSliceThickness();
     inline void Load3DImage(void* target, ::Orthanc::PixelFormat format, LAAW_INT64 lineStride, LAAW_INT64 stackStride);
     inline void Load3DImage(void* target, ::Orthanc::PixelFormat format, LAAW_INT64 lineStride, LAAW_INT64 stackStride, float progress[]);
   };
@@ -1323,6 +1333,22 @@
     return result_;
   }
   /**
+  * @brief Get the slice thickness.
+  *
+  * Get the slice thickness. This call is only valid if this series corresponds to a 3D image.
+  *
+  * @return The slice thickness.
+  **/
+  inline float Series::GetSliceThickness()
+  {
+    float result_;
+    typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, float*);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(33);
+    char* error = function(pimpl_, &result_);
+    ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
+    return result_;
+  }
+  /**
   * @brief Load the 3D image into a memory buffer.
   *
   * Load the 3D image into a memory buffer. This call is only valid if this series corresponds to a 3D image. The "target" buffer must be wide enough to store all the voxels of the image.
@@ -1335,7 +1361,7 @@
   inline void Series::Load3DImage(void* target, ::Orthanc::PixelFormat format, LAAW_INT64 lineStride, LAAW_INT64 stackStride)
   {
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, void*, LAAW_INT32, LAAW_INT64, LAAW_INT64);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(33);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(34);
     char* error = function(pimpl_, target, format, lineStride, stackStride);
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
   }
@@ -1353,7 +1379,7 @@
   inline void Series::Load3DImage(void* target, ::Orthanc::PixelFormat format, LAAW_INT64 lineStride, LAAW_INT64 stackStride, float progress[])
   {
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, void*, LAAW_INT32, LAAW_INT64, LAAW_INT64, float*);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(34);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(35);
     char* error = function(pimpl_, target, format, lineStride, stackStride, progress);
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
   }
@@ -1373,7 +1399,7 @@
   {
     isReference_ = false;
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void**, void*, const char*);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(35);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(36);
     char* error = function(&pimpl_, connection.pimpl_, id.c_str());
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
   }
@@ -1387,7 +1413,7 @@
   {
     if (isReference_) return;
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(36);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(37);
     char* error = function(pimpl_);
     error = error;  // Remove warning about unused variable
   }
@@ -1400,7 +1426,7 @@
   inline void Study::Reload()
   {
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(37);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(38);
     char* error = function(pimpl_);
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
   }
@@ -1415,7 +1441,7 @@
   {
     LAAW_UINT32 result_;
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(38);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(39);
     char* error = function(pimpl_, &result_);
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
     return result_;
@@ -1432,7 +1458,7 @@
   {
     void* result_;
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, void**, LAAW_UINT32);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(39);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(40);
     char* error = function(pimpl_, &result_, index);
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
     return ::OrthancClient::Series(result_);
@@ -1448,7 +1474,7 @@
   {
     const char* result_;
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(40);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(41);
     char* error = function(pimpl_, &result_);
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
     return std::string(result_);
@@ -1466,7 +1492,7 @@
   {
     const char* result_;
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**, const char*, const char*);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(41);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(42);
     char* error = function(pimpl_, &result_, tag.c_str(), defaultValue.c_str());
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
     return std::string(result_);
@@ -1487,7 +1513,7 @@
   {
     isReference_ = false;
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void**, void*, const char*);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(42);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(43);
     char* error = function(&pimpl_, connection.pimpl_, id.c_str());
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
   }
@@ -1501,7 +1527,7 @@
   {
     if (isReference_) return;
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(43);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(44);
     char* error = function(pimpl_);
     error = error;  // Remove warning about unused variable
   }
@@ -1516,7 +1542,7 @@
   {
     const char* result_;
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(44);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(45);
     char* error = function(pimpl_, &result_);
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
     return std::string(result_);
@@ -1531,7 +1557,7 @@
   inline void Instance::SetImageExtractionMode(::Orthanc::ImageExtractionMode mode)
   {
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_INT32);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(45);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(46);
     char* error = function(pimpl_, mode);
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
   }
@@ -1546,7 +1572,7 @@
   {
     LAAW_INT32 result_;
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, LAAW_INT32*);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(46);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(47);
     char* error = function(pimpl_, &result_);
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
     return static_cast< ::Orthanc::ImageExtractionMode >(result_);
@@ -1563,7 +1589,7 @@
   {
     const char* result_;
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**, const char*);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(47);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(48);
     char* error = function(pimpl_, &result_, tag.c_str());
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
     return std::string(result_);
@@ -1580,7 +1606,7 @@
   {
     float result_;
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, float*, const char*);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(48);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(49);
     char* error = function(pimpl_, &result_, tag.c_str());
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
     return result_;
@@ -1597,7 +1623,7 @@
   {
     LAAW_INT32 result_;
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, LAAW_INT32*, const char*);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(49);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(50);
     char* error = function(pimpl_, &result_, tag.c_str());
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
     return result_;
@@ -1613,7 +1639,7 @@
   {
     LAAW_UINT32 result_;
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(50);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(51);
     char* error = function(pimpl_, &result_);
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
     return result_;
@@ -1629,7 +1655,7 @@
   {
     LAAW_UINT32 result_;
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(51);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(52);
     char* error = function(pimpl_, &result_);
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
     return result_;
@@ -1645,7 +1671,7 @@
   {
     LAAW_UINT32 result_;
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT32*);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(52);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(53);
     char* error = function(pimpl_, &result_);
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
     return result_;
@@ -1661,7 +1687,7 @@
   {
     LAAW_INT32 result_;
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_INT32*);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(53);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(54);
     char* error = function(pimpl_, &result_);
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
     return static_cast< ::Orthanc::PixelFormat >(result_);
@@ -1677,7 +1703,7 @@
   {
     const void* result_;
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, const void**);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(54);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(55);
     char* error = function(pimpl_, &result_);
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
     return reinterpret_cast< const void* >(result_);
@@ -1694,7 +1720,7 @@
   {
     const void* result_;
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, const void**, LAAW_UINT32);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(55);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(56);
     char* error = function(pimpl_, &result_, y);
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
     return reinterpret_cast< const void* >(result_);
@@ -1710,7 +1736,7 @@
   {
     LAAW_UINT64 result_;
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, LAAW_UINT64*);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(56);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(57);
     char* error = function(pimpl_, &result_);
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
     return result_;
@@ -1726,7 +1752,7 @@
   {
     const void* result_;
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, const void**);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(57);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(58);
     char* error = function(pimpl_, &result_);
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
     return reinterpret_cast< const void* >(result_);
@@ -1740,7 +1766,7 @@
   inline void Instance::DiscardImage()
   {
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(58);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(59);
     char* error = function(pimpl_);
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
   }
@@ -1753,7 +1779,7 @@
   inline void Instance::DiscardDicom()
   {
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(59);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(60);
     char* error = function(pimpl_);
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
   }
@@ -1767,7 +1793,7 @@
   inline void Instance::LoadTagContent(const ::std::string& path)
   {
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (void*, const char*);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(60);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(61);
     char* error = function(pimpl_, path.c_str());
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
   }
@@ -1782,7 +1808,7 @@
   {
     const char* result_;
     typedef char* (LAAW_ORTHANC_CLIENT_CALL_CONV* Function) (const void*, const char**);
-    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(61);
+    Function function = (Function) ::OrthancClient::Internals::Library::GetInstance().GetFunction(62);
     char* error = function(pimpl_, &result_);
     ::OrthancClient::Internals::Library::GetInstance().ThrowExceptionIfNeeded(error);
     return std::string(result_);
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.def	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.def	Thu Jul 10 11:42:32 2014 +0200
@@ -31,6 +31,7 @@
   _LAAW_EXTERNC_b794f5cd3dad7d7b575dd1fd902afdd0@8 = LAAW_EXTERNC_b794f5cd3dad7d7b575dd1fd902afdd0@8
   _LAAW_EXTERNC_8ee2e50dd9df8f66a3c1766090dd03ab@8 = LAAW_EXTERNC_8ee2e50dd9df8f66a3c1766090dd03ab@8
   _LAAW_EXTERNC_046aed35bbe4751691f4c34cc249a61d@8 = LAAW_EXTERNC_046aed35bbe4751691f4c34cc249a61d@8
+  _LAAW_EXTERNC_2be452e7af5bf7dfd8c5021842674497@8 = LAAW_EXTERNC_2be452e7af5bf7dfd8c5021842674497@8
   _LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5@28 = LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5@28
   _LAAW_EXTERNC_b2601a161c24ad0a1d3586246f87452c@32 = LAAW_EXTERNC_b2601a161c24ad0a1d3586246f87452c@32
   _LAAW_EXTERNC_193599b9e345384fcdfcd47c29c55342@12 = LAAW_EXTERNC_193599b9e345384fcdfcd47c29c55342@12
--- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.def	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.def	Thu Jul 10 11:42:32 2014 +0200
@@ -31,6 +31,7 @@
   LAAW_EXTERNC_b794f5cd3dad7d7b575dd1fd902afdd0
   LAAW_EXTERNC_8ee2e50dd9df8f66a3c1766090dd03ab
   LAAW_EXTERNC_046aed35bbe4751691f4c34cc249a61d
+  LAAW_EXTERNC_2be452e7af5bf7dfd8c5021842674497
   LAAW_EXTERNC_4dcc7a0fd025efba251ac6e9b701c2c5
   LAAW_EXTERNC_b2601a161c24ad0a1d3586246f87452c
   LAAW_EXTERNC_193599b9e345384fcdfcd47c29c55342
--- a/OrthancServer/DatabaseWrapper.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/DatabaseWrapper.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -1045,4 +1045,21 @@
       result.push_back(s.ColumnInt64(0));
     }
   }
+
+
+  void DatabaseWrapper::GetAllMetadata(std::map<MetadataType, std::string>& result,
+                                       int64_t id)
+  {
+    result.clear();
+
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "SELECT type, value FROM Metadata WHERE id=?");
+    s.BindInt64(0, id);
+
+    while (s.Step())
+    {
+      MetadataType key = static_cast<MetadataType>(s.ColumnInt(0));
+      result[key] = s.ColumnString(1);
+    }
+  }
+
 }
--- a/OrthancServer/DatabaseWrapper.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/DatabaseWrapper.h	Thu Jul 10 11:42:32 2014 +0200
@@ -235,5 +235,8 @@
 
     void LookupTagValue(std::list<int64_t>& result,
                         const std::string& value);
+
+    void GetAllMetadata(std::map<MetadataType, std::string>& result,
+                        int64_t id);
   };
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/DicomInstanceToStore.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,174 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "DicomInstanceToStore.h"
+
+#include "FromDcmtkBridge.h"
+
+#include <dcmtk/dcmdata/dcfilefo.h>
+#include <glog/logging.h>
+
+
+namespace Orthanc
+{
+  static DcmDataset& GetDataset(ParsedDicomFile& file)
+  {
+    return *reinterpret_cast<DcmFileFormat*>(file.GetDcmtkObject())->getDataset();
+  }
+
+
+  void DicomInstanceToStore::AddMetadata(ResourceType level,
+                                         MetadataType metadata,
+                                         const std::string& value)
+  {
+    metadata_[std::make_pair(level, metadata)] = value;
+  }
+
+
+  void DicomInstanceToStore::ComputeMissingInformation()
+  {
+    if (buffer_.HasContent() &&
+        summary_.HasContent() &&
+        json_.HasContent())
+    {
+      // Fine, everything is available
+      return; 
+    }
+    
+    if (!buffer_.HasContent())
+    {
+      if (!parsed_.HasContent())
+      {
+        throw OrthancException(ErrorCode_NotImplemented);
+      }
+      else
+      {
+        // Serialize the parsed DICOM file
+        buffer_.Allocate();
+        if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer_.GetContent(), GetDataset(parsed_.GetContent())))
+        {
+          LOG(ERROR) << "Unable to serialize a DICOM file to a memory buffer";
+          throw OrthancException(ErrorCode_InternalError);
+        }
+      }
+    }
+
+    if (summary_.HasContent() &&
+        json_.HasContent())
+    {
+      return;
+    }
+
+    // At this point, we know that the DICOM file is available as a
+    // memory buffer, but that its summary or its JSON version is
+    // missing
+
+    if (!parsed_.HasContent())
+    {
+      parsed_.TakeOwnership(new ParsedDicomFile(buffer_.GetConstContent()));
+    }
+
+    // At this point, we have parsed the DICOM file
+    
+    if (!summary_.HasContent())
+    {
+      summary_.Allocate();
+      FromDcmtkBridge::Convert(summary_.GetContent(), GetDataset(parsed_.GetContent()));
+    }
+    
+    if (!json_.HasContent())
+    {
+      json_.Allocate();
+      FromDcmtkBridge::ToJson(json_.GetContent(), GetDataset(parsed_.GetContent()));
+    }
+  }
+
+
+
+  const char* DicomInstanceToStore::GetBufferData()
+  {
+    ComputeMissingInformation();
+    
+    if (!buffer_.HasContent())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    if (buffer_.GetConstContent().size() == 0)
+    {
+      return NULL;
+    }
+    else
+    {
+      return buffer_.GetConstContent().c_str();
+    }
+  }
+
+
+  size_t DicomInstanceToStore::GetBufferSize()
+  {
+    ComputeMissingInformation();
+    
+    if (!buffer_.HasContent())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    return buffer_.GetConstContent().size();
+  }
+
+
+  const DicomMap& DicomInstanceToStore::GetSummary()
+  {
+    ComputeMissingInformation();
+    
+    if (!summary_.HasContent())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    return summary_.GetConstContent();
+  }
+
+    
+  const Json::Value& DicomInstanceToStore::GetJson()
+  {
+    ComputeMissingInformation();
+    
+    if (!json_.HasContent())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    return json_.GetConstContent();
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/DicomInstanceToStore.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,204 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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 "ParsedDicomFile.h"
+#include "ServerIndex.h"
+#include "../Core/OrthancException.h"
+
+namespace Orthanc
+{
+  class DicomInstanceToStore
+  {
+  private:
+    template <typename T>
+    class SmartContainer
+    {
+    private:
+      T* content_;
+      bool toDelete_;
+      bool isReadOnly_;
+
+      void Deallocate()
+      {
+        if (content_ && toDelete_)
+        {
+          delete content_;
+          toDelete_ = false;
+          content_ = NULL;
+        }
+      }
+
+    public:
+      SmartContainer() : content_(NULL), toDelete_(false)
+      {
+      }
+
+      ~SmartContainer()
+      {
+        Deallocate();
+      }
+
+      void Allocate()
+      {
+        Deallocate();
+        content_ = new T;
+        toDelete_ = true;
+        isReadOnly_ = false;
+      }
+
+      void TakeOwnership(T* content)
+      {
+        if (content == NULL)
+        {
+          throw OrthancException(ErrorCode_ParameterOutOfRange);
+        }
+
+        Deallocate();
+        content_ = content;
+        toDelete_ = true;
+        isReadOnly_ = false;
+      }
+
+      void SetReference(T& content)   // Read and write assign, without transfering ownership
+      {
+        Deallocate();
+        content_ = &content;
+        toDelete_ = false;
+        isReadOnly_ = false;
+      }
+
+      void SetConstReference(const T& content)   // Read-only assign, without transfering ownership
+      {
+        Deallocate();
+        content_ = &const_cast<T&>(content);
+        toDelete_ = false;
+        isReadOnly_ = true;
+      }
+
+      bool HasContent() const
+      {
+        return content_ != NULL;
+      }
+
+      T& GetContent()
+      {
+        if (content_ == NULL)
+        {
+          throw OrthancException(ErrorCode_BadSequenceOfCalls);
+        }
+
+        if (isReadOnly_)
+        {
+          throw OrthancException(ErrorCode_ReadOnly);
+        }
+
+        return *content_;
+      }
+
+      const T& GetConstContent() const
+      {
+        if (content_ == NULL)
+        {
+          throw OrthancException(ErrorCode_BadSequenceOfCalls);
+        }
+
+        return *content_;
+      }
+    };
+
+
+    SmartContainer<std::string>  buffer_;
+    SmartContainer<ParsedDicomFile>  parsed_;
+    SmartContainer<DicomMap>  summary_;
+    SmartContainer<Json::Value>  json_;
+
+    std::string remoteAet_;
+    ServerIndex::MetadataMap metadata_;
+
+    void ComputeMissingInformation();
+
+  public:
+    void SetBuffer(const std::string& dicom)
+    {
+      buffer_.SetConstReference(dicom);
+    }
+
+    void SetParsedDicomFile(ParsedDicomFile& parsed)
+    {
+      parsed_.SetReference(parsed);
+    }
+
+    void SetSummary(const DicomMap& summary)
+    {
+      summary_.SetConstReference(summary);
+    }
+
+    void SetJson(const Json::Value& json)
+    {
+      json_.SetConstReference(json);
+    }
+
+    const std::string GetRemoteAet() const
+    {
+      return remoteAet_;
+    }
+
+    void SetRemoteAet(const std::string& aet)
+    {
+      remoteAet_ = aet;
+    }
+
+    void AddMetadata(ResourceType level,
+                     MetadataType metadata,
+                     const std::string& value);
+
+    const ServerIndex::MetadataMap& GetMetadata() const
+    {
+      return metadata_;
+    }
+
+    ServerIndex::MetadataMap& GetMetadata()
+    {
+      return metadata_;
+    }
+
+    const char* GetBufferData();
+
+    size_t GetBufferSize();
+
+    const DicomMap& GetSummary();
+    
+    const Json::Value& GetJson();
+  };
+}
--- a/OrthancServer/DicomModification.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/DicomModification.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -96,12 +96,18 @@
   {
     removals_.erase(tag);
     replacements_.erase(tag);
+
+    if (FromDcmtkBridge::IsPrivateTag(tag))
+    {
+      privateTagsToKeep_.insert(tag);
+    }
   }
 
   void DicomModification::Remove(const DicomTag& tag)
   {
     removals_.insert(tag);
     replacements_.erase(tag);
+    privateTagsToKeep_.erase(tag);
   }
 
   bool DicomModification::IsRemoved(const DicomTag& tag) const
@@ -113,6 +119,7 @@
                                   const std::string& value)
   {
     removals_.erase(tag);
+    privateTagsToKeep_.erase(tag);
     replacements_[tag] = value;
   }
 
@@ -153,6 +160,7 @@
     removePrivateTags_ = true;
     level_ = ResourceType_Patient;
     uidMap_.clear();
+    privateTagsToKeep_.clear();
 
     // This is Table E.1-1 from PS 3.15-2008 - DICOM Part 15: Security and System Management Profiles
     removals_.insert(DicomTag(0x0008, 0x0014));  // Instance Creator UID
@@ -261,11 +269,11 @@
     // (1) Remove the private tags, if need be
     if (removePrivateTags_)
     {
-      toModify.RemovePrivateTags();
+      toModify.RemovePrivateTags(privateTagsToKeep_);
     }
 
     // (2) Remove the tags specified by the user
-    for (Removals::const_iterator it = removals_.begin(); 
+    for (SetOfTags::const_iterator it = removals_.begin(); 
          it != removals_.end(); ++it)
     {
       toModify.Remove(*it);
--- a/OrthancServer/DicomModification.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/DicomModification.h	Thu Jul 10 11:42:32 2014 +0200
@@ -46,15 +46,16 @@
      **/
 
   private:
-    typedef std::set<DicomTag> Removals;
+    typedef std::set<DicomTag> SetOfTags;
     typedef std::map<DicomTag, std::string> Replacements;
     typedef std::map< std::pair<ResourceType, std::string>, std::string>  UidMap;
 
-    Removals removals_;
+    SetOfTags removals_;
     Replacements replacements_;
     bool removePrivateTags_;
     ResourceType level_;
     UidMap uidMap_;
+    SetOfTags privateTagsToKeep_;
 
     void MapDicomIdentifier(ParsedDicomFile& dicom,
                             ResourceType level);
--- a/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/DicomProtocol/DicomUserConnection.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -154,7 +154,8 @@
   {
     if (cond.bad())
     {
-      throw OrthancException("DicomUserConnection: " + std::string(cond.text()));
+      LOG(ERROR) << "DicomUserConnection: " << std::string(cond.text());
+       throw OrthancException(ErrorCode_NetworkProtocol);
     }
   }
 
@@ -162,7 +163,8 @@
   {
     if (!IsOpen())
     {
-      throw OrthancException("DicomUserConnection: First open the connection");
+      LOG(ERROR) << "DicomUserConnection: First open the connection";
+      throw OrthancException(ErrorCode_NetworkProtocol);
     }
   }
 
--- a/OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/DicomProtocol/ReusableDicomUserConnection.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -172,6 +172,14 @@
 
   void ReusableDicomUserConnection::Unlock()
   {
+    if (connection_ != NULL &&
+        connection_->GetDistantManufacturer() == ModalityManufacturer_StoreScp)
+    {
+      // "storescp" from DCMTK has problems when reusing a
+      // connection. Always close.
+      Close();
+    }
+
     lastUse_ = Now();
     mutex_.unlock();
   }
--- a/OrthancServer/FromDcmtkBridge.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/FromDcmtkBridge.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -61,6 +61,7 @@
 #include <dcmtk/dcmdata/dcistrmb.h>
 #include <dcmtk/dcmdata/dcuid.h>
 #include <dcmtk/dcmdata/dcmetinf.h>
+#include <dcmtk/dcmdata/dcdeftag.h>
 
 #include <dcmtk/dcmdata/dcvrae.h>
 #include <dcmtk/dcmdata/dcvras.h>
@@ -115,8 +116,44 @@
             GetCharValue(c[3]));
   }
 
+
+  Encoding FromDcmtkBridge::DetectEncoding(DcmDataset& dataset)
+  {
+    // By default, assume UTF-8 encoding (as in dcm2xml.cc)
+    Encoding encoding = Encoding_Utf8;
+
+    OFString tmp;
+    if (dataset.findAndGetOFString(DCM_SpecificCharacterSet, tmp).good())
+    {
+      std::string characterSet = Toolbox::StripSpaces(std::string(tmp.c_str()));
+
+      // TODO Add more encodings
+
+      if (characterSet == "ISO_IR 6" ||
+          characterSet == "ISO_IR 192")
+      {
+        encoding = Encoding_Utf8;
+      }
+      else if (characterSet == "ISO_IR 100")
+      {
+        encoding = Encoding_Latin1;
+      }
+      else if (!characterSet.empty())
+      {
+        LOG(WARNING) << "Value of Specific Character Set (0008,0005) is not supported: " << characterSet;
+        // Fallback to ASCII (remove all special characters)
+        encoding = Encoding_Ascii;
+      }
+    }
+
+    return encoding;
+  }
+
+
   void FromDcmtkBridge::Convert(DicomMap& target, DcmDataset& dataset)
   {
+    Encoding encoding = DetectEncoding(dataset);
+
     target.Clear();
     for (unsigned long i = 0; i < dataset.card(); i++)
     {
@@ -125,19 +162,40 @@
       {
         target.SetValue(element->getTag().getGTag(),
                         element->getTag().getETag(),
-                        ConvertLeafElement(*element));
+                        ConvertLeafElement(*element, encoding));
       }
     }
   }
 
 
+  DicomTag FromDcmtkBridge::Convert(const DcmTag& tag)
+  {
+    return DicomTag(tag.getGTag(), tag.getETag());
+  }
+
+
   DicomTag FromDcmtkBridge::GetTag(const DcmElement& element)
   {
     return DicomTag(element.getGTag(), element.getETag());
   }
 
 
-  DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element)
+  bool FromDcmtkBridge::IsPrivateTag(DcmTag& tag)
+  {
+    return (tag.getPrivateCreator() != NULL ||
+            !strcmp("PrivateCreator", tag.getTagName()));  // TODO - This may change with future versions of DCMTK
+  }
+
+
+  bool FromDcmtkBridge::IsPrivateTag(const DicomTag& tag)
+  {
+    DcmTag tmp(tag.GetGroup(), tag.GetElement());
+    return IsPrivateTag(tmp);
+  }
+
+
+  DicomValue* FromDcmtkBridge::ConvertLeafElement(DcmElement& element,
+                                                  Encoding encoding)
   {
     if (!element.isLeaf())
     {
@@ -151,7 +209,7 @@
           c != NULL)
       {
         std::string s(c);
-        std::string utf8 = Toolbox::ConvertToUtf8(s, "ISO-8859-1"); // TODO Parameter?
+        std::string utf8 = Toolbox::ConvertToUtf8(s, encoding);
         return new DicomString(utf8);
       }
       else
@@ -313,25 +371,28 @@
 
   static void StoreElement(Json::Value& target,
                            DcmElement& element,
-                           unsigned int maxStringLength);
+                           unsigned int maxStringLength,
+                           Encoding encoding);
 
   static void StoreItem(Json::Value& target,
                         DcmItem& item,
-                        unsigned int maxStringLength)
+                        unsigned int maxStringLength,
+                        Encoding encoding)
   {
     target = Json::Value(Json::objectValue);
 
     for (unsigned long i = 0; i < item.card(); i++)
     {
       DcmElement* element = item.getElement(i);
-      StoreElement(target, *element, maxStringLength);
+      StoreElement(target, *element, maxStringLength, encoding);
     }
   }
 
 
   static void StoreElement(Json::Value& target,
                            DcmElement& element,
-                           unsigned int maxStringLength)
+                           unsigned int maxStringLength,
+                           Encoding encoding)
   {
     assert(target.type() == Json::objectValue);
 
@@ -356,7 +417,7 @@
         value["PrivateCreator"] = tagbis.getPrivateCreator();
       }
 
-      std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element));
+      std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element, encoding));
       if (v->IsNull())
       {
         value["Type"] = "Null";
@@ -393,7 +454,7 @@
       {
         DcmItem* child = sequence.getItem(i);
         Json::Value& v = children.append(Json::objectValue);
-        StoreItem(v, *child, maxStringLength);
+        StoreItem(v, *child, maxStringLength, encoding);
       }  
 
       target[formattedTag]["Name"] = tagName;
@@ -407,7 +468,7 @@
                                DcmDataset& dataset,
                                unsigned int maxStringLength)
   {
-    StoreItem(root, dataset, maxStringLength);
+    StoreItem(root, dataset, maxStringLength, DetectEncoding(dataset));
   }
 
 
@@ -475,7 +536,7 @@
         isxdigit(name[1]) &&
         isxdigit(name[2]) &&
         isxdigit(name[3]) &&
-        name[4] == '-' &&
+        (name[4] == '-' || name[4] == ',') &&
         isxdigit(name[5]) &&
         isxdigit(name[6]) &&
         isxdigit(name[7]) &&
@@ -573,7 +634,7 @@
   }
 
   bool FromDcmtkBridge::SaveToMemoryBuffer(std::string& buffer,
-                                           DcmDataset* dataSet)
+                                           DcmDataset& dataSet)
   {
     // Determine the transfer syntax which shall be used to write the
     // information to the file. We always switch to the Little Endian
@@ -588,7 +649,7 @@
      * dataset into memory. We now keep the original transfer syntax
      * (if available).
      **/
-    E_TransferSyntax xfer = dataSet->getOriginalXfer();
+    E_TransferSyntax xfer = dataSet.getOriginalXfer();
     if (xfer == EXS_Unknown)
     {
       // No information about the original transfer syntax: This is
@@ -599,7 +660,7 @@
     E_EncodingType encodingType = /*opt_sequenceType*/ EET_ExplicitLength;
 
     // Create the meta-header information
-    DcmFileFormat ff(dataSet);
+    DcmFileFormat ff(&dataSet);
     ff.validateMetaInfo(xfer);
 
     // Create a memory buffer with the proper size
--- a/OrthancServer/FromDcmtkBridge.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/FromDcmtkBridge.h	Thu Jul 10 11:42:32 2014 +0200
@@ -44,11 +44,20 @@
   class FromDcmtkBridge
   {
   public:
+    static Encoding DetectEncoding(DcmDataset& dataset);
+
     static void Convert(DicomMap& target, DcmDataset& dataset);
 
+    static DicomTag Convert(const DcmTag& tag);
+
     static DicomTag GetTag(const DcmElement& element);
 
-    static DicomValue* ConvertLeafElement(DcmElement& element);
+    static bool IsPrivateTag(DcmTag& tag);
+
+    static bool IsPrivateTag(const DicomTag& tag);
+
+    static DicomValue* ConvertLeafElement(DcmElement& element,
+                                          Encoding encoding);
 
     static void ToJson(Json::Value& target, 
                        DcmDataset& dataset,
@@ -95,6 +104,6 @@
     static std::string GenerateUniqueIdentifier(ResourceType level);
 
     static bool SaveToMemoryBuffer(std::string& buffer,
-                                   DcmDataset* dataSet);
+                                   DcmDataset& dataSet);
   };
 }
--- a/OrthancServer/Internals/DicomImageDecoder.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/Internals/DicomImageDecoder.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -589,10 +589,18 @@
   }
 
 
+  static bool IsColorImage(PixelFormat format)
+  {
+    return (format == PixelFormat_RGB24 ||
+            format == PixelFormat_RGBA32);
+  }
+
+
   bool DicomImageDecoder::DecodeAndTruncate(ImageBuffer& target,
                                             DcmDataset& dataset,
                                             unsigned int frame,
-                                            PixelFormat format)
+                                            PixelFormat format,
+                                            bool allowColorConversion)
   {
     // TODO Special case for uncompressed images
     
@@ -602,6 +610,19 @@
       return false;
     }
 
+    // If specified, prevent the conversion between color and
+    // grayscale images
+    bool isSourceColor = IsColorImage(source.GetFormat());
+    bool isTargetColor = IsColorImage(format);
+
+    if (!allowColorConversion)
+    {
+      if (isSourceColor ^ isTargetColor)
+      {
+        return false;
+      }
+    }
+
     if (source.GetFormat() == format)
     {
       // No conversion is required, return the temporary image
--- a/OrthancServer/Internals/DicomImageDecoder.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/Internals/DicomImageDecoder.h	Thu Jul 10 11:42:32 2014 +0200
@@ -72,7 +72,8 @@
     static bool DecodeAndTruncate(ImageBuffer& target,
                                   DcmDataset& dataset,
                                   unsigned int frame,
-                                  PixelFormat format);
+                                  PixelFormat format,
+                                  bool allowColorConversion);
 
     static bool DecodePreview(ImageBuffer& target,
                               DcmDataset& dataset,
--- a/OrthancServer/Internals/StoreScp.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/Internals/StoreScp.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -168,7 +168,7 @@
             FromDcmtkBridge::Convert(summary, **imageDataSet);
             FromDcmtkBridge::ToJson(dicomJson, **imageDataSet);       
 
-            if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, *imageDataSet))
+            if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, **imageDataSet))
             {
               LOG(ERROR) << "cannot write DICOM file to memory";
               rsp->DimseStatus = STATUS_STORE_Refused_OutOfResources;
--- a/OrthancServer/OrthancInitialization.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/OrthancInitialization.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -288,7 +288,8 @@
     if (modalities.type() != Json::objectValue ||
         !modalities.isMember(name))
     {
-      throw OrthancException(ErrorCode_BadFileFormat);
+      LOG(ERROR) << "No modality with symbolic name: " << name;
+      throw OrthancException(ErrorCode_InexistentItem);
     }
 
     try
@@ -321,7 +322,8 @@
       if (modalities.type() != Json::objectValue ||
           !modalities.isMember(name))
       {
-        throw OrthancException(ErrorCode_BadFileFormat);
+        LOG(ERROR) << "No peer with symbolic name: " << name;
+        throw OrthancException(ErrorCode_InexistentItem);
       }
 
       peer.FromJson(modalities[name]);
--- a/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -33,7 +33,6 @@
 #include "../PrecompiledHeadersServer.h"
 #include "OrthancRestApi.h"
 
-#include "../DicomModification.h"
 #include "../FromDcmtkBridge.h"
 
 #include <glog/logging.h>
@@ -111,13 +110,11 @@
   }
 
 
-  static bool ParseModifyRequest(DicomModification& target,
-                                 const RestApi::PostCall& call)
+
+  bool OrthancRestApi::ParseModifyRequest(DicomModification& target,
+                                          const Json::Value& request)
   {
-    // curl http://localhost:8042/series/95a6e2bf-9296e2cc-bf614e2f-22b391ee-16e010e0/modify -X POST -d '{"Replace":{"InstitutionName":"My own clinic"}}'
-
-    Json::Value request;
-    if (call.ParseJsonRequest(request) && request.isObject())
+    if (request.isObject())
     {
       if (request.isMember("RemovePrivateTags"))
       {
@@ -143,8 +140,25 @@
   }
 
 
+  static bool ParseModifyRequest(DicomModification& target,
+                                 const RestApiPostCall& call)
+  {
+    // curl http://localhost:8042/series/95a6e2bf-9296e2cc-bf614e2f-22b391ee-16e010e0/modify -X POST -d '{"Replace":{"InstitutionName":"My own clinic"}}'
+
+    Json::Value request;
+    if (call.ParseJsonRequest(request))
+    {
+      return OrthancRestApi::ParseModifyRequest(target, request);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
   static bool ParseAnonymizationRequest(DicomModification& target,
-                                        RestApi::PostCall& call)
+                                        RestApiPostCall& call)
   {
     // curl http://localhost:8042/instances/6e67da51-d119d6ae-c5667437-87b9a8a5-0f07c49f/anonymize -X POST -d '{"Replace":{"PatientName":"hello","0010-0020":"world"},"Keep":["StudyDescription", "SeriesDescription"],"KeepPrivateTags": null,"Remove":["Modality"]}' > Anonymized.dcm
 
@@ -174,7 +188,8 @@
         ParseListOfTags(target, request["Keep"], TagOperation_Keep);
       }
 
-      if (target.GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName)
+      if (target.IsReplaced(DICOM_TAG_PATIENT_NAME) &&
+          target.GetReplacement(DICOM_TAG_PATIENT_NAME) == patientName)
       {
         // Overwrite the random Patient's Name by one that is more
         // user-friendly (provided none was specified by the user)
@@ -191,7 +206,7 @@
 
 
   static void AnonymizeOrModifyInstance(DicomModification& modification,
-                                        RestApi::PostCall& call)
+                                        RestApiPostCall& call)
   {
     std::string id = call.GetUriComponent("id", "");
 
@@ -207,7 +222,7 @@
                                         MetadataType metadataType,
                                         ChangeType changeType,
                                         ResourceType resourceType,
-                                        RestApi::PostCall& call)
+                                        RestApiPostCall& call)
   {
     bool isFirst = true;
     Json::Value result(Json::objectValue);
@@ -251,47 +266,55 @@
 
 
       /**
-       * Compute the resulting DICOM instance and store it into the Orthanc store.
+       * Compute the resulting DICOM instance.
        **/
 
       std::auto_ptr<ParsedDicomFile> modified(original.Clone());
       modification.Apply(*modified);
 
-      std::string modifiedInstance;
-      if (context.Store(modifiedInstance, *modified) != StoreStatus_Success)
-      {
-        LOG(ERROR) << "Error while storing a modified instance " << *it;
-        return;
-      }
+      DicomInstanceToStore toStore;
+      toStore.SetParsedDicomFile(*modified);
 
 
       /**
-       * Record metadata information (AnonymizedFrom/ModifiedFrom).
+       * Prepare the metadata information to associate with the
+       * resulting DICOM instance (AnonymizedFrom/ModifiedFrom).
        **/
 
       DicomInstanceHasher modifiedHasher = modified->GetHasher();
 
       if (originalHasher.HashSeries() != modifiedHasher.HashSeries())
       {
-        context.GetIndex().SetMetadata(modifiedHasher.HashSeries(), 
-                                       metadataType, originalHasher.HashSeries());
+        toStore.AddMetadata(ResourceType_Series, metadataType, originalHasher.HashSeries());
       }
 
       if (originalHasher.HashStudy() != modifiedHasher.HashStudy())
       {
-        context.GetIndex().SetMetadata(modifiedHasher.HashStudy(), 
-                                       metadataType, originalHasher.HashStudy());
+        toStore.AddMetadata(ResourceType_Study, metadataType, originalHasher.HashStudy());
       }
 
       if (originalHasher.HashPatient() != modifiedHasher.HashPatient())
       {
-        context.GetIndex().SetMetadata(modifiedHasher.HashPatient(), 
-                                       metadataType, originalHasher.HashPatient());
+        toStore.AddMetadata(ResourceType_Patient, metadataType, originalHasher.HashPatient());
       }
 
       assert(*it == originalHasher.HashInstance());
+      toStore.AddMetadata(ResourceType_Instance, metadataType, *it);
+
+
+      /**
+       * Store the resulting DICOM instance into the Orthanc store.
+       **/
+
+      std::string modifiedInstance;
+      if (context.Store(modifiedInstance, toStore) != StoreStatus_Success)
+      {
+        LOG(ERROR) << "Error while storing a modified instance " << *it;
+        return;
+      }
+
+      // Sanity checks in debug mode
       assert(modifiedInstance == modifiedHasher.HashInstance());
-      context.GetIndex().SetMetadata(modifiedInstance, metadataType, *it);
 
 
       /**
@@ -333,7 +356,7 @@
 
 
 
-  static void ModifyInstance(RestApi::PostCall& call)
+  static void ModifyInstance(RestApiPostCall& call)
   {
     DicomModification modification;
 
@@ -361,7 +384,7 @@
   }
 
 
-  static void AnonymizeInstance(RestApi::PostCall& call)
+  static void AnonymizeInstance(RestApiPostCall& call)
   {
     DicomModification modification;
 
@@ -374,7 +397,7 @@
 
   template <enum ChangeType changeType,
             enum ResourceType resourceType>
-  static void ModifyResource(RestApi::PostCall& call)
+  static void ModifyResource(RestApiPostCall& call)
   {
     DicomModification modification;
 
@@ -389,7 +412,7 @@
 
   template <enum ChangeType changeType,
             enum ResourceType resourceType>
-  static void AnonymizeResource(RestApi::PostCall& call)
+  static void AnonymizeResource(RestApiPostCall& call)
   {
     DicomModification modification;
 
@@ -401,7 +424,7 @@
   }
 
 
-  static void Create(RestApi::PostCall& call)
+  static void Create(RestApiPostCall& call)
   {
     // curl http://localhost:8042/tools/create-dicom -X POST -d '{"PatientName":"Hello^World"}'
     // curl http://localhost:8042/tools/create-dicom -X POST -d '{"PatientName":"Hello^World","PixelData":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDDcB53FulQAAAElJREFUGNNtj0sSAEEEQ1+U+185s1CtmRkblQ9CZldsKHJDk6DLGLJa6chjh0ooQmpjXMM86zPwydGEj6Ed/UGykkEM8X+p3u8/8LcOJIWLGeMAAAAASUVORK5CYII="}'
@@ -422,8 +445,11 @@
 
       modification.Apply(dicom);
 
+      DicomInstanceToStore toStore;
+      toStore.SetParsedDicomFile(dicom);
+
       std::string id;
-      StoreStatus status = OrthancRestApi::GetContext(call).Store(id, dicom);
+      StoreStatus status = OrthancRestApi::GetContext(call).Store(id, toStore);
 
       if (status == StoreStatus_Failure)
       {
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestApi.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -39,9 +39,9 @@
 
 namespace Orthanc
 {
-  void OrthancRestApi::AnswerStoredInstance(RestApi::PostCall& call,
+  void OrthancRestApi::AnswerStoredInstance(RestApiPostCall& call,
                                             const std::string& publicId,
-                                            StoreStatus status)
+                                            StoreStatus status) const
   {
     Json::Value result = Json::objectValue;
 
@@ -59,7 +59,7 @@
 
   // Upload of DICOM files through HTTP ---------------------------------------
 
-  static void UploadDicomFile(RestApi::PostCall& call)
+  static void UploadDicomFile(RestApiPostCall& call)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
 
@@ -71,8 +71,11 @@
 
     LOG(INFO) << "Receiving a DICOM file of " << postData.size() << " bytes through HTTP";
 
+    DicomInstanceToStore toStore;
+    toStore.SetBuffer(postData);
+
     std::string publicId;
-    StoreStatus status = context.Store(publicId, postData);
+    StoreStatus status = context.Store(publicId, toStore);
 
     OrthancRestApi::GetApi(call).AnswerStoredInstance(call, publicId, status);
   }
--- a/OrthancServer/OrthancRestApi/OrthancRestApi.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestApi.h	Thu Jul 10 11:42:32 2014 +0200
@@ -32,8 +32,9 @@
 
 #pragma once
 
+#include "../../Core/RestApi/RestApi.h"
+#include "../DicomModification.h"
 #include "../ServerContext.h"
-#include "../../Core/RestApi/RestApi.h"
 
 #include <set>
 
@@ -62,23 +63,26 @@
   public:
     OrthancRestApi(ServerContext& context);
 
-    static OrthancRestApi& GetApi(RestApi::Call& call)
+    static OrthancRestApi& GetApi(RestApiCall& call)
     {
       return dynamic_cast<OrthancRestApi&>(call.GetContext());
     }
 
-    static ServerContext& GetContext(RestApi::Call& call)
+    static ServerContext& GetContext(RestApiCall& call)
     {
       return GetApi(call).context_;
     }
 
-    static ServerIndex& GetIndex(RestApi::Call& call)
+    static ServerIndex& GetIndex(RestApiCall& call)
     {
       return GetContext(call).GetIndex();
     }
 
-    void AnswerStoredInstance(RestApi::PostCall& call,
+    void AnswerStoredInstance(RestApiPostCall& call,
                               const std::string& publicId,
-                              StoreStatus status);
+                              StoreStatus status) const;
+
+    static bool ParseModifyRequest(DicomModification& target,
+                                   const Json::Value& request);
   };
 }
--- a/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestArchive.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -38,6 +38,7 @@
 #include "../../Core/Uuid.h"
 
 #include <glog/logging.h>
+#include <stdio.h>
 
 #if defined(_MSC_VER)
 #define snprintf _snprintf
@@ -233,7 +234,7 @@
   }                                 
 
   template <enum ResourceType resourceType>
-  static void GetArchive(RestApi::GetCall& call)
+  static void GetArchive(RestApiGetCall& call)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
 
--- a/OrthancServer/OrthancRestApi/OrthancRestChanges.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestChanges.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -42,7 +42,7 @@
   static void GetSinceAndLimit(int64_t& since,
                                unsigned int& limit,
                                bool& last,
-                               const RestApi::GetCall& call)
+                               const RestApiGetCall& call)
   {
     static const unsigned int MAX_RESULTS = 100;
     
@@ -70,7 +70,7 @@
     }
   }
 
-  static void GetChanges(RestApi::GetCall& call)
+  static void GetChanges(RestApiGetCall& call)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
 
@@ -89,7 +89,7 @@
   }
 
 
-  static void DeleteChanges(RestApi::DeleteCall& call)
+  static void DeleteChanges(RestApiDeleteCall& call)
   {
     OrthancRestApi::GetIndex(call).DeleteChanges();
     call.GetOutput().AnswerBuffer("", "text/plain");
@@ -98,7 +98,7 @@
 
   // Exports API --------------------------------------------------------------
  
-  static void GetExports(RestApi::GetCall& call)
+  static void GetExports(RestApiGetCall& call)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
 
@@ -116,7 +116,7 @@
   }
 
 
-  static void DeleteExports(RestApi::DeleteCall& call)
+  static void DeleteExports(RestApiDeleteCall& call)
   {
     OrthancRestApi::GetIndex(call).DeleteExportedResources();
     call.GetOutput().AnswerBuffer("", "text/plain");
--- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -36,6 +36,9 @@
 #include "../OrthancInitialization.h"
 #include "../../Core/HttpClient.h"
 #include "../FromDcmtkBridge.h"
+#include "../Scheduler/ServerJob.h"
+#include "../Scheduler/StoreScuCommand.h"
+#include "../Scheduler/StorePeerCommand.h"
 
 #include <glog/logging.h>
 
@@ -65,7 +68,7 @@
     return true;
   }
 
-  static void DicomFindPatient(RestApi::PostCall& call)
+  static void DicomFindPatient(RestApiPostCall& call)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
 
@@ -87,7 +90,7 @@
     call.GetOutput().AnswerJson(result);
   }
 
-  static void DicomFindStudy(RestApi::PostCall& call)
+  static void DicomFindStudy(RestApiPostCall& call)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
 
@@ -115,7 +118,7 @@
     call.GetOutput().AnswerJson(result);
   }
 
-  static void DicomFindSeries(RestApi::PostCall& call)
+  static void DicomFindSeries(RestApiPostCall& call)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
 
@@ -144,7 +147,7 @@
     call.GetOutput().AnswerJson(result);
   }
 
-  static void DicomFindInstance(RestApi::PostCall& call)
+  static void DicomFindInstance(RestApiPostCall& call)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
 
@@ -174,7 +177,7 @@
     call.GetOutput().AnswerJson(result);
   }
 
-  static void DicomFind(RestApi::PostCall& call)
+  static void DicomFind(RestApiPostCall& call)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
 
@@ -248,7 +251,7 @@
 
   static bool GetInstancesToExport(std::list<std::string>& instances,
                                    const std::string& remote,
-                                   RestApi::PostCall& call)
+                                   RestApiPostCall& call)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
 
@@ -308,7 +311,7 @@
   }
 
 
-  static void DicomStore(RestApi::PostCall& call)
+  static void DicomStore(RestApiPostCall& call)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
 
@@ -321,17 +324,16 @@
     }
 
     RemoteModalityParameters p = Configuration::GetModalityUsingSymbolicName(remote);
-    ReusableDicomUserConnection::Locker locker(context.GetReusableDicomUserConnection(), p);
 
+    ServerJob job;
     for (std::list<std::string>::const_iterator 
            it = instances.begin(); it != instances.end(); ++it)
     {
-      LOG(INFO) << "Sending resource " << *it << " to modality \"" << remote << "\"";
+      job.AddCommand(new StoreScuCommand(context, p)).AddInput(*it);
+    }
 
-      std::string dicom;
-      context.ReadFile(dicom, *it, FileContentType_Dicom);
-      locker.GetConnection().Store(dicom);
-    }
+    job.SetDescription("HTTP request: Store-SCU to peer \"" + remote + "\"");
+    context.GetScheduler().SubmitAndWait(job);
 
     call.GetOutput().AnswerBuffer("{}", "application/json");
   }
@@ -345,7 +347,7 @@
     return peers.find(id) != peers.end();
   }
 
-  static void ListPeers(RestApi::GetCall& call)
+  static void ListPeers(RestApiGetCall& call)
   {
     OrthancRestApi::SetOfStrings peers;
     Configuration::GetListOfOrthancPeers(peers);
@@ -360,7 +362,7 @@
     call.GetOutput().AnswerJson(result);
   }
 
-  static void ListPeerOperations(RestApi::GetCall& call)
+  static void ListPeerOperations(RestApiGetCall& call)
   {
     OrthancRestApi::SetOfStrings peers;
     Configuration::GetListOfOrthancPeers(peers);
@@ -374,7 +376,7 @@
     }
   }
 
-  static void PeerStore(RestApi::PostCall& call)
+  static void PeerStore(RestApiPostCall& call)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
 
@@ -389,33 +391,15 @@
     OrthancPeerParameters peer;
     Configuration::GetOrthancPeer(peer, remote);
 
-    // Configure the HTTP client
-    HttpClient client;
-    if (peer.GetUsername().size() != 0 && 
-        peer.GetPassword().size() != 0)
-    {
-      client.SetCredentials(peer.GetUsername().c_str(), 
-                            peer.GetPassword().c_str());
-    }
-
-    client.SetUrl(peer.GetUrl() + "instances");
-    client.SetMethod(HttpMethod_Post);
-
-    // Loop over the instances that are to be sent
+    ServerJob job;
     for (std::list<std::string>::const_iterator 
            it = instances.begin(); it != instances.end(); ++it)
     {
-      LOG(INFO) << "Sending resource " << *it << " to peer \"" << remote << "\"";
-
-      context.ReadFile(client.AccessPostData(), *it, FileContentType_Dicom);
+      job.AddCommand(new StorePeerCommand(context, peer)).AddInput(*it);
+    }
 
-      std::string answer;
-      if (!client.Apply(answer))
-      {
-        LOG(ERROR) << "Unable to send resource " << *it << " to peer \"" << remote << "\"";
-        return;
-      }
-    }
+    job.SetDescription("HTTP request: POST to peer \"" + remote + "\"");
+    context.GetScheduler().SubmitAndWait(job);
 
     call.GetOutput().AnswerBuffer("{}", "application/json");
   }
@@ -429,7 +413,7 @@
     return modalities.find(id) != modalities.end();
   }
 
-  static void ListModalities(RestApi::GetCall& call)
+  static void ListModalities(RestApiGetCall& call)
   {
     OrthancRestApi::SetOfStrings modalities;
     Configuration::GetListOfDicomModalities(modalities);
@@ -445,7 +429,7 @@
   }
 
 
-  static void ListModalityOperations(RestApi::GetCall& call)
+  static void ListModalityOperations(RestApiGetCall& call)
   {
     OrthancRestApi::SetOfStrings modalities;
     Configuration::GetListOfDicomModalities(modalities);
@@ -465,7 +449,7 @@
   }
 
 
-  static void UpdateModality(RestApi::PutCall& call)
+  static void UpdateModality(RestApiPutCall& call)
   {
     Json::Value json;
     Json::Reader reader;
@@ -479,14 +463,14 @@
   }
 
 
-  static void DeleteModality(RestApi::DeleteCall& call)
+  static void DeleteModality(RestApiDeleteCall& call)
   {
     Configuration::RemoveModality(call.GetUriComponent("id", ""));
     call.GetOutput().AnswerBuffer("", "text/plain");
   }
 
 
-  static void UpdatePeer(RestApi::PutCall& call)
+  static void UpdatePeer(RestApiPutCall& call)
   {
     Json::Value json;
     Json::Reader reader;
@@ -500,7 +484,7 @@
   }
 
 
-  static void DeletePeer(RestApi::DeleteCall& call)
+  static void DeletePeer(RestApiDeleteCall& call)
   {
     Configuration::RemovePeer(call.GetUriComponent("id", ""));
     call.GetOutput().AnswerBuffer("", "text/plain");
--- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -43,7 +43,7 @@
   // List all the patients, studies, series or instances ----------------------
  
   template <enum ResourceType resourceType>
-  static void ListResources(RestApi::GetCall& call)
+  static void ListResources(RestApiGetCall& call)
   {
     Json::Value result;
     OrthancRestApi::GetIndex(call).GetAllUuids(result, resourceType);
@@ -51,7 +51,7 @@
   }
 
   template <enum ResourceType resourceType>
-  static void GetSingleResource(RestApi::GetCall& call)
+  static void GetSingleResource(RestApiGetCall& call)
   {
     Json::Value result;
     if (OrthancRestApi::GetIndex(call).LookupResource(result, call.GetUriComponent("id", ""), resourceType))
@@ -61,7 +61,7 @@
   }
 
   template <enum ResourceType resourceType>
-  static void DeleteSingleResource(RestApi::DeleteCall& call)
+  static void DeleteSingleResource(RestApiDeleteCall& call)
   {
     Json::Value result;
     if (OrthancRestApi::GetIndex(call).DeleteResource(result, call.GetUriComponent("id", ""), resourceType))
@@ -73,7 +73,7 @@
 
   // Get information about a single patient -----------------------------------
  
-  static void IsProtectedPatient(RestApi::GetCall& call)
+  static void IsProtectedPatient(RestApiGetCall& call)
   {
     std::string publicId = call.GetUriComponent("id", "");
     bool isProtected = OrthancRestApi::GetIndex(call).IsProtectedPatient(publicId);
@@ -81,7 +81,7 @@
   }
 
 
-  static void SetPatientProtection(RestApi::PutCall& call)
+  static void SetPatientProtection(RestApiPutCall& call)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
 
@@ -107,7 +107,7 @@
 
   // Get information about a single instance ----------------------------------
  
-  static void GetInstanceFile(RestApi::GetCall& call)
+  static void GetInstanceFile(RestApiGetCall& call)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
 
@@ -116,7 +116,7 @@
   }
 
 
-  static void ExportInstanceFile(RestApi::PostCall& call)
+  static void ExportInstanceFile(RestApiPostCall& call)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
 
@@ -132,7 +132,7 @@
 
 
   template <bool simplify>
-  static void GetInstanceTags(RestApi::GetCall& call)
+  static void GetInstanceTags(RestApiGetCall& call)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
 
@@ -153,8 +153,23 @@
     }
   }
 
+
+  static void GetInstanceTagsBis(RestApiGetCall& call)
+  {
+    bool simplify = call.HasArgument("simplify");
+
+    if (simplify)
+    {
+      GetInstanceTags<true>(call);
+    }
+    else
+    {
+      GetInstanceTags<false>(call);
+    }
+  }
+
   
-  static void ListFrames(RestApi::GetCall& call)
+  static void ListFrames(RestApiGetCall& call)
   {
     Json::Value instance;
     if (OrthancRestApi::GetIndex(call).LookupResource(instance, call.GetUriComponent("id", ""), ResourceType_Instance))
@@ -182,7 +197,7 @@
 
 
   template <enum ImageExtractionMode mode>
-  static void GetImage(RestApi::GetCall& call)
+  static void GetImage(RestApiGetCall& call)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
 
@@ -230,7 +245,7 @@
   }
 
 
-  static void GetMatlabImage(RestApi::GetCall& call)
+  static void GetMatlabImage(RestApiGetCall& call)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
 
@@ -264,7 +279,7 @@
 
 
 
-  static void GetResourceStatistics(RestApi::GetCall& call)
+  static void GetResourceStatistics(RestApiGetCall& call)
   {
     std::string publicId = call.GetUriComponent("id", "");
     Json::Value result;
@@ -276,14 +291,14 @@
 
   // Handling of metadata -----------------------------------------------------
 
-  static void CheckValidResourceType(RestApi::Call& call)
+  static void CheckValidResourceType(RestApiCall& call)
   {
     std::string resourceType = call.GetUriComponent("resourceType", "");
     StringToResourceType(resourceType.c_str());
   }
 
 
-  static void ListMetadata(RestApi::GetCall& call)
+  static void ListMetadata(RestApiGetCall& call)
   {
     CheckValidResourceType(call);
     
@@ -303,7 +318,7 @@
   }
 
 
-  static void GetMetadata(RestApi::GetCall& call)
+  static void GetMetadata(RestApiGetCall& call)
   {
     CheckValidResourceType(call);
     
@@ -319,7 +334,7 @@
   }
 
 
-  static void DeleteMetadata(RestApi::DeleteCall& call)
+  static void DeleteMetadata(RestApiDeleteCall& call)
   {
     CheckValidResourceType(call);
 
@@ -337,7 +352,7 @@
   }
 
 
-  static void SetMetadata(RestApi::PutCall& call)
+  static void SetMetadata(RestApiPutCall& call)
   {
     CheckValidResourceType(call);
 
@@ -360,7 +375,7 @@
 
   // Handling of attached files -----------------------------------------------
 
-  static void ListAttachments(RestApi::GetCall& call)
+  static void ListAttachments(RestApiGetCall& call)
   {
     std::string resourceType = call.GetUriComponent("resourceType", "");
     std::string publicId = call.GetUriComponent("id", "");
@@ -379,7 +394,7 @@
   }
 
 
-  static bool GetAttachmentInfo(FileInfo& info, RestApi::Call& call)
+  static bool GetAttachmentInfo(FileInfo& info, RestApiCall& call)
   {
     CheckValidResourceType(call);
  
@@ -391,7 +406,7 @@
   }
 
 
-  static void GetAttachmentOperations(RestApi::GetCall& call)
+  static void GetAttachmentOperations(RestApiGetCall& call)
   {
     FileInfo info;
     if (GetAttachmentInfo(info, call))
@@ -427,7 +442,7 @@
 
   
   template <int uncompress>
-  static void GetAttachmentData(RestApi::GetCall& call)
+  static void GetAttachmentData(RestApiGetCall& call)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
 
@@ -444,7 +459,7 @@
   }
 
 
-  static void GetAttachmentSize(RestApi::GetCall& call)
+  static void GetAttachmentSize(RestApiGetCall& call)
   {
     FileInfo info;
     if (GetAttachmentInfo(info, call))
@@ -454,7 +469,7 @@
   }
 
 
-  static void GetAttachmentCompressedSize(RestApi::GetCall& call)
+  static void GetAttachmentCompressedSize(RestApiGetCall& call)
   {
     FileInfo info;
     if (GetAttachmentInfo(info, call))
@@ -464,7 +479,7 @@
   }
 
 
-  static void GetAttachmentMD5(RestApi::GetCall& call)
+  static void GetAttachmentMD5(RestApiGetCall& call)
   {
     FileInfo info;
     if (GetAttachmentInfo(info, call) &&
@@ -475,7 +490,7 @@
   }
 
 
-  static void GetAttachmentCompressedMD5(RestApi::GetCall& call)
+  static void GetAttachmentCompressedMD5(RestApiGetCall& call)
   {
     FileInfo info;
     if (GetAttachmentInfo(info, call) &&
@@ -486,7 +501,7 @@
   }
 
 
-  static void VerifyAttachment(RestApi::PostCall& call)
+  static void VerifyAttachment(RestApiPostCall& call)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
     CheckValidResourceType(call);
@@ -540,7 +555,7 @@
   }
 
 
-  static void UploadAttachment(RestApi::PutCall& call)
+  static void UploadAttachment(RestApiPutCall& call)
   {
     ServerContext& context = OrthancRestApi::GetContext(call);
     CheckValidResourceType(call);
@@ -560,7 +575,7 @@
   }
 
 
-  static void DeleteAttachment(RestApi::DeleteCall& call)
+  static void DeleteAttachment(RestApiDeleteCall& call)
   {
     CheckValidResourceType(call);
 
@@ -580,7 +595,7 @@
 
   // Raw access to the DICOM tags of an instance ------------------------------
 
-  static void GetRawContent(RestApi::GetCall& call)
+  static void GetRawContent(RestApiGetCall& call)
   {
     std::string id = call.GetUriComponent("id", "");
 
@@ -591,6 +606,160 @@
 
 
 
+  static bool ExtractSharedTags(Json::Value& shared,
+                                ServerContext& context,
+                                const std::string& publicId)
+  {
+    // Retrieve all the instances of this patient/study/series
+    typedef std::list<std::string> Instances;
+    Instances instances;
+    context.GetIndex().GetChildInstances(instances, publicId);  // (*)
+
+    // Loop over the instances
+    bool isFirst = true;
+    shared = Json::objectValue;
+
+    for (Instances::const_iterator it = instances.begin();
+         it != instances.end(); it++)
+    {
+      // Get the tags of the current instance, in the simplified format
+      Json::Value tags;
+
+      try
+      {
+        context.ReadJson(tags, *it);
+      }
+      catch (OrthancException&)
+      {
+        // Race condition: This instance has been removed since
+        // (*). Ignore this instance.
+        continue;
+      }
+
+      if (tags.type() != Json::objectValue)
+      {
+        return false;   // Error
+      }
+
+      // Only keep the tags that are mapped to a string
+      Json::Value::Members members = tags.getMemberNames();
+      for (size_t i = 0; i < members.size(); i++)
+      {
+        const Json::Value& tag = tags[members[i]];
+        if (tag.type() != Json::objectValue ||
+            tag["Type"].type() != Json::stringValue ||
+            tag["Type"].asString() != "String")
+        {
+          tags.removeMember(members[i]);
+        }
+      }
+
+      if (isFirst)
+      {
+        // This is the first instance, keep its tags as such
+        shared = tags;
+        isFirst = false;
+      }
+      else
+      {
+        // Loop over all the members of the shared tags extracted so
+        // far. If the value of one of these tags does not match its
+        // value in the current instance, remove it.
+        members = shared.getMemberNames();
+        for (size_t i = 0; i < members.size(); i++)
+        {
+          if (!tags.isMember(members[i]) ||
+              tags[members[i]]["Value"].asString() != shared[members[i]]["Value"].asString())
+          {
+            shared.removeMember(members[i]);
+          }
+        }
+      }
+    }
+
+    return true;
+  }
+
+
+  static void GetSharedTags(RestApiGetCall& call)
+  {
+    ServerContext& context = OrthancRestApi::GetContext(call);
+    std::string publicId = call.GetUriComponent("id", "");
+    bool simplify = call.HasArgument("simplify");
+
+    Json::Value sharedTags;
+    if (ExtractSharedTags(sharedTags, context, publicId))
+    {
+      // Success: Send the value of the shared tags
+      if (simplify)
+      {
+        Json::Value simplified;
+        SimplifyTags(simplified, sharedTags);
+        call.GetOutput().AnswerJson(simplified);
+      }
+      else
+      {
+        call.GetOutput().AnswerJson(sharedTags);
+      }
+    }
+  }
+
+
+  template <enum ResourceType resourceType>
+  static void GetModule(RestApiGetCall& call)
+  {
+    ServerContext& context = OrthancRestApi::GetContext(call);
+    std::string publicId = call.GetUriComponent("id", "");
+    bool simplify = call.HasArgument("simplify");
+
+    typedef std::set<DicomTag> Module;
+    Module module;
+    DicomTag::GetTagsForModule(module, resourceType);
+
+    Json::Value tags;
+
+    if (resourceType != ResourceType_Instance)
+    {
+      // Retrieve all the instances of this patient/study/series
+      typedef std::list<std::string> Instances;
+      Instances instances;
+      context.GetIndex().GetChildInstances(instances, publicId);
+
+      if (instances.empty())
+      {
+        return;   // Error: No instance (should never happen)
+      }
+
+      // Select one child instance
+      publicId = instances.front();
+    }
+
+    context.ReadJson(tags, publicId);
+    
+    // Filter the tags of the instance according to the module
+    Json::Value result = Json::objectValue;
+    for (Module::const_iterator it = module.begin(); it != module.end(); it++)
+    {
+      std::string s = it->Format();
+      if (tags.isMember(s))
+      {
+        result[s] = tags[s];
+      }      
+    }
+
+    if (simplify)
+    {
+      Json::Value simplified;
+      SimplifyTags(simplified, result);
+      call.GetOutput().AnswerJson(simplified);
+    }
+    else
+    {
+      call.GetOutput().AnswerJson(result);
+    }
+  }
+
+
   void OrthancRestApi::RegisterResources()
   {
     Register("/instances", ListResources<ResourceType_Instance>);
@@ -612,9 +781,18 @@
     Register("/studies/{id}/statistics", GetResourceStatistics);
     Register("/series/{id}/statistics", GetResourceStatistics);
 
+    Register("/patients/{id}/shared-tags", GetSharedTags);
+    Register("/series/{id}/shared-tags", GetSharedTags);
+    Register("/studies/{id}/shared-tags", GetSharedTags);
+
+    Register("/instances/{id}/module", GetModule<ResourceType_Instance>);
+    Register("/patients/{id}/module", GetModule<ResourceType_Patient>);
+    Register("/series/{id}/module", GetModule<ResourceType_Series>);
+    Register("/studies/{id}/module", GetModule<ResourceType_Study>);
+
     Register("/instances/{id}/file", GetInstanceFile);
     Register("/instances/{id}/export", ExportInstanceFile);
-    Register("/instances/{id}/tags", GetInstanceTags<false>);
+    Register("/instances/{id}/tags", GetInstanceTagsBis);
     Register("/instances/{id}/simplified-tags", GetInstanceTags<true>);
     Register("/instances/{id}/frames", ListFrames);
 
--- a/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/OrthancRestApi/OrthancRestSystem.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -43,12 +43,12 @@
 {
   // System information -------------------------------------------------------
 
-  static void ServeRoot(RestApi::GetCall& call)
+  static void ServeRoot(RestApiGetCall& call)
   {
     call.GetOutput().Redirect("app/explorer.html");
   }
  
-  static void GetSystemInformation(RestApi::GetCall& call)
+  static void GetSystemInformation(RestApiGetCall& call)
   {
     Json::Value result = Json::objectValue;
 
@@ -58,14 +58,14 @@
     call.GetOutput().AnswerJson(result);
   }
 
-  static void GetStatistics(RestApi::GetCall& call)
+  static void GetStatistics(RestApiGetCall& call)
   {
     Json::Value result = Json::objectValue;
     OrthancRestApi::GetIndex(call).ComputeStatistics(result);
     call.GetOutput().AnswerJson(result);
   }
 
-  static void GenerateUid(RestApi::GetCall& call)
+  static void GenerateUid(RestApiGetCall& call)
   {
     std::string level = call.GetArgument("level", "");
     if (level == "patient")
@@ -86,15 +86,20 @@
     }
   }
 
-  static void ExecuteScript(RestApi::PostCall& call)
+  static void ExecuteScript(RestApiPostCall& call)
   {
     std::string result;
     ServerContext& context = OrthancRestApi::GetContext(call);
-    context.GetLuaContext().Execute(result, call.GetPostBody());
+
+    {
+      ServerContext::LuaContextLocker locker(context);
+      locker.GetLua().Execute(result, call.GetPostBody());
+    }
+
     call.GetOutput().AnswerBuffer(result, "text/plain");
   }
 
-  static void GetNowIsoString(RestApi::GetCall& call)
+  static void GetNowIsoString(RestApiGetCall& call)
   {
     call.GetOutput().AnswerBuffer(Toolbox::GetNowIsoString(), "text/plain");
   }
--- a/OrthancServer/ParsedDicomFile.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/ParsedDicomFile.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -146,6 +146,7 @@
   struct ParsedDicomFile::PImpl
   {
     std::auto_ptr<DcmFileFormat> file_;
+    Encoding encoding_;
   };
 
 
@@ -170,6 +171,8 @@
     }
     pimpl_->file_->loadAllDataIntoMemory();
     pimpl_->file_->transferEnd();
+
+    pimpl_->encoding_ = FromDcmtkBridge::DetectEncoding(*pimpl_->file_->getDataset());
   }
 
 
@@ -279,7 +282,7 @@
 
       if (cond.good())
       {
-        output.GetLowLevelOutput().Send(&buffer[0], nbytes);
+        output.GetLowLevelOutput().SendBodyData(&buffer[0], nbytes);
         offset += nbytes;
       }
       else
@@ -753,24 +756,42 @@
 
 
 
-  void ParsedDicomFile::RemovePrivateTags()
+  void ParsedDicomFile::RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep)
   {
+    DcmDataset& dataset = *pimpl_->file_->getDataset();
+
+    // Loop over the dataset to detect its private tags
     typedef std::list<DcmElement*> Tags;
-
     Tags privateTags;
 
-    DcmDataset& dataset = *pimpl_->file_->getDataset();
     for (unsigned long i = 0; i < dataset.card(); i++)
     {
       DcmElement* element = dataset.getElement(i);
       DcmTag tag(element->getTag());
-      if (!strcmp("PrivateCreator", tag.getTagName()) ||  // TODO - This may change with future versions of DCMTK
-          tag.getPrivateCreator() != NULL)
+
+      // Is this a private tag?
+      if (FromDcmtkBridge::IsPrivateTag(tag))
       {
-        privateTags.push_back(element);
+        bool remove = true;
+
+        // Check whether this private tag is to be kept
+        if (toKeep != NULL)
+        {
+          DicomTag tmp = FromDcmtkBridge::Convert(tag);
+          if (toKeep->find(tmp) != toKeep->end())
+          {
+            remove = false;  // Keep it
+          }
+        }
+            
+        if (remove)
+        {
+          privateTags.push_back(element);
+        }
       }
     }
 
+    // Loop over the detected private tags to remove them
     for (Tags::iterator it = privateTags.begin(); 
          it != privateTags.end(); ++it)
     {
@@ -852,7 +873,7 @@
   void ParsedDicomFile::Answer(RestApiOutput& output)
   {
     std::string serialized;
-    if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, pimpl_->file_->getDataset()))
+    if (FromDcmtkBridge::SaveToMemoryBuffer(serialized, *pimpl_->file_->getDataset()))
     {
       output.AnswerBuffer(serialized, CONTENT_TYPE_OCTET_STREAM);
     }
@@ -872,7 +893,7 @@
       return false;
     }
 
-    std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(*element));
+    std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(*element, pimpl_->encoding_));
 
     if (v.get() == NULL)
     {
@@ -887,7 +908,6 @@
   }
 
 
-
   DicomInstanceHasher ParsedDicomFile::GetHasher()
   {
     std::string patientId, studyUid, seriesUid, instanceUid;
@@ -904,98 +924,6 @@
   }
 
 
-  static void StoreElement(Json::Value& target,
-                           DcmElement& element,
-                           unsigned int maxStringLength);
-
-  static void StoreItem(Json::Value& target,
-                        DcmItem& item,
-                        unsigned int maxStringLength)
-  {
-    target = Json::Value(Json::objectValue);
-
-    for (unsigned long i = 0; i < item.card(); i++)
-    {
-      DcmElement* element = item.getElement(i);
-      StoreElement(target, *element, maxStringLength);
-    }
-  }
-
-
-  static void StoreElement(Json::Value& target,
-                           DcmElement& element,
-                           unsigned int maxStringLength)
-  {
-    assert(target.type() == Json::objectValue);
-
-    DicomTag tag(FromDcmtkBridge::GetTag(element));
-    const std::string formattedTag = tag.Format();
-
-#if 0
-    const std::string tagName = FromDcmtkBridge::GetName(tag);
-#else
-    // This version of the code gives access to the name of the private tags
-    DcmTag tagbis(element.getTag());
-    const std::string tagName(tagbis.getTagName());      
-#endif
-
-    if (element.isLeaf())
-    {
-      Json::Value value(Json::objectValue);
-      value["Name"] = tagName;
-
-      if (tagbis.getPrivateCreator() != NULL)
-      {
-        value["PrivateCreator"] = tagbis.getPrivateCreator();
-      }
-
-      std::auto_ptr<DicomValue> v(FromDcmtkBridge::ConvertLeafElement(element));
-      if (v->IsNull())
-      {
-        value["Type"] = "Null";
-        value["Value"] = Json::nullValue;
-      }
-      else
-      {
-        std::string s = v->AsString();
-        if (maxStringLength == 0 ||
-            s.size() <= maxStringLength)
-        {
-          value["Type"] = "String";
-          value["Value"] = s;
-        }
-        else
-        {
-          value["Type"] = "TooLong";
-          value["Value"] = Json::nullValue;
-        }
-      }
-
-      target[formattedTag] = value;
-    }
-    else
-    {
-      Json::Value children(Json::arrayValue);
-
-      // "All subclasses of DcmElement except for DcmSequenceOfItems
-      // are leaf nodes, while DcmSequenceOfItems, DcmItem, DcmDataset
-      // etc. are not." The following cast is thus OK.
-      DcmSequenceOfItems& sequence = dynamic_cast<DcmSequenceOfItems&>(element);
-
-      for (unsigned long i = 0; i < sequence.card(); i++)
-      {
-        DcmItem* child = sequence.getItem(i);
-        Json::Value& v = children.append(Json::objectValue);
-        StoreItem(v, *child, maxStringLength);
-      }  
-
-      target[formattedTag]["Name"] = tagName;
-      target[formattedTag]["Type"] = "Sequence";
-      target[formattedTag]["Value"] = children;
-    }
-  }
-
-
   template <typename T>
   static void ExtractPngImageTruncate(std::string& result,
                                       DicomIntegerPixelAccessor& accessor,
@@ -1028,7 +956,7 @@
 
   void ParsedDicomFile::SaveToMemoryBuffer(std::string& buffer)
   {
-    FromDcmtkBridge::SaveToMemoryBuffer(buffer, pimpl_->file_->getDataset());
+    FromDcmtkBridge::SaveToMemoryBuffer(buffer, *pimpl_->file_->getDataset());
   }
 
 
@@ -1044,6 +972,7 @@
   ParsedDicomFile::ParsedDicomFile() : pimpl_(new PImpl)
   {
     pimpl_->file_.reset(new DcmFileFormat);
+    pimpl_->encoding_ = Encoding_Ascii;
     Replace(DICOM_TAG_PATIENT_ID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Patient));
     Replace(DICOM_TAG_STUDY_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study));
     Replace(DICOM_TAG_SERIES_INSTANCE_UID, FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Series));
@@ -1073,6 +1002,8 @@
     pimpl_(new PImpl)
   {
     pimpl_->file_.reset(dynamic_cast<DcmFileFormat*>(other.pimpl_->file_->clone()));
+
+    pimpl_->encoding_ = other.pimpl_->encoding_;
   }
 
 
@@ -1241,15 +1172,15 @@
     switch (mode)
     {
       case ImageExtractionMode_UInt8:
-        ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_Grayscale8);
+        ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_Grayscale8, false);
         break;
 
       case ImageExtractionMode_UInt16:
-        ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_Grayscale16);
+        ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_Grayscale16, false);
         break;
 
       case ImageExtractionMode_Int16:
-        ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_SignedGrayscale16);
+        ok = DicomImageDecoder::DecodeAndTruncate(result, dataset, frame, PixelFormat_SignedGrayscale16, false);
         break;
 
       case ImageExtractionMode_Preview:
@@ -1279,4 +1210,9 @@
     writer.WriteToMemory(result, accessor);
   }
 
+
+  Encoding ParsedDicomFile::GetEncoding() const
+  {
+    return pimpl_->encoding_;
+  }
 }
--- a/OrthancServer/ParsedDicomFile.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/ParsedDicomFile.h	Thu Jul 10 11:42:32 2014 +0200
@@ -51,6 +51,8 @@
     void Setup(const char* content,
                size_t size);
 
+    void RemovePrivateTagsInternal(const std::set<DicomTag>* toKeep);
+
   public:
     ParsedDicomFile();  // Create a minimal DICOM instance
 
@@ -79,7 +81,15 @@
                  const std::string& value,
                  DicomReplaceMode mode = DicomReplaceMode_InsertIfAbsent);
 
-    void RemovePrivateTags();
+    void RemovePrivateTags()
+    {
+      RemovePrivateTagsInternal(NULL);
+    }
+
+    void RemovePrivateTags(const std::set<DicomTag>& toKeep)
+    {
+      RemovePrivateTagsInternal(&toKeep);
+    }
 
     bool GetTagValue(std::string& value,
                      const DicomTag& tag);
@@ -104,6 +114,8 @@
     void ExtractPngImage(std::string& result,
                          unsigned int frame,
                          ImageExtractionMode mode);
+
+    Encoding GetEncoding() const;
   };
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Scheduler/DeleteInstanceCommand.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,53 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "DeleteInstanceCommand.h"
+
+#include <glog/logging.h>
+
+namespace Orthanc
+{
+  bool DeleteInstanceCommand::Apply(ListOfStrings& outputs,
+                                    const ListOfStrings& inputs)
+  {
+    for (ListOfStrings::const_iterator
+           it = inputs.begin(); it != inputs.end(); ++it)
+    {
+      LOG(INFO) << "Deleting instance " << *it;
+
+      Json::Value tmp;
+      context_.GetIndex().DeleteResource(tmp, *it, ResourceType_Instance);
+    }
+
+    return true;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Scheduler/DeleteInstanceCommand.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,53 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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 "IServerCommand.h"
+#include "../ServerContext.h"
+
+namespace Orthanc
+{
+  class DeleteInstanceCommand : public IServerCommand
+  {
+  private:
+    ServerContext& context_;
+
+  public:
+    DeleteInstanceCommand(ServerContext& context) : context_(context)
+    {
+    }
+
+    virtual bool Apply(ListOfStrings& outputs,
+                       const ListOfStrings& inputs);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Scheduler/IServerCommand.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,53 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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 <list>
+#include <string>
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class IServerCommand : public boost::noncopyable
+  {
+  public:
+    typedef std::list<std::string>  ListOfStrings;
+
+    virtual ~IServerCommand()
+    {
+    }
+
+    virtual bool Apply(ListOfStrings& outputs,
+                       const ListOfStrings& inputs) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Scheduler/ModifyInstanceCommand.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,69 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "ModifyInstanceCommand.h"
+
+#include <glog/logging.h>
+
+namespace Orthanc
+{
+  bool ModifyInstanceCommand::Apply(ListOfStrings& outputs,
+                                    const ListOfStrings& inputs)
+  {
+    for (ListOfStrings::const_iterator
+           it = inputs.begin(); it != inputs.end(); ++it)
+    {
+      LOG(INFO) << "Modifying resource " << *it;
+
+      std::auto_ptr<ParsedDicomFile> modified;
+
+      {
+        ServerContext::DicomCacheLocker lock(context_, *it);
+        modified.reset(lock.GetDicom().Clone());
+      }
+
+      modification_.Apply(*modified);
+
+      DicomInstanceToStore toStore;
+      toStore.SetParsedDicomFile(*modified);
+      // TODO other metadata
+      toStore.AddMetadata(ResourceType_Instance, MetadataType_ModifiedFrom, *it);
+
+      std::string modifiedId;
+      context_.Store(modifiedId, toStore);
+
+      outputs.push_back(modifiedId);
+    }
+
+    return true;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Scheduler/ModifyInstanceCommand.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,66 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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 "IServerCommand.h"
+#include "../ServerContext.h"
+#include "../DicomModification.h"
+
+namespace Orthanc
+{
+  class ModifyInstanceCommand : public IServerCommand
+  {
+  private:
+    ServerContext& context_;
+    DicomModification modification_;
+
+  public:
+    ModifyInstanceCommand(ServerContext& context) :
+      context_(context)
+    {
+    }
+
+    DicomModification& GetModification()
+    {
+      return modification_;
+    }
+
+    const DicomModification& GetModification() const
+    {
+      return modification_;
+    }
+
+    virtual bool Apply(ListOfStrings& outputs,
+                       const ListOfStrings& inputs);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Scheduler/ServerCommandInstance.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,97 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "ServerCommandInstance.h"
+
+#include "../../Core/OrthancException.h"
+
+namespace Orthanc
+{
+  bool ServerCommandInstance::Execute(IListener& listener)
+  {
+    ListOfStrings outputs;
+
+    bool success = false;
+
+    try
+    {
+      if (command_->Apply(outputs, inputs_))
+      {
+        success = true;
+      }
+    }
+    catch (OrthancException&)
+    {
+    }
+
+    if (!success)
+    {
+      listener.SignalFailure(jobId_);
+      return true;
+    }
+
+    for (std::list<ServerCommandInstance*>::iterator
+           it = next_.begin(); it != next_.end(); it++)
+    {
+      for (ListOfStrings::const_iterator
+             output = outputs.begin(); output != outputs.end(); output++)
+      {
+        (*it)->AddInput(*output);
+      }
+    }
+
+    listener.SignalSuccess(jobId_);
+    return true;
+  }
+
+
+  ServerCommandInstance::ServerCommandInstance(IServerCommand *command,
+                                               const std::string& jobId) : 
+    command_(command), 
+    jobId_(jobId),
+    connectedToSink_(false)
+  {
+    if (command_ == NULL)
+    {
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  ServerCommandInstance::~ServerCommandInstance()
+  {
+    if (command_ != NULL)
+    {
+      delete command_;
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Scheduler/ServerCommandInstance.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,104 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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 "../../Core/IDynamicObject.h"
+#include "IServerCommand.h"
+
+namespace Orthanc
+{
+  class ServerCommandInstance : public IDynamicObject
+  {
+    friend class ServerScheduler;
+
+  public:
+    class IListener
+    {
+    public:
+      virtual ~IListener()
+      {
+      }
+
+      virtual void SignalSuccess(const std::string& jobId) = 0;
+
+      virtual void SignalFailure(const std::string& jobId) = 0;
+    };
+
+  private:
+    typedef IServerCommand::ListOfStrings  ListOfStrings;
+
+    IServerCommand *command_;
+    std::string jobId_;
+    ListOfStrings inputs_;
+    std::list<ServerCommandInstance*> next_;
+    bool connectedToSink_;
+
+    bool Execute(IListener& listener);
+
+  public:
+    ServerCommandInstance(IServerCommand *command,
+                          const std::string& jobId);
+
+    virtual ~ServerCommandInstance();
+
+    const std::string& GetJobId() const
+    {
+      return jobId_;
+    }
+
+    void AddInput(const std::string& input)
+    {
+      inputs_.push_back(input);
+    }
+
+    void ConnectOutput(ServerCommandInstance& next)
+    {
+      next_.push_back(&next);
+    }
+
+    void SetConnectedToSink(bool connected = true)
+    {
+      connectedToSink_ = connected;
+    }
+
+    bool IsConnectedToSink() const
+    {
+      return connectedToSink_;
+    }
+
+    const std::list<ServerCommandInstance*>& GetNextCommands() const
+    {
+      return next_;
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Scheduler/ServerJob.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,146 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "ServerJob.h"
+
+#include "../../Core/OrthancException.h"
+#include "../../Core/Toolbox.h"
+#include "../../Core/Uuid.h"
+
+namespace Orthanc
+{
+  void ServerJob::CheckOrdering()
+  {
+    std::map<ServerCommandInstance*, unsigned int> index;
+
+    unsigned int count = 0;
+    for (std::list<ServerCommandInstance*>::const_iterator
+           it = filters_.begin(); it != filters_.end(); it++)
+    {
+      index[*it] = count++;
+    }
+
+    for (std::list<ServerCommandInstance*>::const_iterator
+           it = filters_.begin(); it != filters_.end(); it++)
+    {
+      const std::list<ServerCommandInstance*>& nextCommands = (*it)->GetNextCommands();
+
+      for (std::list<ServerCommandInstance*>::const_iterator
+             next = nextCommands.begin(); next != nextCommands.end(); next++)
+      {
+        if (index.find(*next) == index.end() ||
+            index[*next] <= index[*it])
+        {
+          // You must reorder your calls to "ServerJob::AddCommand"
+          throw OrthancException("Bad ordering of filters in a job");
+        }
+      }
+    }
+  }
+
+
+  size_t ServerJob::Submit(SharedMessageQueue& target,
+                           ServerCommandInstance::IListener& listener)
+  {
+    if (submitted_)
+    {
+      // This job has already been submitted
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    CheckOrdering();
+
+    size_t size = filters_.size();
+
+    for (std::list<ServerCommandInstance*>::iterator 
+           it = filters_.begin(); it != filters_.end(); it++)
+    {
+      target.Enqueue(*it);
+    }
+
+    filters_.clear();
+    submitted_ = true;
+
+    return size;
+  }
+
+
+  ServerJob::ServerJob()
+  {
+    jobId_ = Toolbox::GenerateUuid();      
+    submitted_ = false;
+    description_ = "no description";
+  }
+
+
+  ServerJob::~ServerJob()
+  {
+    for (std::list<ServerCommandInstance*>::iterator
+           it = filters_.begin(); it != filters_.end(); it++)
+    {
+      delete *it;
+    }
+
+    for (std::list<IDynamicObject*>::iterator
+           it = payloads_.begin(); it != payloads_.end(); it++)
+    {
+      delete *it;
+    }
+  }
+
+
+  ServerCommandInstance& ServerJob::AddCommand(IServerCommand* filter)
+  {
+    if (submitted_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    filters_.push_back(new ServerCommandInstance(filter, jobId_));
+      
+    return *filters_.back();
+  }
+
+
+  IDynamicObject& ServerJob::AddPayload(IDynamicObject* payload)
+  {
+    if (submitted_)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+
+    payloads_.push_back(payload);
+      
+    return *filters_.back();
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Scheduler/ServerJob.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,82 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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 "ServerCommandInstance.h"
+#include "../../Core/MultiThreading/SharedMessageQueue.h"
+
+namespace Orthanc
+{
+  class ServerJob
+  {
+    friend class ServerScheduler;
+
+  private:
+    std::list<ServerCommandInstance*> filters_;
+    std::list<IDynamicObject*> payloads_;
+    std::string jobId_;
+    bool submitted_;
+    std::string description_;
+
+    void CheckOrdering();
+
+    size_t Submit(SharedMessageQueue& target,
+                  ServerCommandInstance::IListener& listener);
+
+  public:
+    ServerJob();
+
+    ~ServerJob();
+
+    const std::string& GetId() const
+    {
+      return jobId_;
+    }
+
+    void SetDescription(const std::string& description)
+    {
+      description_ = description;
+    }
+
+    const std::string& GetDescription() const
+    {
+      return description_;
+    }
+
+    ServerCommandInstance& AddCommand(IServerCommand* filter);
+
+    // Take the ownership of a payload to a job. This payload will be
+    // automatically freed when the job succeeds or fails.
+    IDynamicObject& AddPayload(IDynamicObject* payload);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Scheduler/ServerScheduler.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,336 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "ServerScheduler.h"
+
+#include "../../Core/OrthancException.h"
+
+#include <glog/logging.h>
+
+namespace Orthanc
+{
+  namespace
+  {
+    // Anonymous namespace to avoid clashes between compilation modules
+    class Sink : public IServerCommand
+    {
+    private:
+      ListOfStrings& target_;
+
+    public:
+      Sink(ListOfStrings& target) : target_(target)
+      {
+      }
+
+      virtual bool Apply(ListOfStrings& outputs,
+                         const ListOfStrings& inputs)
+      {
+        for (ListOfStrings::const_iterator 
+               it = inputs.begin(); it != inputs.end(); it++)
+        {
+          target_.push_back(*it);
+        }
+
+        return true;
+      }    
+    };
+  }
+
+
+  ServerScheduler::JobInfo& ServerScheduler::GetJobInfo(const std::string& jobId)
+  {
+    Jobs::iterator info = jobs_.find(jobId);
+
+    if (info == jobs_.end())
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+    return info->second;
+  }
+
+
+  void ServerScheduler::SignalSuccess(const std::string& jobId)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    JobInfo& info = GetJobInfo(jobId);
+    info.success_++;
+
+    assert(info.failures_ == 0);
+
+    if (info.success_ >= info.size_)
+    {
+      if (info.watched_)
+      {
+        watchedJobStatus_[jobId] = JobStatus_Success;
+        watchedJobFinished_.notify_all();
+      }
+
+      LOG(INFO) << "Job successfully finished (" << info.description_ << ")";
+      jobs_.erase(jobId);
+
+      availableJob_.Release();
+    }
+  }
+
+
+  void ServerScheduler::SignalFailure(const std::string& jobId)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    JobInfo& info = GetJobInfo(jobId);
+    info.failures_++;
+
+    if (info.success_ + info.failures_ >= info.size_)
+    {
+      if (info.watched_)
+      {
+        watchedJobStatus_[jobId] = JobStatus_Failure;
+        watchedJobFinished_.notify_all();
+      }
+
+      LOG(ERROR) << "Job has failed (" << info.description_ << ")";
+      jobs_.erase(jobId);
+
+      availableJob_.Release();
+    }
+  }
+
+
+  void ServerScheduler::Worker(ServerScheduler* that)
+  {
+    static const int32_t TIMEOUT = 100;
+
+    LOG(WARNING) << "The server scheduler has started";
+
+    while (!that->finish_)
+    {
+      std::auto_ptr<IDynamicObject> object(that->queue_.Dequeue(TIMEOUT));
+      if (object.get() != NULL)
+      {
+        ServerCommandInstance& filter = dynamic_cast<ServerCommandInstance&>(*object);
+
+        // Skip the execution of this filter if its parent job has
+        // previously failed.
+        bool jobHasFailed;
+        {
+          boost::mutex::scoped_lock lock(that->mutex_);
+          JobInfo& info = that->GetJobInfo(filter.GetJobId());
+          jobHasFailed = (info.failures_ > 0 || info.cancel_); 
+        }
+
+        if (jobHasFailed)
+        {
+          that->SignalFailure(filter.GetJobId());
+        }
+        else
+        {
+          filter.Execute(*that);
+        }
+      }
+    }
+  }
+
+
+  void ServerScheduler::SubmitInternal(ServerJob& job,
+                                       bool watched)
+  {
+    availableJob_.Acquire();
+
+    boost::mutex::scoped_lock lock(mutex_);
+
+    JobInfo info;
+    info.size_ = job.Submit(queue_, *this);
+    info.cancel_ = false;
+    info.success_ = 0;
+    info.failures_ = 0;
+    info.description_ = job.GetDescription();
+    info.watched_ = watched;
+
+    assert(info.size_ > 0);
+
+    if (watched)
+    {
+      watchedJobStatus_[job.GetId()] = JobStatus_Running;
+    }
+
+    jobs_[job.GetId()] = info;
+
+    LOG(INFO) << "New job submitted (" << job.description_ << ")";
+  }
+
+
+  ServerScheduler::ServerScheduler(unsigned int maxJobs) : availableJob_(maxJobs)
+  {
+    finish_ = false;
+    worker_ = boost::thread(Worker, this);
+  }
+
+
+  ServerScheduler::~ServerScheduler()
+  {
+    finish_ = true;
+    worker_.join();
+  }
+
+
+  void ServerScheduler::Submit(ServerJob& job)
+  {
+    if (job.filters_.empty())
+    {
+      return;
+    }
+
+    SubmitInternal(job, false);
+  }
+
+
+  bool ServerScheduler::SubmitAndWait(ListOfStrings& outputs,
+                                      ServerJob& job)
+  {
+    std::string jobId = job.GetId();
+
+    outputs.clear();
+
+    if (job.filters_.empty())
+    {
+      return true;
+    }
+
+    // Add a sink filter to collect all the results of the filters
+    // that have no next filter.
+    ServerCommandInstance& sink = job.AddCommand(new Sink(outputs));
+
+    for (std::list<ServerCommandInstance*>::iterator
+           it = job.filters_.begin(); it != job.filters_.end(); it++)
+    {
+      if ((*it) != &sink &&
+          (*it)->IsConnectedToSink())
+      {
+        (*it)->ConnectOutput(sink);
+      }
+    }
+
+    // Submit the job
+    SubmitInternal(job, true);
+
+    // Wait for the job to complete (either success or failure)
+    JobStatus status;
+
+    {
+      boost::mutex::scoped_lock lock(mutex_);
+
+      assert(watchedJobStatus_.find(jobId) != watchedJobStatus_.end());
+        
+      while (watchedJobStatus_[jobId] == JobStatus_Running)
+      {
+        watchedJobFinished_.wait(lock);
+      }
+
+      status = watchedJobStatus_[jobId];
+      watchedJobStatus_.erase(jobId);
+    }
+
+    return (status == JobStatus_Success);
+  }
+
+
+  bool ServerScheduler::SubmitAndWait(ServerJob& job)
+  {
+    ListOfStrings ignoredSink;
+    return SubmitAndWait(ignoredSink, job);
+  }
+
+
+  bool ServerScheduler::IsRunning(const std::string& jobId)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+    return jobs_.find(jobId) != jobs_.end();
+  }
+
+
+  void ServerScheduler::Cancel(const std::string& jobId)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    Jobs::iterator job = jobs_.find(jobId);
+
+    if (job != jobs_.end())
+    {
+      job->second.cancel_ = true;
+      LOG(WARNING) << "Canceling a job (" << job->second.description_ << ")";
+    }
+  }
+
+
+  float ServerScheduler::GetProgress(const std::string& jobId) 
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    Jobs::iterator job = jobs_.find(jobId);
+
+    if (job == jobs_.end() || 
+        job->second.size_ == 0  /* should never happen */)
+    {
+      // This job is not running
+      return 1;
+    }
+
+    if (job->second.failures_ != 0)
+    {
+      return 1;
+    }
+
+    if (job->second.size_ == 1)
+    {
+      return job->second.success_;
+    }
+
+    return (static_cast<float>(job->second.success_) / 
+            static_cast<float>(job->second.size_ - 1));
+  }
+
+
+  void ServerScheduler::GetListOfJobs(ListOfStrings& jobs)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    jobs.clear();
+
+    for (Jobs::const_iterator 
+           it = jobs_.begin(); it != jobs_.end(); it++)
+    {
+      jobs.push_back(it->first);
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Scheduler/ServerScheduler.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,120 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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 "ServerJob.h"
+
+#include "../../Core/MultiThreading/Semaphore.h"
+
+namespace Orthanc
+{
+  class ServerScheduler : public ServerCommandInstance::IListener
+  {
+  private:
+    struct JobInfo
+    {
+      bool watched_;
+      bool cancel_;
+      size_t size_;
+      size_t success_;
+      size_t failures_;
+      std::string description_;
+    };
+
+    enum JobStatus
+    {
+      JobStatus_Running = 1,
+      JobStatus_Success = 2,
+      JobStatus_Failure = 3
+    };
+
+    typedef IServerCommand::ListOfStrings  ListOfStrings;
+    typedef std::map<std::string, JobInfo> Jobs;
+
+    boost::mutex mutex_;
+    boost::condition_variable watchedJobFinished_;
+    Jobs jobs_;
+    SharedMessageQueue queue_;
+    bool finish_;
+    boost::thread worker_;
+    std::map<std::string, JobStatus> watchedJobStatus_;
+    Semaphore availableJob_;
+
+    JobInfo& GetJobInfo(const std::string& jobId);
+
+    virtual void SignalSuccess(const std::string& jobId);
+
+    virtual void SignalFailure(const std::string& jobId);
+
+    static void Worker(ServerScheduler* that);
+
+    void SubmitInternal(ServerJob& job,
+                        bool watched);
+
+  public:
+    ServerScheduler(unsigned int maxjobs);
+
+    ~ServerScheduler();
+
+    void Submit(ServerJob& job);
+
+    bool SubmitAndWait(ListOfStrings& outputs,
+                       ServerJob& job);
+
+    bool SubmitAndWait(ServerJob& job);
+
+    bool IsRunning(const std::string& jobId);
+
+    void Cancel(const std::string& jobId);
+
+    // Returns a number between 0 and 1
+    float GetProgress(const std::string& jobId);
+
+    bool IsRunning(const ServerJob& job)
+    {
+      return IsRunning(job.GetId());
+    }
+
+    void Cancel(const ServerJob& job) 
+    {
+      Cancel(job.GetId());
+    }
+
+    float GetProgress(const ServerJob& job) 
+    {
+      return GetProgress(job.GetId());
+    }
+
+    void GetListOfJobs(ListOfStrings& jobs);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Scheduler/StorePeerCommand.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,83 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "StorePeerCommand.h"
+
+#include "../../Core/HttpClient.h"
+
+#include <glog/logging.h>
+
+namespace Orthanc
+{
+  StorePeerCommand::StorePeerCommand(ServerContext& context,
+                                     const OrthancPeerParameters& peer) : 
+    context_(context),
+    peer_(peer)
+  {
+  }
+
+  bool StorePeerCommand::Apply(ListOfStrings& outputs,
+                               const ListOfStrings& inputs)
+  {
+    // Configure the HTTP client
+    HttpClient client;
+    if (peer_.GetUsername().size() != 0 && 
+        peer_.GetPassword().size() != 0)
+    {
+      client.SetCredentials(peer_.GetUsername().c_str(), 
+                            peer_.GetPassword().c_str());
+    }
+
+    client.SetUrl(peer_.GetUrl() + "instances");
+    client.SetMethod(HttpMethod_Post);
+
+    for (ListOfStrings::const_iterator
+           it = inputs.begin(); it != inputs.end(); ++it)
+    {
+      LOG(INFO) << "Sending resource " << *it << " to peer \"" 
+                << peer_.GetUrl() << "\"";
+
+      context_.ReadFile(client.AccessPostData(), *it, FileContentType_Dicom);
+
+      std::string answer;
+      if (!client.Apply(answer))
+      {
+        LOG(ERROR) << "Unable to send resource " << *it << " to peer \"" << peer_.GetUrl() << "\"";
+        throw OrthancException(ErrorCode_NetworkProtocol);
+      }
+
+      outputs.push_back(*it);
+    }
+
+    return true;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Scheduler/StorePeerCommand.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,54 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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 "IServerCommand.h"
+#include "../ServerContext.h"
+#include "../OrthancInitialization.h"
+
+namespace Orthanc
+{
+  class StorePeerCommand : public IServerCommand
+  {
+  private:
+    ServerContext& context_;
+    OrthancPeerParameters peer_;
+
+  public:
+    StorePeerCommand(ServerContext& context,
+                     const OrthancPeerParameters& peer);
+    
+    virtual bool Apply(ListOfStrings& outputs,
+                       const ListOfStrings& inputs);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Scheduler/StoreScuCommand.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,66 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "StoreScuCommand.h"
+
+#include <glog/logging.h>
+
+namespace Orthanc
+{
+  StoreScuCommand::StoreScuCommand(ServerContext& context,
+                                 const RemoteModalityParameters& modality) : 
+    context_(context),
+    modality_(modality)
+  {
+  }
+
+  bool StoreScuCommand::Apply(ListOfStrings& outputs,
+                             const ListOfStrings& inputs)
+  {
+    ReusableDicomUserConnection::Locker locker(context_.GetReusableDicomUserConnection(), modality_);
+
+    for (ListOfStrings::const_iterator
+           it = inputs.begin(); it != inputs.end(); ++it)
+    {
+      LOG(INFO) << "Sending resource " << *it << " to modality \"" 
+                << modality_.GetApplicationEntityTitle() << "\"";
+
+      std::string dicom;
+      context_.ReadFile(dicom, *it, FileContentType_Dicom);
+      locker.GetConnection().Store(dicom);
+
+      outputs.push_back(*it);
+    }
+
+    return true;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancServer/Scheduler/StoreScuCommand.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,53 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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 "IServerCommand.h"
+#include "../ServerContext.h"
+
+namespace Orthanc
+{
+  class StoreScuCommand : public IServerCommand
+  {
+  private:
+    ServerContext& context_;
+    RemoteModalityParameters modality_;
+
+  public:
+    StoreScuCommand(ServerContext& context,
+                   const RemoteModalityParameters& modality);
+
+    virtual bool Apply(ListOfStrings& outputs,
+                       const ListOfStrings& inputs);
+  };
+}
--- a/OrthancServer/ServerContext.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/ServerContext.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -43,9 +43,19 @@
 #include <EmbeddedResources.h>
 #include <dcmtk/dcmdata/dcfilefo.h>
 
+
+#include "Scheduler/DeleteInstanceCommand.h"
+#include "Scheduler/ModifyInstanceCommand.h"
+#include "Scheduler/StoreScuCommand.h"
+#include "Scheduler/StorePeerCommand.h"
+#include "OrthancRestApi/OrthancRestApi.h"
+
+
+
 #define ENABLE_DICOM_CACHE  1
 
 static const char* RECEIVED_INSTANCE_FILTER = "ReceivedInstanceFilter";
+static const char* ON_STORED_INSTANCE = "OnStoredInstance";
 
 static const size_t DICOM_CACHE_SIZE = 2;
 
@@ -67,7 +77,8 @@
     accessor_(storage_),
     compressionEnabled_(false),
     provider_(*this),
-    dicomCache_(provider_, DICOM_CACHE_SIZE)
+    dicomCache_(provider_, DICOM_CACHE_SIZE),
+    scheduler_(Configuration::GetGlobalIntegerParameter("LimitJobs", 10))
   {
     scu_.SetLocalApplicationEntityTitle(Configuration::GetGlobalStringParameter("DicomAet", "ORTHANC"));
     //scu_.SetMillisecondsBeforeClose(1);  // The connection is always released
@@ -90,76 +101,233 @@
     storage_.Remove(fileUuid);
   }
 
-  StoreStatus ServerContext::Store(const char* dicomInstance,
-                                   size_t dicomSize,
-                                   const DicomMap& dicomSummary,
-                                   const Json::Value& dicomJson,
-                                   const std::string& remoteAet)
+
+  bool ServerContext::ApplyReceivedInstanceFilter(const Json::Value& simplified,
+                                                  const std::string& remoteAet)
   {
-    // Test if the instance must be filtered out
-    if (lua_.IsExistingFunction(RECEIVED_INSTANCE_FILTER))
+    LuaContextLocker locker(*this);
+
+    if (locker.GetLua().IsExistingFunction(RECEIVED_INSTANCE_FILTER))
     {
-      Json::Value simplified;
-      SimplifyTags(simplified, dicomJson);
-
-      LuaFunctionCall call(lua_, RECEIVED_INSTANCE_FILTER);
-      call.PushJSON(simplified);
+      LuaFunctionCall call(locker.GetLua(), RECEIVED_INSTANCE_FILTER);
+      call.PushJson(simplified);
       call.PushString(remoteAet);
 
       if (!call.ExecutePredicate())
       {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+  static IServerCommand* ParseOperation(ServerContext& context,
+                                        const std::string& operation,
+                                        const Json::Value& parameters)
+  {
+    if (operation == "delete")
+    {
+      LOG(INFO) << "Lua script to delete instance " << parameters["Instance"].asString();
+      return new DeleteInstanceCommand(context);
+    }
+
+    if (operation == "store-scu")
+    {
+      std::string modality = parameters["Modality"].asString();
+      LOG(INFO) << "Lua script to send instance " << parameters["Instance"].asString()
+                << " to modality " << modality << " using Store-SCU";
+      return new StoreScuCommand(context, Configuration::GetModalityUsingSymbolicName(modality));
+    }
+
+    if (operation == "store-peer")
+    {
+      std::string peer = parameters["Peer"].asString();
+      LOG(INFO) << "Lua script to send instance " << parameters["Instance"].asString()
+                << " to peer " << peer << " using HTTP";
+
+      OrthancPeerParameters parameters;
+      Configuration::GetOrthancPeer(parameters, peer);
+      return new StorePeerCommand(context, parameters);
+    }
+
+    if (operation == "modify")
+    {
+      LOG(INFO) << "Lua script to modify instance " << parameters["Instance"].asString();
+      std::auto_ptr<ModifyInstanceCommand> command(new ModifyInstanceCommand(context));
+      OrthancRestApi::ParseModifyRequest(command->GetModification(), parameters);
+      return command.release();
+    }
+
+    throw OrthancException(ErrorCode_ParameterOutOfRange);
+  }
+
+
+  void ServerContext::ApplyOnStoredInstance(const std::string& instanceId,
+                                            const Json::Value& simplifiedDicom,
+                                            const Json::Value& metadata)
+  {
+    LuaContextLocker locker(*this);
+
+    if (locker.GetLua().IsExistingFunction(ON_STORED_INSTANCE))
+    {
+      locker.GetLua().Execute("_InitializeJob()");
+
+      LuaFunctionCall call(locker.GetLua(), ON_STORED_INSTANCE);
+      call.PushString(instanceId);
+      call.PushJson(simplifiedDicom);
+      call.PushJson(metadata);
+      call.Execute();
+
+      Json::Value operations;
+      LuaFunctionCall call2(locker.GetLua(), "_AccessJob");
+      call2.ExecuteToJson(operations);
+     
+      if (operations.type() != Json::arrayValue)
+      {
+        throw OrthancException(ErrorCode_InternalError);
+      }
+
+      ServerJob job;
+      ServerCommandInstance* previousCommand = NULL;
+
+      for (Json::Value::ArrayIndex i = 0; i < operations.size(); ++i)
+      {
+        if (operations[i].type() != Json::objectValue ||
+            !operations[i].isMember("Operation"))
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+
+        const Json::Value& parameters = operations[i];
+        std::string operation = parameters["Operation"].asString();
+
+        ServerCommandInstance& command = job.AddCommand(ParseOperation(*this, operation, operations[i]));
+        
+        if (!parameters.isMember("Instance"))
+        {
+          throw OrthancException(ErrorCode_InternalError);
+        }
+
+        std::string instance = parameters["Instance"].asString();
+        if (instance.empty())
+        {
+          previousCommand->ConnectOutput(command);
+        }
+        else 
+        {
+          command.AddInput(instance);
+        }
+
+        previousCommand = &command;
+      }
+
+      job.SetDescription(std::string("Lua script: ") + ON_STORED_INSTANCE);
+      scheduler_.Submit(job);
+    }
+  }
+
+
+  StoreStatus ServerContext::Store(std::string& resultPublicId,
+                                   DicomInstanceToStore& dicom)
+  {
+    try
+    {
+      DicomInstanceHasher hasher(dicom.GetSummary());
+      resultPublicId = hasher.HashInstance();
+
+      Json::Value simplified;
+      SimplifyTags(simplified, dicom.GetJson());
+
+      // Test if the instance must be filtered out
+      if (!ApplyReceivedInstanceFilter(simplified, dicom.GetRemoteAet()))
+      {
         LOG(INFO) << "An incoming instance has been discarded by the filter";
         return StoreStatus_FilteredOut;
       }
-    }
+
+      if (compressionEnabled_)
+      {
+        accessor_.SetCompressionForNextOperations(CompressionType_Zlib);
+      }
+      else
+      {
+        accessor_.SetCompressionForNextOperations(CompressionType_None);
+      }      
+
+      FileInfo dicomInfo = accessor_.Write(dicom.GetBufferData(), dicom.GetBufferSize(), FileContentType_Dicom);
+      FileInfo jsonInfo = accessor_.Write(dicom.GetJson().toStyledString(), FileContentType_DicomAsJson);
 
-    if (compressionEnabled_)
-    {
-      accessor_.SetCompressionForNextOperations(CompressionType_Zlib);
-    }
-    else
-    {
-      accessor_.SetCompressionForNextOperations(CompressionType_None);
-    }      
+      ServerIndex::Attachments attachments;
+      attachments.push_back(dicomInfo);
+      attachments.push_back(jsonInfo);
+
+      std::map<MetadataType, std::string> instanceMetadata;
+      StoreStatus status = index_.Store(instanceMetadata, dicom.GetSummary(), attachments, 
+                                        dicom.GetRemoteAet(), dicom.GetMetadata());
+
+      if (status != StoreStatus_Success)
+      {
+        storage_.Remove(dicomInfo.GetUuid());
+        storage_.Remove(jsonInfo.GetUuid());
+      }
+
+      switch (status)
+      {
+        case StoreStatus_Success:
+          LOG(INFO) << "New instance stored";
+          break;
 
-    FileInfo dicomInfo = accessor_.Write(dicomInstance, dicomSize, FileContentType_Dicom);
-    FileInfo jsonInfo = accessor_.Write(dicomJson.toStyledString(), FileContentType_DicomAsJson);
+        case StoreStatus_AlreadyStored:
+          LOG(INFO) << "Already stored";
+          break;
+
+        case StoreStatus_Failure:
+          LOG(ERROR) << "Store failure";
+          break;
+
+        default:
+          // This should never happen
+          break;
+      }
 
-    ServerIndex::Attachments attachments;
-    attachments.push_back(dicomInfo);
-    attachments.push_back(jsonInfo);
+      if (status == StoreStatus_Success ||
+          status == StoreStatus_AlreadyStored)
+      {
+        Json::Value metadata = Json::objectValue;
+        for (std::map<MetadataType, std::string>::const_iterator 
+               it = instanceMetadata.begin(); 
+             it != instanceMetadata.end(); ++it)
+        {
+          metadata[EnumerationToString(it->first)] = it->second;
+        }
 
-    StoreStatus status = index_.Store(dicomSummary, attachments, remoteAet);
+        try
+        {
+          ApplyOnStoredInstance(resultPublicId, simplified, metadata);
+        }
+        catch (OrthancException& e)
+        {
+          LOG(ERROR) << "Error in OnStoredInstance callback (Lua): " << e.What();
+        }
+      }
 
-    if (status != StoreStatus_Success)
+      return status;
+    }
+    catch (OrthancException& e)
     {
-      storage_.Remove(dicomInfo.GetUuid());
-      storage_.Remove(jsonInfo.GetUuid());
-    }
-
-    switch (status)
-    {
-      case StoreStatus_Success:
-        LOG(INFO) << "New instance stored";
-        break;
+      if (e.GetErrorCode() == ErrorCode_InexistentTag)
+      {
+        LogMissingRequiredTag(dicom.GetSummary());
+      }
 
-      case StoreStatus_AlreadyStored:
-        LOG(INFO) << "Already stored";
-        break;
-
-      case StoreStatus_Failure:
-        LOG(ERROR) << "Store failure";
-        break;
-
-      default:
-        // This should never happen
-        break;
+      throw;
     }
-
-    return status;
   }
 
-  
+
+
   void ServerContext::AnswerDicomFile(RestApiOutput& output,
                                       const std::string& instancePublicId,
                                       FileContentType content)
@@ -249,87 +417,6 @@
   }
 
 
-  static DcmFileFormat& GetDicom(ParsedDicomFile& file)
-  {
-    return *reinterpret_cast<DcmFileFormat*>(file.GetDcmtkObject());
-  }
-
-
-  StoreStatus ServerContext::Store(std::string& resultPublicId,
-                                   ParsedDicomFile& dicomInstance,
-                                   const char* dicomBuffer,
-                                   size_t dicomSize)
-  {
-    DicomMap dicomSummary;
-    FromDcmtkBridge::Convert(dicomSummary, *GetDicom(dicomInstance).getDataset());
-
-    try
-    {
-      DicomInstanceHasher hasher(dicomSummary);
-      resultPublicId = hasher.HashInstance();
-
-      Json::Value dicomJson;
-      FromDcmtkBridge::ToJson(dicomJson, *GetDicom(dicomInstance).getDataset());
-      
-      StoreStatus status = StoreStatus_Failure;
-      if (dicomSize > 0)
-      {
-        status = Store(dicomBuffer, dicomSize, dicomSummary, dicomJson, "");
-      }   
-
-      return status;
-    }
-    catch (OrthancException& e)
-    {
-      if (e.GetErrorCode() == ErrorCode_InexistentTag)
-      {
-        LogMissingRequiredTag(dicomSummary);
-      }
-
-      throw;
-    }
-  }
-
-
-  StoreStatus ServerContext::Store(std::string& resultPublicId,
-                                   ParsedDicomFile& dicomInstance)
-  {
-    std::string buffer;
-    if (!FromDcmtkBridge::SaveToMemoryBuffer(buffer, GetDicom(dicomInstance).getDataset()))
-    {
-      throw OrthancException(ErrorCode_InternalError);
-    }
-
-    if (buffer.size() == 0)
-      return Store(resultPublicId, dicomInstance, NULL, 0);
-    else
-      return Store(resultPublicId, dicomInstance, &buffer[0], buffer.size());
-  }
-
-
-  StoreStatus ServerContext::Store(std::string& resultPublicId,
-                                   const char* dicomBuffer,
-                                   size_t dicomSize)
-  {
-    ParsedDicomFile dicom(dicomBuffer, dicomSize);
-    return Store(resultPublicId, dicom, dicomBuffer, dicomSize);
-  }
-
-
-  StoreStatus ServerContext::Store(std::string& resultPublicId,
-                                   const std::string& dicomContent)
-  {
-    if (dicomContent.size() == 0)
-    {
-      return Store(resultPublicId, NULL, 0);
-    }
-    else
-    {
-      return Store(resultPublicId, &dicomContent[0], dicomContent.size());
-    }
-  }
-
-
   void ServerContext::SetStoreMD5ForAttachments(bool storeMD5)
   {
     LOG(INFO) << "Storing MD5 for attachments: " << (storeMD5 ? "yes" : "no");
--- a/OrthancServer/ServerContext.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/ServerContext.h	Thu Jul 10 11:42:32 2014 +0200
@@ -40,6 +40,8 @@
 #include "ServerIndex.h"
 #include "ParsedDicomFile.h"
 #include "DicomProtocol/ReusableDicomUserConnection.h"
+#include "Scheduler/ServerScheduler.h"
+#include "DicomInstanceToStore.h"
 
 namespace Orthanc
 {
@@ -64,6 +66,13 @@
       virtual IDynamicObject* Provide(const std::string& id);
     };
 
+    bool ApplyReceivedInstanceFilter(const Json::Value& simplified,
+                                     const std::string& remoteAet);
+
+    void ApplyOnStoredInstance(const std::string& instanceId,
+                               const Json::Value& simplifiedDicom,
+                               const Json::Value& metadata);
+
     FileStorage storage_;
     ServerIndex index_;
     CompressedFileStorageAccessor accessor_;
@@ -73,11 +82,13 @@
     boost::mutex dicomCacheMutex_;
     MemoryCache dicomCache_;
     ReusableDicomUserConnection scu_;
+    ServerScheduler scheduler_;
 
+    boost::mutex luaMutex_;
     LuaContext lua_;
 
   public:
-    class DicomCacheLocker
+    class DicomCacheLocker : public boost::noncopyable
     {
     private:
       ServerContext& that_;
@@ -95,6 +106,29 @@
       }
     };
 
+    class LuaContextLocker : public boost::noncopyable
+    {
+    private:
+      ServerContext& that_;
+
+    public:
+      LuaContextLocker(ServerContext& that) : that_(that)
+      {
+        that.luaMutex_.lock();
+      }
+
+      ~LuaContextLocker()
+      {
+        that_.luaMutex_.unlock();
+      }
+
+      LuaContext& GetLua()
+      {
+        return that_.lua_;
+      }
+    };
+
+
     ServerContext(const boost::filesystem::path& storagePath,
                   const boost::filesystem::path& indexPath);
 
@@ -117,26 +151,8 @@
                        const void* data,
                        size_t size);
 
-    StoreStatus Store(const char* dicomInstance,
-                      size_t dicomSize,
-                      const DicomMap& dicomSummary,
-                      const Json::Value& dicomJson,
-                      const std::string& remoteAet);
-
     StoreStatus Store(std::string& resultPublicId,
-                      ParsedDicomFile& dicomInstance,
-                      const char* dicomBuffer,
-                      size_t dicomSize);
-
-    StoreStatus Store(std::string& resultPublicId,
-                      ParsedDicomFile& dicomInstance);
-
-    StoreStatus Store(std::string& resultPublicId,
-                      const char* dicomBuffer,
-                      size_t dicomSize);
-
-    StoreStatus Store(std::string& resultPublicId,
-                      const std::string& dicomContent);
+                      DicomInstanceToStore& dicom);
 
     void AnswerDicomFile(RestApiOutput& output,
                          const std::string& instancePublicId,
@@ -151,11 +167,6 @@
                   FileContentType content,
                   bool uncompressIfNeeded = true);
 
-    LuaContext& GetLuaContext()
-    {
-      return lua_;
-    }
-
     void SetStoreMD5ForAttachments(bool storeMD5);
 
     bool IsStoreMD5ForAttachments() const
@@ -167,5 +178,10 @@
     {
       return scu_;
     }
+
+    ServerScheduler& GetScheduler()
+    {
+      return scheduler_;
+    }
   };
 }
--- a/OrthancServer/ServerEnumerations.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/ServerEnumerations.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -279,6 +279,9 @@
       case ModalityManufacturer_Generic:
         return "Generic";
 
+      case ModalityManufacturer_StoreScp:
+        return "StoreScp";
+      
       case ModalityManufacturer_ClearCanvas:
         return "ClearCanvas";
       
@@ -336,6 +339,10 @@
     {
       return ModalityManufacturer_ClearCanvas;
     }
+    else if (manufacturer == "StoreScp")
+    {
+      return ModalityManufacturer_StoreScp;
+    }
     else if (manufacturer == "MedInria")
     {
       return ModalityManufacturer_MedInria;
--- a/OrthancServer/ServerEnumerations.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/ServerEnumerations.h	Thu Jul 10 11:42:32 2014 +0200
@@ -32,6 +32,7 @@
 #pragma once
 
 #include <string>
+#include <map>
 
 #include "../Core/Enumerations.h"
 
@@ -56,6 +57,7 @@
   enum ModalityManufacturer
   {
     ModalityManufacturer_Generic,
+    ModalityManufacturer_StoreScp,
     ModalityManufacturer_ClearCanvas,
     ModalityManufacturer_MedInria,
     ModalityManufacturer_Dcm4Chee
@@ -124,6 +126,8 @@
     ChangeType_StableSeries = 14
   };
 
+
+
   void InitializeServerEnumerations();
 
   void RegisterUserMetadata(int metadata,
--- a/OrthancServer/ServerIndex.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/ServerIndex.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -382,13 +382,17 @@
   }
 
 
-  StoreStatus ServerIndex::Store(const DicomMap& dicomSummary,
+  StoreStatus ServerIndex::Store(std::map<MetadataType, std::string>& instanceMetadata,
+                                 const DicomMap& dicomSummary,
                                  const Attachments& attachments,
-                                 const std::string& remoteAet)
+                                 const std::string& remoteAet,
+                                 const MetadataMap& metadata)
   {
     boost::mutex::scoped_lock lock(mutex_);
     listener_->Reset();
 
+    instanceMetadata.clear();
+
     DicomInstanceHasher hasher(dicomSummary);
 
     try
@@ -402,6 +406,7 @@
         if (db_->LookupResource(hasher.HashInstance(), tmp, type))
         {
           assert(type == ResourceType_Instance);
+          db_->GetAllMetadata(instanceMetadata, tmp);
           return StoreStatus_AlreadyStored;
         }
       }
@@ -519,27 +524,62 @@
         db_->AddAttachment(instance, *it);
       }
 
-      // Attach the metadata
+      // Attach the user-specified metadata
+      for (MetadataMap::const_iterator 
+             it = metadata.begin(); it != metadata.end(); ++it)
+      {
+        switch (it->first.first)
+        {
+          case ResourceType_Patient:
+            db_->SetMetadata(patient, it->first.second, it->second);
+            break;
+
+          case ResourceType_Study:
+            db_->SetMetadata(study, it->first.second, it->second);
+            break;
+
+          case ResourceType_Series:
+            db_->SetMetadata(series, it->first.second, it->second);
+            break;
+
+          case ResourceType_Instance:
+            db_->SetMetadata(instance, it->first.second, it->second);
+            instanceMetadata[it->first.second] = it->second;
+            break;
+
+          default:
+            throw OrthancException(ErrorCode_ParameterOutOfRange);
+        }
+      }
+
+      // Attach the auto-computed metadata for the patient/study/series levels
       std::string now = Toolbox::GetNowIsoString();
-      db_->SetMetadata(instance, MetadataType_Instance_ReceptionDate, now);
       db_->SetMetadata(series, MetadataType_LastUpdate, now);
       db_->SetMetadata(study, MetadataType_LastUpdate, now);
       db_->SetMetadata(patient, MetadataType_LastUpdate, now);
+
+      // Attach the auto-computed metadata for the instance level,
+      // reflecting these additions into the input metadata map
+      db_->SetMetadata(instance, MetadataType_Instance_ReceptionDate, now);
+      instanceMetadata[MetadataType_Instance_ReceptionDate] = now;
+
       db_->SetMetadata(instance, MetadataType_Instance_RemoteAet, remoteAet);
+      instanceMetadata[MetadataType_Instance_RemoteAet] = remoteAet;
 
       const DicomValue* value;
       if ((value = dicomSummary.TestAndGetValue(DICOM_TAG_INSTANCE_NUMBER)) != NULL ||
           (value = dicomSummary.TestAndGetValue(DICOM_TAG_IMAGE_INDEX)) != NULL)
       {
         db_->SetMetadata(instance, MetadataType_Instance_IndexInSeries, value->AsString());
+        instanceMetadata[MetadataType_Instance_IndexInSeries] = value->AsString();
       }
 
+      // Check whether the series of this new instance is now completed
       if (isNewSeries)
       {
         ComputeExpectedNumberOfInstances(*db_, series, dicomSummary);
       }
 
-      // Check whether the series of this new instance is now completed
       SeriesStatus seriesStatus = GetSeriesStatus(series);
       if (seriesStatus == SeriesStatus_Complete)
       {
@@ -1694,4 +1734,33 @@
   }
 
 
+  bool ServerIndex::GetMetadata(Json::Value& target,
+                                const std::string& publicId)
+  {
+    boost::mutex::scoped_lock lock(mutex_);
+
+    target = Json::objectValue;
+
+    ResourceType type;
+    int64_t id;
+    if (!db_->LookupResource(publicId, id, type))
+    {
+      return false;
+    }
+
+    std::list<MetadataType> metadata;
+    db_->ListAvailableMetadata(metadata, id);
+
+    for (std::list<MetadataType>::const_iterator
+           it = metadata.begin(); it != metadata.end(); it++)
+    {
+      std::string key = EnumerationToString(*it);
+      std::string value = db_->GetMetadata(id, *it);
+      target[key] = value;
+    }
+
+    return true;
+  }
+
+
 }
--- a/OrthancServer/ServerIndex.h	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/ServerIndex.h	Thu Jul 10 11:42:32 2014 +0200
@@ -54,6 +54,10 @@
 
   class ServerIndex : public boost::noncopyable
   {
+  public:
+    typedef std::list<FileInfo> Attachments;
+    typedef std::map< std::pair<ResourceType, MetadataType>, std::string>  MetadataMap;
+
   private:
     class Transaction;
     struct UnstableResourcePayload;
@@ -99,8 +103,6 @@
                                /* in  */ ResourceType type);
 
   public:
-    typedef std::list<FileInfo> Attachments;
-
     ServerIndex(ServerContext& context,
                 const std::string& dbPath);
 
@@ -122,9 +124,11 @@
     // "count == 0" means no limit on the number of patients
     void SetMaximumPatientCount(unsigned int count);
 
-    StoreStatus Store(const DicomMap& dicomSummary,
+    StoreStatus Store(std::map<MetadataType, std::string>& instanceMetadata,
+                      const DicomMap& dicomSummary,
                       const Attachments& attachments,
-                      const std::string& remoteAet);
+                      const std::string& remoteAet,
+                      const MetadataMap& metadata);
 
     void ComputeStatistics(Json::Value& target);                        
 
@@ -183,6 +187,9 @@
     void ListAvailableMetadata(std::list<MetadataType>& target,
                                const std::string& publicId);
 
+    bool GetMetadata(Json::Value& target,
+                     const std::string& publicId);
+
     void ListAvailableAttachments(std::list<FileContentType>& target,
                                   const std::string& publicId,
                                   ResourceType expectedType);
--- a/OrthancServer/main.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/OrthancServer/main.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -48,6 +48,8 @@
 #include "OrthancFindRequestHandler.h"
 #include "OrthancMoveRequestHandler.h"
 #include "ServerToolbox.h"
+#include "../Plugins/Engine/PluginsManager.h"
+#include "../Plugins/Engine/PluginsHttpHandler.h"
 
 using namespace Orthanc;
 
@@ -71,7 +73,14 @@
   {
     if (dicomFile.size() > 0)
     {
-      server_.Store(&dicomFile[0], dicomFile.size(), dicomSummary, dicomJson, remoteAet);
+      DicomInstanceToStore toStore;
+      toStore.SetBuffer(dicomFile);
+      toStore.SetSummary(dicomSummary);
+      toStore.SetJson(dicomJson);
+      toStore.SetRemoteAet(remoteAet);
+
+      std::string id;
+      server_.Store(id, toStore);
     }
   }
 };
@@ -186,10 +195,12 @@
   {
     static const char* HTTP_FILTER = "IncomingHttpRequestFilter";
 
+    ServerContext::LuaContextLocker locker(context_);
+
     // Test if the instance must be filtered out
-    if (context_.GetLuaContext().IsExistingFunction(HTTP_FILTER))
+    if (locker.GetLua().IsExistingFunction(HTTP_FILTER))
     {
-      LuaFunctionCall call(context_.GetLuaContext(), HTTP_FILTER);
+      LuaFunctionCall call(locker.GetLua(), HTTP_FILTER);
 
       switch (method)
       {
@@ -229,7 +240,7 @@
 };
 
 
-void PrintHelp(char* path)
+static void PrintHelp(char* path)
 {
   std::cout 
     << "Usage: " << path << " [OPTION]... [CONFIGURATION]" << std::endl
@@ -256,7 +267,7 @@
 }
 
 
-void PrintVersion(char* path)
+static void PrintVersion(char* path)
 {
   std::cout
     << path << " " << ORTHANC_VERSION << std::endl
@@ -269,6 +280,41 @@
 }
 
 
+
+static void LoadLuaScripts(ServerContext& context)
+{
+  std::list<std::string> luaScripts;
+  Configuration::GetGlobalListOfStringsParameter(luaScripts, "LuaScripts");
+  for (std::list<std::string>::const_iterator
+         it = luaScripts.begin(); it != luaScripts.end(); ++it)
+  {
+    std::string path = Configuration::InterpretStringParameterAsPath(*it);
+    LOG(WARNING) << "Installing the Lua scripts from: " << path;
+    std::string script;
+    Toolbox::ReadFile(script, path);
+
+    ServerContext::LuaContextLocker locker(context);
+    locker.GetLua().Execute(script);
+  }
+}
+
+
+static void LoadPlugins(PluginsManager& pluginsManager)
+{
+  std::list<std::string> plugins;
+  Configuration::GetGlobalListOfStringsParameter(plugins, "Plugins");
+  for (std::list<std::string>::const_iterator
+         it = plugins.begin(); it != plugins.end(); ++it)
+  {
+    std::string path = Configuration::InterpretStringParameterAsPath(*it);
+    LOG(WARNING) << "Registering a plugin from: " << path;
+    pluginsManager.RegisterPlugin(path);
+  }  
+}
+
+
+
+
 int main(int argc, char* argv[]) 
 {
   // Initialize Google's logging library.
@@ -362,18 +408,7 @@
     context.SetCompressionEnabled(Configuration::GetGlobalBoolParameter("StorageCompression", false));
     context.SetStoreMD5ForAttachments(Configuration::GetGlobalBoolParameter("StoreMD5ForAttachments", true));
 
-    std::list<std::string> luaScripts;
-    Configuration::GetGlobalListOfStringsParameter(luaScripts, "LuaScripts");
-    for (std::list<std::string>::const_iterator
-           it = luaScripts.begin(); it != luaScripts.end(); ++it)
-    {
-      std::string path = Configuration::InterpretStringParameterAsPath(*it);
-      LOG(WARNING) << "Installing the Lua scripts from: " << path;
-      std::string script;
-      Toolbox::ReadFile(script, path);
-      context.GetLuaContext().Execute(script);
-    }
-
+    LoadLuaScripts(context);
 
     try
     {
@@ -430,13 +465,25 @@
         httpServer.SetSslEnabled(false);
       }
 
+      OrthancRestApi restApi(context);
+
 #if ORTHANC_STANDALONE == 1
-      httpServer.RegisterHandler(new EmbeddedResourceHttpHandler("/app", EmbeddedResources::ORTHANC_EXPLORER));
+      EmbeddedResourceHttpHandler staticResources("/app", EmbeddedResources::ORTHANC_EXPLORER);
 #else
-      httpServer.RegisterHandler(new FilesystemHttpHandler("/app", ORTHANC_PATH "/OrthancExplorer"));
+      FilesystemHttpHandler staticResources("/app", ORTHANC_PATH "/OrthancExplorer");
 #endif
 
-      httpServer.RegisterHandler(new OrthancRestApi(context));
+      PluginsHttpHandler httpPlugins(context);
+      httpPlugins.SetOrthancRestApi(restApi);
+
+      PluginsManager pluginsManager;
+      pluginsManager.RegisterServiceProvider(httpPlugins);
+      LoadPlugins(pluginsManager);
+
+      httpServer.RegisterHandler(httpPlugins);
+      httpServer.RegisterHandler(staticResources);
+      httpServer.RegisterHandler(restApi);
+      httpPlugins.SetOrthancRestApi(restApi);
 
       // GO !!! Start the requested servers
       if (Configuration::GetGlobalBoolParameter("HttpServerEnabled", true))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Engine/IPluginServiceProvider.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,51 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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 "../OrthancCPlugin/OrthancCPlugin.h"
+
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class IPluginServiceProvider : boost::noncopyable
+  {
+  public:
+    virtual ~IPluginServiceProvider()
+    {
+    }
+
+    virtual bool InvokeService(_OrthancPluginService service,
+                               const void* parameters) = 0;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Engine/PluginsHttpHandler.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,497 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "PluginsHttpHandler.h"
+
+#include "../../Core/ChunkedBuffer.h"
+#include "../../Core/OrthancException.h"
+#include "../../Core/Toolbox.h"
+#include "../../Core/HttpServer/HttpOutput.h"
+#include "../../Core/ImageFormats/PngWriter.h"
+
+#include <boost/regex.hpp> 
+#include <glog/logging.h>
+
+namespace Orthanc
+{
+  namespace
+  {
+    // Anonymous namespace to avoid clashes between compilation modules
+    class StringHttpOutput : public IHttpOutputStream
+    {
+    private:
+      ChunkedBuffer buffer_;
+
+    public:
+      void GetOutput(std::string& output)
+      {
+        buffer_.Flatten(output);
+      }
+
+      virtual void OnHttpStatusReceived(HttpStatus status)
+      {
+        if (status != HttpStatus_200_Ok)
+        {
+          throw OrthancException(ErrorCode_BadRequest);
+        }
+      }
+
+      virtual void Send(bool isHeader, const void* buffer, size_t length)
+      {
+        if (!isHeader)
+        {
+          buffer_.AddChunk(reinterpret_cast<const char*>(buffer), length);
+        }
+      }
+    };
+  }
+
+
+
+  struct PluginsHttpHandler::PImpl
+  {
+    typedef std::pair<boost::regex*, OrthancPluginRestCallback> Callback;
+    typedef std::list<Callback>  Callbacks;
+
+    ServerContext& context_;
+    Callbacks callbacks_;
+    OrthancRestApi* restApi_;
+
+    PImpl(ServerContext& context) : context_(context), restApi_(NULL)
+    {
+    }
+  };
+
+
+  PluginsHttpHandler::PluginsHttpHandler(ServerContext& context)
+  {
+    pimpl_.reset(new PImpl(context));
+  }
+
+  
+  PluginsHttpHandler::~PluginsHttpHandler()
+  {
+    for (PImpl::Callbacks::iterator it = pimpl_->callbacks_.begin(); 
+         it != pimpl_->callbacks_.end(); ++it)
+    {
+      delete it->first;
+    }
+  }
+
+
+  bool PluginsHttpHandler::Handle(HttpOutput& output,
+                                  HttpMethod method,
+                                  const UriComponents& uri,
+                                  const Arguments& headers,
+                                  const Arguments& getArguments,
+                                  const std::string& postData)
+  {
+    std::string flatUri = Toolbox::FlattenUri(uri);
+    OrthancPluginRestCallback callback = NULL;
+
+    std::vector<std::string> groups;
+    std::vector<const char*> cgroups;
+
+    bool found = false;
+    for (PImpl::Callbacks::const_iterator it = pimpl_->callbacks_.begin(); 
+         it != pimpl_->callbacks_.end() && !found; ++it)
+    {
+      boost::cmatch what;
+      if (boost::regex_match(flatUri.c_str(), what, *(it->first)))
+      {
+        callback = it->second;
+
+        if (what.size() > 1)
+        {
+          groups.resize(what.size() - 1);
+          cgroups.resize(what.size() - 1);
+          for (size_t i = 1; i < what.size(); i++)
+          {
+            groups[i - 1] = what[i];
+            cgroups[i - 1] = groups[i - 1].c_str();
+          }
+        }
+
+        found = true;
+      }
+    }
+
+    if (!found)
+    {
+      return false;
+    }
+
+    LOG(INFO) << "Delegating HTTP request to plugin for URI: " << flatUri;
+
+    std::vector<const char*> getKeys(getArguments.size());
+    std::vector<const char*> getValues(getArguments.size());
+
+    OrthancPluginHttpRequest request;
+    memset(&request, 0, sizeof(OrthancPluginHttpRequest));
+
+    switch (method)
+    {
+      case HttpMethod_Get:
+      {
+        request.method = OrthancPluginHttpMethod_Get;
+
+        size_t i = 0;
+        for (Arguments::const_iterator it = getArguments.begin(); 
+             it != getArguments.end(); ++it, ++i)
+        {
+          getKeys[i] = it->first.c_str();
+          getValues[i] = it->second.c_str();
+        }
+
+        break;
+      }
+
+      case HttpMethod_Post:
+        request.method = OrthancPluginHttpMethod_Post;
+        break;
+
+      case HttpMethod_Delete:
+        request.method = OrthancPluginHttpMethod_Delete;
+        break;
+
+      case HttpMethod_Put:
+        request.method = OrthancPluginHttpMethod_Put;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_InternalError);
+    }
+
+
+    request.groups = (cgroups.size() ? &cgroups[0] : NULL);
+    request.groupsCount = cgroups.size();
+    request.getCount = getArguments.size();
+    request.body = (postData.size() ? &postData[0] : NULL);
+    request.bodySize = postData.size();
+
+    if (getArguments.size() > 0)
+    {
+      request.getKeys = &getKeys[0];
+      request.getValues = &getValues[0];
+    }
+
+    assert(callback != NULL);
+    int32_t error = callback(reinterpret_cast<OrthancPluginRestOutput*>(&output), 
+                             flatUri.c_str(), 
+                             &request);
+
+    if (error < 0)
+    {
+      LOG(ERROR) << "Plugin failed with error code " << error;
+      return false;
+    }
+    else
+    {
+      if (error > 0)
+      {
+        LOG(WARNING) << "Plugin finished with warning code " << error;
+      }
+
+      return true;
+    }
+  }
+
+
+  static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer& target,
+                                 const void* data,
+                                 size_t size)
+  {
+    target.size = size;
+
+    if (size == 0)
+    {
+      target.data = NULL;
+    }
+    else
+    {
+      target.data = malloc(size);
+      if (target.data != NULL)
+      {
+        memcpy(target.data, data, size);
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_NotEnoughMemory);
+      }
+    }
+  }
+
+
+  static void CopyToMemoryBuffer(OrthancPluginMemoryBuffer& target,
+                                 const std::string& str)
+  {
+    if (str.size() == 0)
+    {
+      target.size = 0;
+      target.data = NULL;
+    }
+    else
+    {
+      CopyToMemoryBuffer(target, str.c_str(), str.size());
+    }
+  }
+
+
+  void PluginsHttpHandler::RegisterRestCallback(const void* parameters)
+  {
+    const _OrthancPluginRestCallback& p = 
+      *reinterpret_cast<const _OrthancPluginRestCallback*>(parameters);
+
+    LOG(INFO) << "Plugin has registered a REST callback on: " << p.pathRegularExpression;
+    pimpl_->callbacks_.push_back(std::make_pair(new boost::regex(p.pathRegularExpression), p.callback));
+  }
+
+
+
+  void PluginsHttpHandler::AnswerBuffer(const void* parameters)
+  {
+    const _OrthancPluginAnswerBuffer& p = 
+      *reinterpret_cast<const _OrthancPluginAnswerBuffer*>(parameters);
+
+    HttpOutput* translatedOutput = reinterpret_cast<HttpOutput*>(p.output);
+    translatedOutput->AnswerBufferWithContentType(p.answer, p.answerSize, p.mimeType);
+  }
+
+
+  void PluginsHttpHandler::Redirect(const void* parameters)
+  {
+    const _OrthancPluginRedirect& p = 
+      *reinterpret_cast<const _OrthancPluginRedirect*>(parameters);
+
+    HttpOutput* translatedOutput = reinterpret_cast<HttpOutput*>(p.output);
+    translatedOutput->Redirect(p.redirection);
+  }
+
+
+  void PluginsHttpHandler::CompressAndAnswerPngImage(const void* parameters)
+  {
+    const _OrthancPluginCompressAndAnswerPngImage& p = 
+      *reinterpret_cast<const _OrthancPluginCompressAndAnswerPngImage*>(parameters);
+
+    HttpOutput* translatedOutput = reinterpret_cast<HttpOutput*>(p.output);
+
+    PixelFormat format;
+    switch (p.format)
+    {
+      case OrthancPluginPixelFormat_Grayscale8:  
+        format = PixelFormat_Grayscale8;
+        break;
+
+      case OrthancPluginPixelFormat_Grayscale16:  
+        format = PixelFormat_Grayscale16;
+        break;
+
+      case OrthancPluginPixelFormat_SignedGrayscale16:  
+        format = PixelFormat_SignedGrayscale16;
+        break;
+
+      case OrthancPluginPixelFormat_RGB24:  
+        format = PixelFormat_RGB24;
+        break;
+
+      case OrthancPluginPixelFormat_RGBA32:  
+        format = PixelFormat_RGBA32;
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+
+    ImageAccessor accessor;
+    accessor.AssignReadOnly(format, p.width, p.height, p.pitch, p.buffer);
+
+    PngWriter writer;
+    std::string png;
+    writer.WriteToMemory(png, accessor);
+
+    translatedOutput->AnswerBufferWithContentType(png, "image/png");
+  }
+
+
+  void PluginsHttpHandler::GetDicomForInstance(const void* parameters)
+  {
+    const _OrthancPluginGetDicomForInstance& p = 
+      *reinterpret_cast<const _OrthancPluginGetDicomForInstance*>(parameters);
+
+    std::string dicom;
+    pimpl_->context_.ReadFile(dicom, p.instanceId, FileContentType_Dicom);
+    CopyToMemoryBuffer(*p.target, dicom);
+  }
+
+
+  void PluginsHttpHandler::RestApiGet(const void* parameters)
+  {
+    const _OrthancPluginRestApiGet& p = 
+      *reinterpret_cast<const _OrthancPluginRestApiGet*>(parameters);
+        
+    HttpHandler::Arguments headers;  // No HTTP header
+    std::string body;  // No body for a GET request
+
+    UriComponents uri;
+    HttpHandler::Arguments getArguments;
+    HttpHandler::ParseGetQuery(uri, getArguments, p.uri);
+
+    StringHttpOutput stream;
+    HttpOutput http(stream);
+
+    LOG(INFO) << "Plugin making REST GET call on URI " << p.uri;
+
+    if (pimpl_->restApi_ != NULL &&
+        pimpl_->restApi_->Handle(http, HttpMethod_Get, uri, headers, getArguments, body))
+    {
+      std::string result;
+      stream.GetOutput(result);
+      CopyToMemoryBuffer(*p.target, result);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+  }
+
+
+  void PluginsHttpHandler::RestApiPostPut(bool isPost, const void* parameters)
+  {
+    const _OrthancPluginRestApiPostPut& p = 
+      *reinterpret_cast<const _OrthancPluginRestApiPostPut*>(parameters);
+
+    HttpHandler::Arguments headers;  // No HTTP header
+    HttpHandler::Arguments getArguments;  // No GET argument for POST/PUT
+
+    UriComponents uri;
+    Toolbox::SplitUriComponents(uri, p.uri);
+
+    // TODO Avoid unecessary memcpy
+    std::string body(p.body, p.bodySize);
+
+    StringHttpOutput stream;
+    HttpOutput http(stream);
+
+    HttpMethod method = (isPost ? HttpMethod_Post : HttpMethod_Put);
+    LOG(INFO) << "Plugin making REST " << EnumerationToString(method) << " call on URI " << p.uri;
+
+    if (pimpl_->restApi_ != NULL &&
+        pimpl_->restApi_->Handle(http, method, uri, headers, getArguments, body))
+    {
+      std::string result;
+      stream.GetOutput(result);
+      CopyToMemoryBuffer(*p.target, result);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+  }
+
+
+  void PluginsHttpHandler::RestApiDelete(const void* parameters)
+  {
+    // The "parameters" point to the URI
+    UriComponents uri;
+    Toolbox::SplitUriComponents(uri, reinterpret_cast<const char*>(parameters));
+
+    HttpHandler::Arguments headers;  // No HTTP header
+    HttpHandler::Arguments getArguments;  // No GET argument for POST/PUT
+    std::string body;  // No body for DELETE
+
+    StringHttpOutput stream;
+    HttpOutput http(stream);
+
+    LOG(INFO) << "Plugin making REST DELETE call on URI " 
+              << reinterpret_cast<const char*>(parameters);
+
+    if (pimpl_->restApi_ == NULL ||
+        !pimpl_->restApi_->Handle(http, HttpMethod_Delete, uri, headers, getArguments, body))
+    {
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+  }
+
+
+  bool PluginsHttpHandler::InvokeService(_OrthancPluginService service,
+                                         const void* parameters)
+  {
+    switch (service)
+    {
+      case _OrthancPluginService_RegisterRestCallback:
+        RegisterRestCallback(parameters);
+        return true;
+
+      case _OrthancPluginService_AnswerBuffer:
+        AnswerBuffer(parameters);
+        return true;
+
+      case _OrthancPluginService_CompressAndAnswerPngImage:
+        CompressAndAnswerPngImage(parameters);
+        return true;
+
+      case _OrthancPluginService_GetDicomForInstance:
+        GetDicomForInstance(parameters);
+        return true;
+
+      case _OrthancPluginService_RestApiGet:
+        RestApiGet(parameters);
+        return true;
+
+      case _OrthancPluginService_RestApiPost:
+        RestApiPostPut(true, parameters);
+        return true;
+
+      case _OrthancPluginService_RestApiDelete:
+        RestApiDelete(parameters);
+        return true;
+
+      case _OrthancPluginService_RestApiPut:
+        RestApiPostPut(false, parameters);
+        return true;
+
+      case _OrthancPluginService_Redirect:
+        Redirect(parameters);
+        return true;
+
+      default:
+        return false;
+    }
+  }
+
+
+  void PluginsHttpHandler::SetOrthancRestApi(OrthancRestApi& restApi)
+  {
+    pimpl_->restApi_ = &restApi;
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Engine/PluginsHttpHandler.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,86 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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 "PluginsManager.h"
+#include "../../Core/HttpServer/HttpHandler.h"
+#include "../../OrthancServer/ServerContext.h"
+#include "../../OrthancServer/OrthancRestApi/OrthancRestApi.h"
+#include "../OrthancCPlugin/OrthancCPlugin.h"
+
+#include <list>
+#include <boost/shared_ptr.hpp>
+
+namespace Orthanc
+{
+  class PluginsHttpHandler : public HttpHandler, public IPluginServiceProvider
+  {
+  private:
+    struct PImpl;
+
+    boost::shared_ptr<PImpl> pimpl_;
+
+    void RegisterRestCallback(const void* parameters);
+
+    void AnswerBuffer(const void* parameters);
+
+    void Redirect(const void* parameters);
+
+    void CompressAndAnswerPngImage(const void* parameters);
+
+    void GetDicomForInstance(const void* parameters);
+
+    void RestApiGet(const void* parameters);
+
+    void RestApiPostPut(bool isPost, const void* parameters);
+
+    void RestApiDelete(const void* parameters);
+
+  public:
+    PluginsHttpHandler(ServerContext& context);
+
+    virtual ~PluginsHttpHandler();
+
+    virtual bool Handle(HttpOutput& output,
+                        HttpMethod method,
+                        const UriComponents& uri,
+                        const Arguments& headers,
+                        const Arguments& getArguments,
+                        const std::string& postData);
+
+    virtual bool InvokeService(_OrthancPluginService service,
+                               const void* parameters);
+
+    void SetOrthancRestApi(OrthancRestApi& restApi);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Engine/PluginsManager.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,299 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "PluginsManager.h"
+
+#include "../../Core/Toolbox.h"
+#include "../../Core/HttpServer/HttpOutput.h"
+
+#include <glog/logging.h>
+#include <cassert>
+#include <memory>
+#include <boost/filesystem.hpp>
+
+#ifdef WIN32
+#define PLUGIN_EXTENSION ".dll"
+#elif defined(__linux)
+#define PLUGIN_EXTENSION ".so"
+#else
+#error Support your platform here
+#endif
+
+
+namespace Orthanc
+{
+  static void CallInitialize(SharedLibrary& plugin,
+                             const OrthancPluginContext& context)
+  {
+    typedef int32_t (*Initialize) (const OrthancPluginContext*);
+
+#if defined(_WIN32)
+    Initialize initialize = (Initialize) plugin.GetFunction("OrthancPluginInitialize");
+#else
+    /**
+     * gcc would complain about "ISO C++ forbids casting between
+     * pointer-to-function and pointer-to-object" without the trick
+     * below, that is known as "the POSIX.1-2003 (Technical Corrigendum
+     * 1) workaround". See the man page of "dlsym()".
+     * http://www.trilithium.com/johan/2004/12/problem-with-dlsym/
+     * http://stackoverflow.com/a/14543811/881731
+     **/
+
+    Initialize initialize;
+    *(void **) (&initialize) = plugin.GetFunction("OrthancPluginInitialize");
+#endif
+
+    assert(initialize != NULL);
+    int32_t error = initialize(&context);
+
+    if (error != 0)
+    {
+      LOG(ERROR) << "Error while initializing plugin " << plugin.GetPath()
+                 << " (code " << error << ")";
+      throw OrthancException(ErrorCode_SharedLibrary);
+    }
+  }
+
+
+  static void CallFinalize(SharedLibrary& plugin)
+  {
+    typedef void (*Finalize) ();
+
+#if defined(_WIN32)
+    Finalize finalize = (Finalize) plugin.GetFunction("OrthancPluginFinalize");
+#else
+    Finalize finalize;
+    *(void **) (&finalize) = plugin.GetFunction("OrthancPluginFinalize");
+#endif
+
+    assert(finalize != NULL);
+    finalize();
+  }
+
+
+  static const char* CallGetName(SharedLibrary& plugin)
+  {
+    typedef const char* (*GetName) ();
+
+#if defined(_WIN32)
+    GetName getName = (GetName) plugin.GetFunction("OrthancPluginGetName");
+#else
+    GetName getName;
+    *(void **) (&getName) = plugin.GetFunction("OrthancPluginGetName");
+#endif
+
+    assert(getName != NULL);
+    return getName();
+  }
+
+
+  static const char* CallGetVersion(SharedLibrary& plugin)
+  {
+    typedef const char* (*GetVersion) ();
+
+#if defined(_WIN32)
+    GetVersion getVersion = (GetVersion) plugin.GetFunction("OrthancPluginGetVersion");
+#else
+    GetVersion getVersion;
+    *(void **) (&getVersion) = plugin.GetFunction("OrthancPluginGetVersion");
+#endif
+
+    assert(getVersion != NULL);
+    return getVersion();
+  }
+
+
+  int32_t PluginsManager::InvokeService(OrthancPluginContext* context,
+                                        _OrthancPluginService service, 
+                                        const void* params)
+  {
+    switch (service)
+    {
+      case _OrthancPluginService_LogError:
+        LOG(ERROR) << reinterpret_cast<const char*>(params);
+        return 0;
+
+      case _OrthancPluginService_LogWarning:
+        LOG(WARNING) << reinterpret_cast<const char*>(params);
+        return 0;
+
+      case _OrthancPluginService_LogInfo:
+        LOG(INFO) << reinterpret_cast<const char*>(params);
+        return 0;
+
+      default:
+        break;
+    }
+
+    PluginsManager* that = reinterpret_cast<PluginsManager*>(context->pluginsManager);
+    bool error = false;
+
+    for (std::list<IPluginServiceProvider*>::iterator
+           it = that->serviceProviders_.begin(); 
+         it != that->serviceProviders_.end(); ++it)
+    {
+      try
+      {
+        if ((*it)->InvokeService(service, params))
+        {
+          return 0;
+        }
+      }
+      catch (OrthancException&)
+      {
+        // This service provider has failed, go to the next
+        error = true;
+      }
+    }
+
+    if (error)
+    {
+      LOG(ERROR) << "Exception when dealing with service " << service;
+    }
+    else
+    {
+      LOG(ERROR) << "Plugin invoking unknown service " << service;
+    }
+
+    return -1;
+  }
+
+
+  PluginsManager::PluginsManager()
+  {
+    memset(&context_, 0, sizeof(context_));
+    context_.pluginsManager = this;
+    context_.orthancVersion = ORTHANC_VERSION;
+    context_.Free = ::free;
+    context_.InvokeService = InvokeService;
+  }
+
+  PluginsManager::~PluginsManager()
+  {
+    for (Plugins::iterator it = plugins_.begin(); it != plugins_.end(); ++it)
+    {
+      if (it->second != NULL)
+      {
+        LOG(WARNING) << "Unregistering plugin '" << CallGetName(*it->second)
+                     << "' (version " << CallGetVersion(*it->second) << ")";
+
+        CallFinalize(*(it->second));
+        delete it->second;
+      }
+    }
+  }
+
+
+  static bool IsOrthancPlugin(SharedLibrary& library)
+  {
+    return (library.HasFunction("OrthancPluginInitialize") &&
+            library.HasFunction("OrthancPluginFinalize") &&
+            library.HasFunction("OrthancPluginGetName") &&
+            library.HasFunction("OrthancPluginGetVersion"));
+  }
+
+  
+  void PluginsManager::RegisterPlugin(const std::string& path)
+  {
+    std::auto_ptr<SharedLibrary> plugin(new SharedLibrary(path));
+
+    if (!IsOrthancPlugin(*plugin))
+    {
+      LOG(ERROR) << "Plugin " << plugin->GetPath()
+                 << " does not declare the proper entry functions";
+      throw OrthancException(ErrorCode_SharedLibrary);
+    }
+
+    std::string name(CallGetName(*plugin));
+    if (plugins_.find(name) != plugins_.end())
+    {
+      LOG(ERROR) << "Plugin '" << name << "' already registered";
+      throw OrthancException(ErrorCode_SharedLibrary);
+    }
+
+    LOG(WARNING) << "Registering plugin '" << name
+                 << "' (version " << CallGetVersion(*plugin) << ")";
+
+    CallInitialize(*plugin, context_);
+
+    plugins_[name] = plugin.release();
+  }
+
+
+  void PluginsManager::ScanFolderForPlugins(const std::string& folder,
+                                            bool isRecursive)
+  {
+    using namespace boost::filesystem;
+
+    if (!exists(folder))
+    {
+      return;
+    }
+
+    LOG(INFO) << "Scanning folder " << folder << " for plugins";
+
+    directory_iterator end_it; // default construction yields past-the-end
+    for (directory_iterator it(folder);
+          it != end_it;
+          ++it)
+    {
+      std::string path = it->path().string();
+
+      if (is_directory(it->status()))
+      {
+        if (isRecursive)
+        {
+          ScanFolderForPlugins(path, true);
+        }
+      }
+      else
+      {
+        if (boost::filesystem::extension(it->path()) == PLUGIN_EXTENSION)
+        {
+          LOG(INFO) << "Found a shared library: " << it->path();
+
+          try
+          {
+            SharedLibrary plugin(path);
+            if (IsOrthancPlugin(plugin))
+            {
+              RegisterPlugin(path);
+            }
+          }
+          catch (OrthancException&)
+          {
+          }
+        }
+      }
+    }
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Engine/PluginsManager.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,71 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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 "SharedLibrary.h"
+#include "IPluginServiceProvider.h"
+
+#include <map>
+#include <list>
+
+namespace Orthanc
+{
+  class PluginsManager : boost::noncopyable
+  {
+  private:
+    typedef std::map<std::string, SharedLibrary*>  Plugins;
+
+    OrthancPluginContext  context_;
+    Plugins  plugins_;
+    std::list<IPluginServiceProvider*> serviceProviders_;
+
+    static int32_t InvokeService(OrthancPluginContext* context,
+                                 _OrthancPluginService service,
+                                 const void* parameters);
+
+  public:
+    PluginsManager();
+
+    ~PluginsManager();
+
+    void RegisterPlugin(const std::string& path);
+
+    void ScanFolderForPlugins(const std::string& path,
+                              bool isRecursive);
+
+    void RegisterServiceProvider(IPluginServiceProvider& provider)
+    {
+      serviceProviders_.push_back(&provider);
+    }
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Engine/SharedLibrary.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,133 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "SharedLibrary.h"
+
+#include "../../Core/Toolbox.h"
+
+#if defined(_WIN32)
+#include <windows.h>
+#elif defined(__linux)
+#include <dlfcn.h>
+#else
+#error Support your platform here
+#endif
+
+#include <glog/logging.h>
+
+namespace Orthanc
+{
+  SharedLibrary::SharedLibrary(const std::string& path) : 
+    path_(path),
+    handle_(NULL)
+  {
+#if defined(_WIN32)
+    handle_ = ::LoadLibraryA(path.c_str());
+    if (handle_ == NULL)
+    {
+      LOG(ERROR) << "LoadLibrary(" << path << ") failed: Error " << ::GetLastError();
+      throw OrthancException(ErrorCode_SharedLibrary);
+    }
+
+#elif defined(__linux)
+    handle_ = ::dlopen(path.c_str(), RTLD_NOW);
+    if (handle_ == NULL) 
+    {
+      std::string explanation;
+      const char *tmp = ::dlerror();
+      if (tmp)
+      {
+        explanation = ": Error " + std::string(tmp);
+      }
+
+      LOG(ERROR) << "dlopen(" << path << ") failed" << explanation;
+      throw OrthancException(ErrorCode_SharedLibrary);
+    }
+
+#else
+#error Support your platform here
+#endif   
+  }
+
+  SharedLibrary::~SharedLibrary()
+  {
+    if (handle_)
+    {
+#if defined(_WIN32)
+      ::FreeLibrary((HMODULE)handle_);
+#elif defined(__linux)
+      ::dlclose(handle_);
+#else
+#error Support your platform here
+#endif
+    }
+  }
+
+
+  SharedLibrary::FunctionPointer SharedLibrary::GetFunctionInternal(const std::string& name)
+  {
+    if (!handle_)
+    {
+      throw OrthancException(ErrorCode_InternalError);
+    }
+
+#if defined(_WIN32)
+    return ::GetProcAddress((HMODULE)handle_, name.c_str());
+#elif defined(__linux)
+    return ::dlsym(handle_, name.c_str());
+#else
+#error Support your platform here
+#endif
+  }
+
+
+  SharedLibrary::FunctionPointer SharedLibrary::GetFunction(const std::string& name)
+  {
+    SharedLibrary::FunctionPointer result = GetFunctionInternal(name);
+  
+    if (result == NULL)
+    {
+      LOG(ERROR) << "Shared library does not expose function \"" << name << "\"";
+      throw OrthancException(ErrorCode_SharedLibrary);
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  bool SharedLibrary::HasFunction(const std::string& name)
+  {
+    return GetFunctionInternal(name) != NULL;
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Engine/SharedLibrary.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,70 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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 "../../Core/OrthancException.h"
+
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class SharedLibrary : boost::noncopyable
+  {
+  public:
+#if defined(_WIN32)
+    typedef FARPROC FunctionPointer;
+#else
+    typedef void* FunctionPointer;
+#endif
+
+  private:
+    std::string path_;
+    void *handle_;
+
+    FunctionPointer GetFunctionInternal(const std::string& name);
+
+  public:
+    SharedLibrary(const std::string& path);
+
+    ~SharedLibrary();
+
+    const std::string& GetPath() const
+    {
+      return path_;
+    }
+
+    bool HasFunction(const std::string& name);
+
+    FunctionPointer GetFunction(const std::string& name);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/OrthancCPlugin/OrthancCPlugin.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,702 @@
+/**
+ * \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 store the context pointer so that it can use the plugin 
+ *    services of Orthanc. It must also register all its callbacks using
+ *    ::OrthancPluginRegisterRestCallback().
+ * -# <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.
+ * 
+ * 
+ **/
+
+
+
+/**
+ * @defgroup CInterface C Interface 
+ * @brief The C interface to create Orthanc plugins.
+ * 
+ * These functions must be used to create C plugins for Orthanc.
+ **/
+
+
+
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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
+
+
+
+/********************************************************************
+ ** 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.
+ ********************************************************************/
+
+#ifdef _MSC_VER
+#include "../../Resources/VisualStudio/stdint.h"
+#else
+#include <stdint.h>
+#endif
+
+#include <stdlib.h>
+
+
+
+/********************************************************************
+ ** Definition of the Orthanc Plugin API.
+ ********************************************************************/
+
+/** @{ */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+  /**
+   * 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;
+
+
+  /**
+   * @brief The parameters of a REST request.
+   **/
+  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;
+  } OrthancPluginHttpRequest;
+
+  typedef enum 
+  {
+    /* Generic services */
+    _OrthancPluginService_LogInfo = 1,
+    _OrthancPluginService_LogWarning = 2,
+    _OrthancPluginService_LogError = 3,
+
+    /* Registration of callbacks */
+    _OrthancPluginService_RegisterRestCallback = 1000,
+
+    /* Sending answers to REST calls */
+    _OrthancPluginService_AnswerBuffer = 2000,
+    _OrthancPluginService_CompressAndAnswerPngImage = 2001,
+    _OrthancPluginService_Redirect = 2002,
+
+    /* Access to the Orthanc database and API */
+    _OrthancPluginService_GetDicomForInstance = 3000,
+    _OrthancPluginService_RestApiGet = 3001,
+    _OrthancPluginService_RestApiPost = 3002,
+    _OrthancPluginService_RestApiDelete = 3003,
+    _OrthancPluginService_RestApiPut = 3004
+  } _OrthancPluginService;
+
+
+
+  /**
+   * The memory layout of the pixels of an image.
+   **/
+  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;
+
+
+  /**
+   * @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.
+   **/
+  typedef struct _OrthancPluginRestOutput_t OrthancPluginRestOutput;
+
+
+  /**
+   * @brief Signature of a callback function that answers to a REST request.
+   **/
+  typedef int32_t (*OrthancPluginRestCallback) (
+    OrthancPluginRestOutput* output,
+    const char* url,
+    const OrthancPluginHttpRequest* request);
+
+
+  /**
+   * @brief Opaque structure that contains information about the Orthanc core.
+   **/
+  typedef struct _OrthancPluginContext_t
+  {
+    void*        pluginsManager;
+    const char*  orthancVersion;
+    void       (*Free) (void* buffer);
+    int32_t    (*InvokeService) (struct _OrthancPluginContext_t* context,
+                                 _OrthancPluginService service,
+                                 const void* params);
+  } OrthancPluginContext;
+
+
+
+  /**
+   * @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)
+  {
+    context->Free(str);
+  }
+
+
+  /**
+   * @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.
+   * 
+   * @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.
+   **/
+  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);
+  }
+
+
+
+  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.
+   **/
+  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;
+
+  /**
+   * @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.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginCompressAndAnswerPngImage(
+    OrthancPluginContext*     context,
+    OrthancPluginRestOutput*  output,
+    OrthancPluginPixelFormat  format,
+    uint32_t                  width,
+    uint32_t                  height,
+    uint32_t                  pitch,
+    const void*               buffer)
+  {
+    _OrthancPluginCompressAndAnswerPngImage params;
+    params.output = output;
+    params.format = format;
+    params.width = width;
+    params.height = height;
+    params.pitch = pitch;
+    params.buffer = buffer;
+    context->InvokeService(context, _OrthancPluginService_CompressAndAnswerPngImage, &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.
+   * @param instanceId The Orthanc identifier of the DICOM instance of interest.
+   * @return 0 if success, other value if error.
+   **/
+  ORTHANC_PLUGIN_INLINE int  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.
+   * @param uri The URI in the built-in Orthanc API.
+   * @return 0 if success, other value if error.
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginRestApiGet(
+    OrthancPluginContext*       context,
+    OrthancPluginMemoryBuffer*  target,
+    const char*                 uri)
+  {
+    _OrthancPluginRestApiGet params;
+    params.target = target;
+    params.uri = uri;
+    return context->InvokeService(context, _OrthancPluginService_RestApiGet, &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.
+   * @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, other value if error.
+   **/
+  ORTHANC_PLUGIN_INLINE int  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 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, other value if error.
+   **/
+  ORTHANC_PLUGIN_INLINE int  OrthancPluginRestApiDelete(
+    OrthancPluginContext*       context,
+    const char*                 uri)
+  {
+    return context->InvokeService(context, _OrthancPluginService_RestApiDelete, 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.
+   * @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, other value if error.
+   **/
+  ORTHANC_PLUGIN_INLINE int  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);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginRestOutput* output;
+    const char*              redirection;
+  } _OrthancPluginRedirect;
+
+  /**
+   * @brief Redirect a GET 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.
+   **/
+  ORTHANC_PLUGIN_INLINE void OrthancPluginRedirect(
+    OrthancPluginContext*    context,
+    OrthancPluginRestOutput* output,
+    const char*              redirection)
+  {
+    _OrthancPluginRedirect params;
+    params.output = output;
+    params.redirection = redirection;
+    context->InvokeService(context, _OrthancPluginService_Redirect, &params);
+  }
+
+
+#ifdef  __cplusplus
+}
+#endif
+
+
+/** @} */
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/Basic/CMakeLists.txt	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,17 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(Basic)
+
+if (${CMAKE_COMPILER_IS_GNUCXX})
+  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -pedantic -Werror")
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic -Werror")
+endif()
+
+include_directories(${CMAKE_SOURCE_DIR}/../../OrthancCPlugin/)
+add_library(PluginTest SHARED Plugin.c)
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+  # Linking with "pthread" is necessary, otherwise the software crashes
+  # http://sourceware.org/bugzilla/show_bug.cgi?id=10652#c17
+  target_link_libraries(PluginTest pthread dl)
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/Basic/Plugin.c	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,195 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ **/
+
+
+#include <OrthancCPlugin.h>
+
+#include <string.h>
+#include <stdio.h>
+
+static OrthancPluginContext* context = NULL;
+
+
+ORTHANC_PLUGINS_API int32_t Callback1(OrthancPluginRestOutput* output,
+                                      const char* url,
+                                      const OrthancPluginHttpRequest* request)
+{
+  char buffer[1024];
+  uint32_t i;
+
+  sprintf(buffer, "Callback on URL [%s] with body [%s]", url, request->body);
+  OrthancPluginLogInfo(context, buffer);
+
+  OrthancPluginAnswerBuffer(context, output, buffer, strlen(buffer), "text/plain");
+
+  for (i = 0; i < request->getCount; i++)
+  {
+    sprintf(buffer, "  [%s] = [%s]", request->getKeys[i], request->getValues[i]);
+    OrthancPluginLogInfo(context, buffer);    
+  }
+
+  printf("** %d\n", request->groupsCount);
+  for (i = 0; i < request->groupsCount; i++)
+  {
+    printf("** [%s]\n",  request->groups[i]);
+  }
+
+  return 1;
+}
+
+
+ORTHANC_PLUGINS_API int32_t Callback2(OrthancPluginRestOutput* output,
+                                      const char* url,
+                                      const OrthancPluginHttpRequest* request)
+{
+  /* Answer with a sample 16bpp image. */
+
+  uint16_t buffer[256 * 256];
+  uint32_t x, y, value;
+
+  value = 0;
+  for (y = 0; y < 256; y++)
+  {
+    for (x = 0; x < 256; x++, value++)
+    {
+      buffer[value] = value;
+    }
+  }
+
+  OrthancPluginCompressAndAnswerPngImage(context, output, OrthancPluginPixelFormat_Grayscale16,
+                                         256, 256, sizeof(uint16_t) * 256, buffer);
+  return 0;
+}
+
+
+ORTHANC_PLUGINS_API int32_t Callback3(OrthancPluginRestOutput* output,
+                                      const char* url,
+                                      const OrthancPluginHttpRequest* request)
+{
+  OrthancPluginMemoryBuffer dicom;
+  if (!OrthancPluginGetDicomForInstance(context, &dicom, request->groups[0]))
+  {
+    /* No error, forward the DICOM file */
+    OrthancPluginAnswerBuffer(context, output, dicom.data, dicom.size, "application/dicom");
+
+    /* Free memory */
+    OrthancPluginFreeMemoryBuffer(context, &dicom);
+  }
+
+  return 0;
+}
+
+
+ORTHANC_PLUGINS_API int32_t Callback4(OrthancPluginRestOutput* output,
+                                      const char* url,
+                                      const OrthancPluginHttpRequest* request)
+{
+  /* Answer with a sample 8bpp image. */
+
+  uint8_t  buffer[256 * 256];
+  uint32_t x, y, value;
+
+  value = 0;
+  for (y = 0; y < 256; y++)
+  {
+    for (x = 0; x < 256; x++, value++)
+    {
+      buffer[value] = x;
+    }
+  }
+
+  OrthancPluginCompressAndAnswerPngImage(context, output, OrthancPluginPixelFormat_Grayscale8,
+                                         256, 256, 256, buffer);
+  return 0;
+}
+
+
+ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* c)
+{
+  const char* pathLocator = "\"Path\" : \"";
+  OrthancPluginMemoryBuffer tmp;
+  char info[1024];
+  char *id, *eos;
+
+  context = c;
+  OrthancPluginLogWarning(context, "Sample plugin is initializing");
+
+  sprintf(info, "The version of Orthanc is '%s'", context->orthancVersion);
+  OrthancPluginLogInfo(context, info);
+
+  OrthancPluginRegisterRestCallback(context, "/(plu.*)/hello", Callback1);
+  OrthancPluginRegisterRestCallback(context, "/plu.*/image", Callback2);
+  OrthancPluginRegisterRestCallback(context, "/plugin/instances/([^/]+)/info", Callback3);
+
+  OrthancPluginRegisterRestCallback(context, "/instances/([^/]+)/preview", Callback4);
+
+  /* Make REST requests to the built-in Orthanc API */
+  OrthancPluginRestApiGet(context, &tmp, "/changes");
+  OrthancPluginFreeMemoryBuffer(context, &tmp);
+  OrthancPluginRestApiGet(context, &tmp, "/changes?limit=1");
+  OrthancPluginFreeMemoryBuffer(context, &tmp);
+ 
+  /* Make POST request to create a new DICOM instance */
+  sprintf(info, "{\"PatientName\":\"Test\"}");
+  OrthancPluginRestApiPost(context, &tmp, "/tools/create-dicom", info, strlen(info));
+
+  /**
+   * Recover he ID of the created instance is constructed by a
+   * quick-and-dirty parsing of a JSON string.
+   **/
+  id = strstr((char*) tmp.data, pathLocator) + strlen(pathLocator);
+  eos = strchr(id, '\"');
+  eos[0] = '\0';
+
+  /* Delete the newly created DICOM instance. */
+  OrthancPluginRestApiDelete(context, id);
+  OrthancPluginFreeMemoryBuffer(context, &tmp);
+
+  /* Play with PUT by defining a new target modality. */
+  sprintf(info, "[ \"STORESCP\", \"localhost\", 2000 ]");
+  OrthancPluginRestApiPut(context, &tmp, "/modalities/demo", info, strlen(info));
+
+  return 0;
+}
+
+
+ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+{
+  OrthancPluginLogWarning(context, "Sample plugin is finalizing");
+}
+
+
+ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+{
+  return "sample";
+}
+
+
+ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+{
+  return "1.0";
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/GdcmDecoding/CMakeLists.txt	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,56 @@
+cmake_minimum_required(VERSION 2.8)
+
+project(GdcmDecoding)
+
+SET(ALLOW_DOWNLOADS ON CACHE BOOL "Allow CMake to download packages")
+SET(USE_SYSTEM_BOOST ON CACHE BOOL "Use the system version of Boost")
+SET(USE_SYSTEM_GOOGLE_LOG OFF CACHE BOOL "Use the system version of Google Log")
+
+set(ORTHANC_ROOT ${CMAKE_SOURCE_DIR}/../../..)
+
+if (${CMAKE_COMPILER_IS_GNUCXX})
+  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
+  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined")
+endif()
+
+include(${ORTHANC_ROOT}/Resources/CMake/DownloadPackage.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/BoostConfiguration.cmake)
+include(${ORTHANC_ROOT}/Resources/CMake/GoogleLogConfiguration.cmake)
+
+find_package(GDCM REQUIRED)
+if (GDCM_FOUND)
+  include(${GDCM_USE_FILE})
+  set(GDCM_LIBRARIES gdcmCommon gdcmMSFF)
+else(GDCM_FOUND)
+  message(FATAL_ERROR "Cannot find GDCM, did you set GDCM_DIR?")
+endif(GDCM_FOUND)
+
+include_directories(
+  ${ORTHANC_ROOT}/Plugins/OrthancCPlugin/
+  ${ORTHANC_ROOT}/OrthancCppClient/SharedLibrary/Laaw
+  )
+
+add_library(GdcmDecoding SHARED
+  Plugin.cpp
+  OrthancContext.cpp
+
+  # Sources from Orthanc
+  ${GOOGLE_LOG_SOURCES}
+  ${ORTHANC_ROOT}/Core/ChunkedBuffer.cpp
+  ${ORTHANC_ROOT}/Core/Enumerations.cpp
+  ${ORTHANC_ROOT}/Core/ImageFormats/ImageAccessor.cpp
+  ${ORTHANC_ROOT}/Core/ImageFormats/ImageBuffer.cpp
+  ${ORTHANC_ROOT}/Core/ImageFormats/ImageProcessing.cpp
+  ${ORTHANC_ROOT}/Core/OrthancException.cpp
+  ${ORTHANC_ROOT}/Core/Toolbox.cpp
+  ${ORTHANC_ROOT}/Resources/ThirdParty/base64/base64.cpp
+  ${ORTHANC_ROOT}/Resources/ThirdParty/md5/md5.c
+  ${THIRD_PARTY_SOURCES}
+  )
+
+target_link_libraries(GdcmDecoding ${GDCM_LIBRARIES})
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+  target_link_libraries(GdcmDecoding pthread dl rt)
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/GdcmDecoding/OrthancContext.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,167 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ **/
+
+
+#include "OrthancContext.h"
+
+#include <stdexcept>
+
+
+void OrthancContext::Check()
+{
+  if (context_ == NULL)
+  {
+    throw std::runtime_error("The Orthanc plugin context is not initialized");
+  }
+}
+
+
+OrthancContext& OrthancContext::GetInstance()
+{
+  static OrthancContext instance;
+  return instance;
+}
+
+
+OrthancContext::~OrthancContext()
+{
+  if (context_ != NULL)
+  {
+    throw std::runtime_error("The Orthanc plugin was not properly finalized");
+  }
+}
+
+
+void OrthancContext::ExtractGetArguments(Arguments& arguments,
+                                         const OrthancPluginHttpRequest& request)
+{
+  Check();
+  arguments.clear();
+
+  for (uint32_t i = 0; i < request.getCount; i++)
+  {
+    arguments[request.getKeys[i]] = request.getValues[i];
+  }
+}
+
+
+void OrthancContext::LogError(const std::string& s)
+{
+  Check();
+  OrthancPluginLogError(context_, s.c_str());
+}
+
+
+void OrthancContext::LogWarning(const std::string& s)
+{
+  Check();
+  OrthancPluginLogWarning(context_, s.c_str());
+}
+
+
+void OrthancContext::LogInfo(const std::string& s)
+{
+  Check();
+  OrthancPluginLogInfo(context_, s.c_str());
+}
+  
+
+void OrthancContext::Register(const std::string& uri,
+                              OrthancPluginRestCallback callback)
+{
+  Check();
+  OrthancPluginRegisterRestCallback(context_, uri.c_str(), callback);
+}
+
+
+void OrthancContext::GetDicomForInstance(std::string& result,
+                                         const std::string& instanceId)
+{
+  Check();
+  OrthancPluginMemoryBuffer buffer;
+    
+  if (OrthancPluginGetDicomForInstance(context_, &buffer, instanceId.c_str()))
+  {
+    throw std::runtime_error("No DICOM instance with Orthanc ID: " + instanceId);
+  }
+
+  if (buffer.size == 0)
+  {
+    result.clear();
+  }
+  else
+  {
+    result.assign(reinterpret_cast<char*>(buffer.data), buffer.size);
+  }
+
+  OrthancPluginFreeMemoryBuffer(context_, &buffer);
+}
+
+
+void OrthancContext::CompressAndAnswerPngImage(OrthancPluginRestOutput* output,
+                                               const Orthanc::ImageAccessor& accessor)
+{
+  Check();
+
+  OrthancPluginPixelFormat format;
+  switch (accessor.GetFormat())
+  {
+    case Orthanc::PixelFormat_Grayscale8:
+      format = OrthancPluginPixelFormat_Grayscale8;
+      break;
+
+    case Orthanc::PixelFormat_Grayscale16:
+      format = OrthancPluginPixelFormat_Grayscale16;
+      break;
+
+    case Orthanc::PixelFormat_SignedGrayscale16:
+      format = OrthancPluginPixelFormat_SignedGrayscale16;
+      break;
+
+    case Orthanc::PixelFormat_RGB24:
+      format = OrthancPluginPixelFormat_RGB24;
+      break;
+
+    case Orthanc::PixelFormat_RGBA32:
+      format = OrthancPluginPixelFormat_RGBA32;
+      break;
+
+    default:
+      throw std::runtime_error("Unsupported pixel format");
+  }
+
+  OrthancPluginCompressAndAnswerPngImage(context_, output, format, accessor.GetWidth(),
+                                         accessor.GetHeight(), accessor.GetPitch(), accessor.GetConstBuffer());
+}
+
+
+
+void OrthancContext::Redirect(OrthancPluginRestOutput* output,
+                              const std::string& s)
+{
+  Check();
+  OrthancPluginRedirect(context_, output, s.c_str());
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/GdcmDecoding/OrthancContext.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,87 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ **/
+
+
+#pragma once
+
+#include <OrthancCPlugin.h>
+
+#include "../../../Core/ImageFormats/ImageBuffer.h"
+
+#include <map>
+#include <string>
+#include <boost/noncopyable.hpp>
+
+
+class OrthancContext : public boost::noncopyable
+{
+private:
+  OrthancPluginContext* context_;
+
+  OrthancContext() : context_(NULL)
+  {
+  }
+
+  void Check();
+
+public:
+  typedef std::map<std::string, std::string>  Arguments;
+
+  static OrthancContext& GetInstance();
+
+  ~OrthancContext();
+
+  void Initialize(OrthancPluginContext* context)
+  {
+    context_ = context;
+  }
+
+  void Finalize()
+  {
+    context_ = NULL;
+  }
+
+  void ExtractGetArguments(Arguments& arguments,
+                           const OrthancPluginHttpRequest& request);
+
+  void LogError(const std::string& s);
+
+  void LogWarning(const std::string& s);
+
+  void LogInfo(const std::string& s);
+  
+  void Register(const std::string& uri,
+                OrthancPluginRestCallback callback);
+
+  void GetDicomForInstance(std::string& result,
+                           const std::string& instanceId);
+
+  void CompressAndAnswerPngImage(OrthancPluginRestOutput* output,
+                                 const Orthanc::ImageAccessor& accessor);
+
+  void Redirect(OrthancPluginRestOutput* output,
+                const std::string& s);
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/Samples/GdcmDecoding/Plugin.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,257 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ **/
+
+
+#include <map>
+#include <string>
+#include <stdexcept>
+#include <sstream>
+
+#include "OrthancContext.h"
+#include "../../../Core/ImageFormats/ImageProcessing.h"
+
+#include <gdcmReader.h>
+#include <gdcmImageReader.h>
+#include <gdcmImageChangePlanarConfiguration.h>
+
+
+static void AnswerUnsupportedImage(OrthancPluginRestOutput* output)
+{
+  OrthancContext::GetInstance().Redirect(output, "/app/images/unsupported.png");
+}
+
+
+static bool GetOrthancPixelFormat(Orthanc::PixelFormat& format,
+                                  const gdcm::Image& image)
+{
+  if (image.GetPlanarConfiguration() != 0 && 
+      image.GetPixelFormat().GetSamplesPerPixel() != 1)
+  {
+    OrthancContext::GetInstance().LogError("Planar configurations are not supported");
+    return false;
+  }
+
+  if (image.GetPixelFormat().GetSamplesPerPixel() == 1)
+  {
+    switch (image.GetPixelFormat().GetScalarType())
+    {
+      case gdcm::PixelFormat::UINT8:
+        format = Orthanc::PixelFormat_Grayscale8;
+        return true;
+
+      case gdcm::PixelFormat::UINT16:
+        format = Orthanc::PixelFormat_Grayscale16;
+        return true;
+
+      case gdcm::PixelFormat::INT16:
+        format = Orthanc::PixelFormat_SignedGrayscale16;
+        return true;
+
+      default:
+        return false;
+    }
+  }
+  else if (image.GetPixelFormat().GetSamplesPerPixel() == 3 &&
+           image.GetPixelFormat().GetScalarType() == gdcm::PixelFormat::UINT8)
+  {
+    format = Orthanc::PixelFormat_RGB24;
+    return true;
+  }
+  else if (image.GetPixelFormat().GetSamplesPerPixel() == 4 &&
+           image.GetPixelFormat().GetScalarType() == gdcm::PixelFormat::UINT8)
+  {
+    format = Orthanc::PixelFormat_RGBA32;
+    return true;
+  }
+  
+  return false;
+}
+
+
+ORTHANC_PLUGINS_API int32_t DecodeImage(OrthancPluginRestOutput* output,
+                                        const char* url,
+                                        const OrthancPluginHttpRequest* request)
+{
+  std::string instance(request->groups[0]);
+  std::string outputFormat(request->groups[1]);
+  OrthancContext::GetInstance().LogWarning("Using GDCM to decode instance " + instance);
+
+  // Download the request DICOM instance from Orthanc into a memory buffer
+  std::string dicom;
+  OrthancContext::GetInstance().GetDicomForInstance(dicom, instance);
+
+  // Prepare a memory stream over the DICOM instance
+  std::stringstream stream(dicom);
+
+  // Parse the DICOM instance using GDCM
+  gdcm::ImageReader imageReader;
+  imageReader.SetStream(stream);
+  if (!imageReader.Read())
+  {
+    OrthancContext::GetInstance().LogError("GDCM cannot extract an image from this DICOM instance");
+    AnswerUnsupportedImage(output);
+    return 0;
+  }
+
+  gdcm::Image& image = imageReader.GetImage();
+
+
+  // Log information about the decoded image
+  char tmp[1024];
+  sprintf(tmp, "Image format: %dx%d %s with %d color channel(s)", image.GetRows(), image.GetColumns(), 
+          image.GetPixelFormat().GetScalarTypeAsString(), image.GetPixelFormat().GetSamplesPerPixel());
+  OrthancContext::GetInstance().LogWarning(tmp);
+
+
+  // Convert planar configuration
+  gdcm::ImageChangePlanarConfiguration planar;
+  if (image.GetPlanarConfiguration() != 0 && 
+      image.GetPixelFormat().GetSamplesPerPixel() != 1)
+  {
+    OrthancContext::GetInstance().LogWarning("Converting planar configuration to interleaved");
+    planar.SetInput(imageReader.GetImage());
+    planar.Change();
+    image = planar.GetOutput();
+  }
+
+
+  // Create a read-only accessor to the bitmap decoded by GDCM
+  Orthanc::PixelFormat format;
+  if (!GetOrthancPixelFormat(format, image))
+  {
+    OrthancContext::GetInstance().LogError("This sample plugin does not support this image format");
+    AnswerUnsupportedImage(output);
+    return 0;
+  }
+
+  Orthanc::ImageAccessor decodedImage;
+  std::vector<char> decodedBuffer(image.GetBufferLength());
+
+  if (decodedBuffer.size())
+  {
+    image.GetBuffer(&decodedBuffer[0]);
+    unsigned int pitch = image.GetColumns() * ::Orthanc::GetBytesPerPixel(format);
+    decodedImage.AssignWritable(format, image.GetColumns(), image.GetRows(), pitch, &decodedBuffer[0]);
+  }
+  else
+  {
+    // Empty image
+    decodedImage.AssignWritable(format, 0, 0, 0, NULL);
+  }
+
+
+  // Convert the pixel format from GDCM to the format requested by the REST query
+  Orthanc::ImageBuffer converted;
+  converted.SetWidth(decodedImage.GetWidth());
+  converted.SetHeight(decodedImage.GetHeight());
+
+  if (outputFormat == "preview")
+  {
+    if (format == Orthanc::PixelFormat_RGB24 ||
+        format == Orthanc::PixelFormat_RGBA32)
+    {
+      // Do not rescale color image
+      converted.SetFormat(Orthanc::PixelFormat_RGB24);
+    }
+    else
+    {
+      converted.SetFormat(Orthanc::PixelFormat_Grayscale8);
+
+      // Rescale the image to the [0,255] range
+      int64_t a, b;
+      Orthanc::ImageProcessing::GetMinMaxValue(a, b, decodedImage);
+
+      float offset = -a;
+      float scaling = 255.0f / static_cast<float>(b - a);
+      Orthanc::ImageProcessing::ShiftScale(decodedImage, offset, scaling);
+    }
+  }
+  else
+  {
+    if (format == Orthanc::PixelFormat_RGB24 ||
+        format == Orthanc::PixelFormat_RGBA32)
+    {
+      // Do not convert color images to grayscale values (this is Orthanc convention)
+      AnswerUnsupportedImage(output);
+      return 0;
+    }
+
+    if (outputFormat == "image-uint8")
+    {
+      converted.SetFormat(Orthanc::PixelFormat_Grayscale8);
+    }
+    else if (outputFormat == "image-uint16")
+    {
+      converted.SetFormat(Orthanc::PixelFormat_Grayscale16);
+    }
+    else if (outputFormat == "image-int16")
+    {
+      converted.SetFormat(Orthanc::PixelFormat_SignedGrayscale16);
+    }
+    else
+    {
+      OrthancContext::GetInstance().LogError("Unknown output format: " + outputFormat);
+      AnswerUnsupportedImage(output);
+      return 0;
+    }
+  }
+
+  Orthanc::ImageAccessor convertedAccessor(converted.GetAccessor());
+  Orthanc::ImageProcessing::Convert(convertedAccessor, decodedImage);
+
+  // Compress the converted image as a PNG file
+  OrthancContext::GetInstance().CompressAndAnswerPngImage(output, convertedAccessor);
+
+  return 0;  // Success
+}
+
+
+extern "C"
+{
+  ORTHANC_PLUGINS_API int32_t OrthancPluginInitialize(OrthancPluginContext* context)
+  {
+    OrthancContext::GetInstance().Initialize(context);
+    OrthancContext::GetInstance().LogWarning("Initializing GDCM decoding");
+    OrthancContext::GetInstance().Register("/instances/([^/]+)/(preview|image-uint8|image-uint16|image-int16)", DecodeImage);
+    return 0;
+  }
+
+  ORTHANC_PLUGINS_API void OrthancPluginFinalize()
+  {
+    OrthancContext::GetInstance().LogWarning("Finalizing GDCM decoding");
+    OrthancContext::GetInstance().Finalize();
+  }
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetName()
+  {
+    return "gdcm-decoding";
+  }
+
+  ORTHANC_PLUGINS_API const char* OrthancPluginGetVersion()
+  {
+    return "1.0";
+  }
+}
--- a/Resources/CMake/Compiler.cmake	Wed Jun 25 11:56:48 2014 +0200
+++ b/Resources/CMake/Compiler.cmake	Thu Jul 10 11:42:32 2014 +0200
@@ -32,7 +32,7 @@
     -D_CRT_SECURE_NO_WARNINGS=1
     -D_CRT_SECURE_NO_DEPRECATE=1
     )
-  include_directories(${CMAKE_SOURCE_DIR}/Resources/VisualStudio)
+  include_directories(${CMAKE_SOURCE_DIR}/Resources/ThirdParty/VisualStudio)
   link_libraries(netapi32)
 endif()
 
--- a/Resources/CMake/GoogleLogConfiguration.cmake	Wed Jun 25 11:56:48 2014 +0200
+++ b/Resources/CMake/GoogleLogConfiguration.cmake	Thu Jul 10 11:42:32 2014 +0200
@@ -65,22 +65,22 @@
   if (CMAKE_COMPILER_IS_GNUCXX)
     if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
       execute_process(
-        COMMAND patch utilities.cc ${CMAKE_SOURCE_DIR}/Resources/Patches/glog-utilities-lsb.diff
+        COMMAND patch utilities.cc ${ORTHANC_ROOT}/Resources/Patches/glog-utilities-lsb.diff
         WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src
         )
     else()
       execute_process(
-        COMMAND patch utilities.cc ${CMAKE_SOURCE_DIR}/Resources/Patches/glog-utilities.diff
+        COMMAND patch utilities.cc ${ORTHANC_ROOT}/Resources/Patches/glog-utilities.diff
         WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src
         )
     endif()
 
     execute_process(
-      COMMAND patch port.h ${CMAKE_SOURCE_DIR}/Resources/Patches/glog-port-h.diff 
+      COMMAND patch port.h ${ORTHANC_ROOT}/Resources/Patches/glog-port-h.diff 
       WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src/windows
       )
     execute_process(
-      COMMAND patch port.cc ${CMAKE_SOURCE_DIR}/Resources/Patches/glog-port-cc.diff 
+      COMMAND patch port.cc ${ORTHANC_ROOT}/Resources/Patches/glog-port-cc.diff 
       WORKING_DIRECTORY ${GOOGLE_LOG_SOURCES_DIR}/src/windows
       )
   endif()
@@ -91,18 +91,18 @@
     if ("${CMAKE_SYSTEM_VERSION}" STREQUAL "LinuxStandardBase")
       # Install the specific configuration for LSB SDK
       configure_file(
-        ${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfigurationLSB.h
+        ${ORTHANC_ROOT}/Resources/CMake/GoogleLogConfigurationLSB.h
         ${GOOGLE_LOG_SOURCES_DIR}/src/config.h
         COPYONLY)
     elseif ("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin")
       # Install the specific configuration for Mac OS
       configure_file(
-        ${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfigurationDarwin.h
+        ${ORTHANC_ROOT}/Resources/CMake/GoogleLogConfigurationDarwin.h
         ${GOOGLE_LOG_SOURCES_DIR}/src/config.h
         COPYONLY)
     else()
       configure_file(
-        ${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleLogConfiguration.h
+        ${ORTHANC_ROOT}/Resources/CMake/GoogleLogConfiguration.h
         ${GOOGLE_LOG_SOURCES_DIR}/src/config.h
         COPYONLY)
     endif()
--- a/Resources/CMake/ZlibConfiguration.cmake	Wed Jun 25 11:56:48 2014 +0200
+++ b/Resources/CMake/ZlibConfiguration.cmake	Thu Jul 10 11:42:32 2014 +0200
@@ -1,7 +1,7 @@
 # This is the minizip distribution to create ZIP files
 list(APPEND THIRD_PARTY_SOURCES 
-  ${ORTHANC_ROOT}/Resources/minizip/ioapi.c
-  ${ORTHANC_ROOT}/Resources/minizip/zip.c
+  ${ORTHANC_ROOT}/Resources/ThirdParty/minizip/ioapi.c
+  ${ORTHANC_ROOT}/Resources/ThirdParty/minizip/zip.c
   )
 
 if (STATIC_BUILD OR NOT USE_SYSTEM_ZLIB)
--- a/Resources/Configuration.json	Wed Jun 25 11:56:48 2014 +0200
+++ b/Resources/Configuration.json	Thu Jul 10 11:42:32 2014 +0200
@@ -28,11 +28,16 @@
   // of patients)
   "MaximumPatientCount" : 0,
   
-  // List of paths to the custom Lua scripts to load into this
-  // instance of Orthanc
+  // List of paths to the custom Lua scripts that are to be loaded
+  // into this instance of Orthanc
   "LuaScripts" : [
   ],
 
+  // List of paths to the plugins that are to be loaded into this
+  // instance of Orthanc (e.g. "/libPluginTest.so" for Linux, or
+  // "./PluginTest.dll" for Windows).
+  "Plugins" : [
+  ],
 
 
   /**
@@ -100,8 +105,9 @@
     /**
      * A fourth parameter is available to enable patches for a
      * specific PACS manufacturer. The allowed values are currently
-     * "Generic" (default value), "ClearCanvas", "MedInria" and
-     * "Dcm4Chee". This parameter is case-sensitive.
+     * "Generic" (default value), "StoreScp" (storescp tool from
+     * DCMTK), "ClearCanvas", "MedInria" and "Dcm4Chee". This
+     * parameter is case-sensitive.
      **/
     // "clearcanvas" : [ "CLEARCANVAS", "192.168.1.1", 104, "ClearCanvas" ]
   },
@@ -169,5 +175,10 @@
 
   // The maximum number of results for a single C-FIND request at the
   // Instance level. Setting this option to "0" means no limit.
-  "LimitFindInstances" : 0
+  "LimitFindInstances" : 0,
+
+  // The maximum number of active jobs in the Orthanc scheduler. When
+  // this limit is reached, the addition of new jobs is blocked until
+  // some job finishes.
+  "LimitJobs" : 10
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/OrthancPlugin.doxygen	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,1792 @@
+# Doxyfile 1.8.1.2
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a hash (#) is considered a comment and will be ignored.
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or sequence of words) that should
+# identify the project. Note that if you do not use Doxywizard you need
+# to put quotes around the project name if it contains spaces.
+
+PROJECT_NAME           = "Orthanc Plugin SDK"
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER         =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer
+# a quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = "Documentation of the plugin interface of Orthanc"
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is
+# included in the documentation. The maximum height of the logo should not
+# exceed 55 pixels and the maximum width should not exceed 200 pixels.
+# Doxygen will copy the logo to the output directory.
+
+PROJECT_LOGO           = @CMAKE_SOURCE_DIR@/Resources/OrthancLogoDocumentation.png
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = OrthancPluginDocumentation
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = NO
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful if your file system
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                =
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding
+# "class=itcl::class" will allow you to use the command class in the
+# itcl::class meaning.
+
+TCL_SUBST              =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this
+# tag. The format is ext=language, where ext is a file extension, and language
+# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C,
+# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
+# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions
+# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all
+# comments according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you
+# can mix doxygen, HTML, and XML commands with Markdown formatting.
+# Disable only in case of backward compatibilities issues.
+
+MARKDOWN_SUPPORT       = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also makes the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = YES
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and
+# unions are shown inside the group in which they are included (e.g. using
+# @ingroup) instead of on a separate page (for HTML and Man pages) or
+# section (for LaTeX and RTF).
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and
+# unions with only public data fields will be shown inline in the documentation
+# of the scope in which they are defined (i.e. file, namespace, or group
+# documentation), provided this scope is documented. If set to NO (the default),
+# structs, classes, and unions are shown on a separate page (for HTML and Man
+# pages) or section (for LaTeX and RTF).
+
+INLINE_SIMPLE_STRUCTS  = NO
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penalty.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will roughly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols.
+
+SYMBOL_CACHE_SIZE      = 0
+
+# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be
+# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given
+# their name and scope. Since this can be an expensive process and often the
+# same symbol appear multiple times in the code, doxygen keeps a cache of
+# pre-resolved symbols. If the cache is too small doxygen will become slower.
+# If the cache is too large, memory is wasted. The cache size is given by this
+# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols.
+
+LOOKUP_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal scope will be included in the documentation.
+
+EXTRACT_PACKAGE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespaces are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = YES
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
+# will list include files with double quotes in the documentation
+# rather than with sharp brackets.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
+# will sort the (brief and detailed) documentation of class members so that
+# constructors and destructors are listed first. If set to NO (the default)
+# the constructors will appear in the respective orders defined by
+# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
+# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
+# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to
+# do proper type resolution of all parameters of a function it will reject a
+# match between the prototype and the implementation of a member function even
+# if there is only one candidate or it is obvious which candidate to choose
+# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen
+# will still accept a match between prototype and implementation in such cases.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or macro consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and macros in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 0
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.
+# This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option.
+# You can optionally specify a file name after the option, if omitted
+# DoxygenLayout.xml will be used as the name of the layout file.
+
+LAYOUT_FILE            =
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files
+# containing the references data. This must be a list of .bib files. The
+# .bib extension is automatically appended if omitted. Using this command
+# requires the bibtex tool to be installed. See also
+# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style
+# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this
+# feature you need bibtex and perl available in the search path.
+
+CITE_BIB_FILES         =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# The WARN_NO_PARAMDOC option can be enabled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC       = YES
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT                  = @CMAKE_SOURCE_DIR@/Plugins/OrthancCPlugin/OrthancCPlugin.h
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh
+# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py
+# *.f90 *.f *.for *.vhd *.vhdl
+
+FILE_PATTERNS          = *.h
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        = _OrthancPlugin*
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS       =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+# If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.
+# Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.
+# The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty or if
+# non of the patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any)
+# and it is also possible to disable source filtering for a specific pattern
+# using *.ext= (so without naming a filter). This option only has effect when
+# FILTER_SOURCE_FILES is enabled.
+
+FILTER_SOURCE_PATTERNS =
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C, C++ and Fortran comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.
+# Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = YES
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = doc
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header. Note that when using a custom header you are responsible
+#  for the proper inclusion of any scripts and style sheets that doxygen
+# needs, which is dependent on the configuration options used.
+# It is advised to generate a default header using "doxygen -w html
+# header.html footer.html stylesheet.css YourConfigFile" and then modify
+# that header. Note that the header is subject to change so you typically
+# have to redo this when upgrading to a newer version of doxygen or when
+# changing the value of configuration settings such as GENERATE_TREEVIEW!
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER            =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# style sheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that
+# the files will be copied as-is; there are no commands or markers available.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
+# Doxygen will adjust the colors in the style sheet and background images
+# according to this color. Hue is specified as an angle on a colorwheel,
+# see http://en.wikipedia.org/wiki/Hue for more information.
+# For instance the value 0 represents red, 60 is yellow, 120 is green,
+# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
+# The allowed range is 0 to 359.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
+# the colors in the HTML output. For a value of 0 the output will use
+# grayscales only. A value of 255 will produce the most vivid colors.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
+# the luminance component of the colors in the HTML output. Values below
+# 100 gradually make the output lighter, whereas values above 100 make
+# the output darker. The value divided by 100 is the actual gamma applied,
+# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
+# and 100 does not change the gamma.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting
+# this to NO can help when comparing the output of multiple runs.
+
+HTML_TIMESTAMP         = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of
+# entries shown in the various tree structured indices initially; the user
+# can expand and collapse entries dynamically later on. Doxygen will expand
+# the tree to such a level that at most the specified number of entries are
+# visible (unless a fully collapsed tree already exceeds this amount).
+# So setting the number of entries 1 will produce a full collapsed tree by
+# default. 0 is a special value representing an infinite number of entries
+# and will result in a full expanded tree by default.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+
+GENERATE_DOCSET        = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE               =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING     =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
+# that can be used as input for Qt's qhelpgenerator to generate a
+# Qt Compressed Help (.qch) of the generated HTML documentation.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
+# add. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
+# Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
+# Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
+#  will be generated, which together with the HTML files, form an Eclipse help
+# plugin. To install this plugin and make it available under the help contents
+# menu in Eclipse, the contents of the directory containing the HTML and XML
+# files needs to be copied into the plugins directory of eclipse. The name of
+# the directory within the plugins directory should be the same as
+# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
+# the help appears.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have
+# this name.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs)
+# at top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it. Since the tabs have the same information as the
+# navigation tree you can set this option to NO if you already set
+# GENERATE_TREEVIEW to YES.
+
+DISABLE_INDEX          = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+# Since the tree basically has the same information as the tab index you
+# could consider to set DISABLE_INDEX to NO when enabling this option.
+
+GENERATE_TREEVIEW      = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values
+# (range [0,1..20]) that doxygen will group on one line in the generated HTML
+# documentation. Note that a value of 0 will completely suppress the enum
+# values from appearing in the overview section.
+
+ENUM_VALUES_PER_LINE   = 1
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
+# links to external symbols imported via tag files in a separate window.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are
+# not supported properly for IE 6.0, but are supported on all modern browsers.
+# Note that when changing this option you need to delete any form_*.png files
+# in the HTML output before the changes have effect.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax
+# (see http://www.mathjax.org) which uses client side Javascript for the
+# rendering instead of using prerendered bitmaps. Use this if you do not
+# have LaTeX installed or if you want to formulas look prettier in the HTML
+# output. When enabled you may also need to install MathJax separately and
+# configure the path to it using the MATHJAX_RELPATH option.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you need to specify the location relative to the
+# HTML output directory using the MATHJAX_RELPATH option. The destination
+# directory should contain the MathJax.js script. For instance, if the mathjax
+# directory is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to
+# the MathJax Content Delivery Network so you can quickly see the result without
+# installing MathJax.
+# However, it is strongly recommended to install a local
+# copy of MathJax from http://www.mathjax.org before deployment.
+
+MATHJAX_RELPATH        = http://www.mathjax.org/mathjax
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension
+# names that should be enabled during MathJax rendering.
+
+MATHJAX_EXTENSIONS     =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box
+# for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using
+# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
+# (GENERATE_DOCSET) there is already a search function so this one should
+# typically be disabled. For large projects the javascript based search engine
+# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
+
+SEARCHENGINE           = NO
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a PHP enabled web server instead of at the web client
+# using Javascript. Doxygen will generate the search PHP script and index
+# file to put on the web server. The advantage of the server
+# based approach is that it scales better to large projects and allows
+# full text search. The disadvantages are that it is more difficult to setup
+# and does not have live searching capabilities.
+
+SERVER_BASED_SEARCH    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+# Note that when enabling USE_PDFLATEX this option is only used for
+# generating bitmaps for formulas in the HTML output, but not in the
+# Makefile that is written to the output directory.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for
+# the generated latex document. The footer should contain everything after
+# the last chapter. If it is left blank doxygen will generate a
+# standard footer. Notice: only use this tag if you know what you are doing!
+
+LATEX_FOOTER           =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include
+# source code with syntax highlighting in the LaTeX output.
+# Note that which sources are shown also depends on other settings
+# such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE      = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See
+# http://en.wikipedia.org/wiki/BibTeX for more info.
+
+LATEX_BIB_STYLE        = plain
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load style sheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA             =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD                =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.
+# This is useful
+# if you want to understand what is going on.
+# On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = YES
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = YES
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# pointed to by INCLUDE_PATH will be searched when a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED             = ORTHANC_PLUGIN_INLINE=
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition that
+# overrules the definition found in the source code.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all references to function-like macros
+# that are alone on a line, have an all uppercase name, and do not end with a
+# semicolon, because these will confuse the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. For each
+# tag file the location of the external documentation should be added. The
+# format of a tag file without this location is as follows:
+#
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths
+# or URLs. Note that each tag file must have a unique name (where the name does
+# NOT include the path). If a tag file is not located in the directory in which
+# doxygen is run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option also works with HAVE_DOT disabled, but it is recommended to
+# install and use dot, since it yields more powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
+# allowed to run in parallel. When set to 0 (the default) doxygen will
+# base this on the number of processors available in the system. You can set it
+# explicitly to a value larger than 0 to get control over the balance
+# between CPU load and processing speed.
+
+DOT_NUM_THREADS        = 0
+
+# By default doxygen will use the Helvetica font for all dot files that
+# doxygen generates. When you want a differently looking font you can specify
+# the font name using DOT_FONTNAME. You need to make sure dot is able to find
+# the font, which can be done by putting it in a standard location or by setting
+# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
+# directory containing the font.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the Helvetica font.
+# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to
+# set the path where dot can find it.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK               = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside
+# the class node. If there are many fields or methods and many nodes the
+# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS
+# threshold limits the number of items for each type to make the size more
+# managable. Set this to 0 for no limit. Note that the threshold may be
+# exceeded by 50% before the limit is enforced.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will generate a graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are svg, png, jpg, or gif.
+# If left blank png will be used. If you choose svg you need to set
+# HTML_FILE_EXTENSION to xhtml in order to make the SVG files
+# visible in IE 9+ (other browsers do not have this requirement).
+
+DOT_IMAGE_FORMAT       = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+# Note that this requires a modern browser other than Internet Explorer.
+# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you
+# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files
+# visible. Older versions of IE do not have SVG support.
+
+INTERACTIVE_SVG        = NO
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the
+# \mscfile command).
+
+MSCFILE_DIRS           =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = YES
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP            = YES
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/Lua/Autorouting.lua	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,3 @@
+function OnStoredInstance(instanceId, tags, metadata)
+   Delete(SendToModality(instanceId, 'sample'))
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/Lua/AutoroutingConditional.lua	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,13 @@
+function OnStoredInstance(instanceId, tags, metadata)
+   -- Extract the value of the "PatientName" DICOM tag
+   local patientName = string.lower(tags['PatientName'])
+
+   if string.find(patientName, 'david') ~= nil then
+      -- Only send patients whose name contains "David"
+      Delete(SendToModality(instanceId, 'sample'))
+
+   else
+      -- Delete the patients that are not called "David"
+      Delete(instanceId)
+   end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/Samples/Lua/AutoroutingModification.lua	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,20 @@
+function OnStoredInstance(instanceId, tags, metadata)
+   -- Ignore the instances that result from a modification to avoid
+   -- infinite loops
+   if (metadata['ModifiedFrom'] == nil and
+       metadata['AnonymizedFrom'] == nil) then
+
+      -- The tags to be replaced
+      local replace = {}
+      replace['StationName'] = 'My Medical Device'
+
+      -- The tags to be removed
+      local remove = { 'MilitaryRank' }
+
+      -- Modify the instance, send it, then delete the modified instance
+      Delete(SendToModality(ModifyInstance(instanceId, replace, remove, true), 'sample'))
+
+      -- Delete the original instance
+      Delete(instanceId)
+   end
+end
--- a/Resources/Samples/OrthancClient/Basic/main.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/Resources/Samples/OrthancClient/Basic/main.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -54,6 +54,15 @@
           OrthancClient::Series series(study.GetSeries(k));
           std::cout << "    Series: " << series.GetId() << std::endl;
 
+          if (series.Is3DImage())
+          {
+            std::cout << "    This is a 3D image whose voxel size is " 
+                      << series.GetVoxelSizeX() << " x " 
+                      << series.GetVoxelSizeY() << " x " 
+                      << series.GetVoxelSizeZ() << ", and slice thickness is " 
+                      << series.GetSliceThickness() << std::endl;
+          }
+
           for (unsigned int l = 0; l < series.GetInstanceCount(); l++)
           {
             std::cout << "      Instance: " << series.GetInstance(l).GetId() << std::endl;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/ThirdParty/VisualStudio/stdint.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,247 @@
+// ISO C9x  compliant stdint.h for Microsoft Visual Studio
+// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 
+// 
+//  Copyright (c) 2006-2008 Alexander Chemeris
+// 
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+// 
+//   1. Redistributions of source code must retain the above copyright notice,
+//      this list of conditions and the following disclaimer.
+// 
+//   2. Redistributions in binary form must reproduce the above copyright
+//      notice, this list of conditions and the following disclaimer in the
+//      documentation and/or other materials provided with the distribution.
+// 
+//   3. The name of the author may be used to endorse or promote products
+//      derived from this software without specific prior written permission.
+// 
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+// 
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef _MSC_VER // [
+#error "Use this header only with Microsoft Visual C++ compilers!"
+#endif // _MSC_VER ]
+
+#ifndef _MSC_STDINT_H_ // [
+#define _MSC_STDINT_H_
+
+#if _MSC_VER > 1000
+#pragma once
+#endif
+
+#include <limits.h>
+
+// For Visual Studio 6 in C++ mode and for many Visual Studio versions when
+// compiling for ARM we should wrap <wchar.h> include with 'extern "C++" {}'
+// or compiler give many errors like this:
+//   error C2733: second C linkage of overloaded function 'wmemchr' not allowed
+#ifdef __cplusplus
+extern "C" {
+#endif
+#  include <wchar.h>
+#ifdef __cplusplus
+}
+#endif
+
+// Define _W64 macros to mark types changing their size, like intptr_t.
+#ifndef _W64
+#  if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300
+#     define _W64 __w64
+#  else
+#     define _W64
+#  endif
+#endif
+
+
+// 7.18.1 Integer types
+
+// 7.18.1.1 Exact-width integer types
+
+// Visual Studio 6 and Embedded Visual C++ 4 doesn't
+// realize that, e.g. char has the same size as __int8
+// so we give up on __intX for them.
+#if (_MSC_VER < 1300)
+   typedef signed char       int8_t;
+   typedef signed short      int16_t;
+   typedef signed int        int32_t;
+   typedef unsigned char     uint8_t;
+   typedef unsigned short    uint16_t;
+   typedef unsigned int      uint32_t;
+#else
+   typedef signed __int8     int8_t;
+   typedef signed __int16    int16_t;
+   typedef signed __int32    int32_t;
+   typedef unsigned __int8   uint8_t;
+   typedef unsigned __int16  uint16_t;
+   typedef unsigned __int32  uint32_t;
+#endif
+typedef signed __int64       int64_t;
+typedef unsigned __int64     uint64_t;
+
+
+// 7.18.1.2 Minimum-width integer types
+typedef int8_t    int_least8_t;
+typedef int16_t   int_least16_t;
+typedef int32_t   int_least32_t;
+typedef int64_t   int_least64_t;
+typedef uint8_t   uint_least8_t;
+typedef uint16_t  uint_least16_t;
+typedef uint32_t  uint_least32_t;
+typedef uint64_t  uint_least64_t;
+
+// 7.18.1.3 Fastest minimum-width integer types
+typedef int8_t    int_fast8_t;
+typedef int16_t   int_fast16_t;
+typedef int32_t   int_fast32_t;
+typedef int64_t   int_fast64_t;
+typedef uint8_t   uint_fast8_t;
+typedef uint16_t  uint_fast16_t;
+typedef uint32_t  uint_fast32_t;
+typedef uint64_t  uint_fast64_t;
+
+// 7.18.1.4 Integer types capable of holding object pointers
+#ifdef _WIN64 // [
+   typedef signed __int64    intptr_t;
+   typedef unsigned __int64  uintptr_t;
+#else // _WIN64 ][
+   typedef _W64 signed int   intptr_t;
+   typedef _W64 unsigned int uintptr_t;
+#endif // _WIN64 ]
+
+// 7.18.1.5 Greatest-width integer types
+typedef int64_t   intmax_t;
+typedef uint64_t  uintmax_t;
+
+
+// 7.18.2 Limits of specified-width integer types
+
+#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [   See footnote 220 at page 257 and footnote 221 at page 259
+
+// 7.18.2.1 Limits of exact-width integer types
+#define INT8_MIN     ((int8_t)_I8_MIN)
+#define INT8_MAX     _I8_MAX
+#define INT16_MIN    ((int16_t)_I16_MIN)
+#define INT16_MAX    _I16_MAX
+#define INT32_MIN    ((int32_t)_I32_MIN)
+#define INT32_MAX    _I32_MAX
+#define INT64_MIN    ((int64_t)_I64_MIN)
+#define INT64_MAX    _I64_MAX
+#define UINT8_MAX    _UI8_MAX
+#define UINT16_MAX   _UI16_MAX
+#define UINT32_MAX   _UI32_MAX
+#define UINT64_MAX   _UI64_MAX
+
+// 7.18.2.2 Limits of minimum-width integer types
+#define INT_LEAST8_MIN    INT8_MIN
+#define INT_LEAST8_MAX    INT8_MAX
+#define INT_LEAST16_MIN   INT16_MIN
+#define INT_LEAST16_MAX   INT16_MAX
+#define INT_LEAST32_MIN   INT32_MIN
+#define INT_LEAST32_MAX   INT32_MAX
+#define INT_LEAST64_MIN   INT64_MIN
+#define INT_LEAST64_MAX   INT64_MAX
+#define UINT_LEAST8_MAX   UINT8_MAX
+#define UINT_LEAST16_MAX  UINT16_MAX
+#define UINT_LEAST32_MAX  UINT32_MAX
+#define UINT_LEAST64_MAX  UINT64_MAX
+
+// 7.18.2.3 Limits of fastest minimum-width integer types
+#define INT_FAST8_MIN    INT8_MIN
+#define INT_FAST8_MAX    INT8_MAX
+#define INT_FAST16_MIN   INT16_MIN
+#define INT_FAST16_MAX   INT16_MAX
+#define INT_FAST32_MIN   INT32_MIN
+#define INT_FAST32_MAX   INT32_MAX
+#define INT_FAST64_MIN   INT64_MIN
+#define INT_FAST64_MAX   INT64_MAX
+#define UINT_FAST8_MAX   UINT8_MAX
+#define UINT_FAST16_MAX  UINT16_MAX
+#define UINT_FAST32_MAX  UINT32_MAX
+#define UINT_FAST64_MAX  UINT64_MAX
+
+// 7.18.2.4 Limits of integer types capable of holding object pointers
+#ifdef _WIN64 // [
+#  define INTPTR_MIN   INT64_MIN
+#  define INTPTR_MAX   INT64_MAX
+#  define UINTPTR_MAX  UINT64_MAX
+#else // _WIN64 ][
+#  define INTPTR_MIN   INT32_MIN
+#  define INTPTR_MAX   INT32_MAX
+#  define UINTPTR_MAX  UINT32_MAX
+#endif // _WIN64 ]
+
+// 7.18.2.5 Limits of greatest-width integer types
+#define INTMAX_MIN   INT64_MIN
+#define INTMAX_MAX   INT64_MAX
+#define UINTMAX_MAX  UINT64_MAX
+
+// 7.18.3 Limits of other integer types
+
+#ifdef _WIN64 // [
+#  define PTRDIFF_MIN  _I64_MIN
+#  define PTRDIFF_MAX  _I64_MAX
+#else  // _WIN64 ][
+#  define PTRDIFF_MIN  _I32_MIN
+#  define PTRDIFF_MAX  _I32_MAX
+#endif  // _WIN64 ]
+
+#define SIG_ATOMIC_MIN  INT_MIN
+#define SIG_ATOMIC_MAX  INT_MAX
+
+#ifndef SIZE_MAX // [
+#  ifdef _WIN64 // [
+#     define SIZE_MAX  _UI64_MAX
+#  else // _WIN64 ][
+#     define SIZE_MAX  _UI32_MAX
+#  endif // _WIN64 ]
+#endif // SIZE_MAX ]
+
+// WCHAR_MIN and WCHAR_MAX are also defined in <wchar.h>
+#ifndef WCHAR_MIN // [
+#  define WCHAR_MIN  0
+#endif  // WCHAR_MIN ]
+#ifndef WCHAR_MAX // [
+#  define WCHAR_MAX  _UI16_MAX
+#endif  // WCHAR_MAX ]
+
+#define WINT_MIN  0
+#define WINT_MAX  _UI16_MAX
+
+#endif // __STDC_LIMIT_MACROS ]
+
+
+// 7.18.4 Limits of other integer types
+
+#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [   See footnote 224 at page 260
+
+// 7.18.4.1 Macros for minimum-width integer constants
+
+#define INT8_C(val)  val##i8
+#define INT16_C(val) val##i16
+#define INT32_C(val) val##i32
+#define INT64_C(val) val##i64
+
+#define UINT8_C(val)  val##ui8
+#define UINT16_C(val) val##ui16
+#define UINT32_C(val) val##ui32
+#define UINT64_C(val) val##ui64
+
+// 7.18.4.2 Macros for greatest-width integer constants
+#define INTMAX_C   INT64_C
+#define UINTMAX_C  UINT64_C
+
+#endif // __STDC_CONSTANT_MACROS ]
+
+
+#endif // _MSC_STDINT_H_ ]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/ThirdParty/base64/base64.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -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/ThirdParty/base64/base64.h	Thu Jul 10 11:42:32 2014 +0200
@@ -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/ThirdParty/md5/md5.c	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,381 @@
+/*
+  Copyright (C) 1999, 2000, 2002 Aladdin Enterprises.  All rights reserved.
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  L. Peter Deutsch
+  ghost@aladdin.com
+
+ */
+/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */
+/*
+  Independent implementation of MD5 (RFC 1321).
+
+  This code implements the MD5 Algorithm defined in RFC 1321, whose
+  text is available at
+	http://www.ietf.org/rfc/rfc1321.txt
+  The code is derived from the text of the RFC, including the test suite
+  (section A.5) but excluding the rest of Appendix A.  It does not include
+  any code or documentation that is identified in the RFC as being
+  copyrighted.
+
+  The original and principal author of md5.c is L. Peter Deutsch
+  <ghost@aladdin.com>.  Other authors are noted in the change history
+  that follows (in reverse chronological order):
+
+  2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order
+	either statically or dynamically; added missing #include <string.h>
+	in library.
+  2002-03-11 lpd Corrected argument list for main(), and added int return
+	type, in test program and T value program.
+  2002-02-21 lpd Added missing #include <stdio.h> in test program.
+  2000-07-03 lpd Patched to eliminate warnings about "constant is
+	unsigned in ANSI C, signed in traditional"; made test program
+	self-checking.
+  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
+  1999-05-03 lpd Original version.
+ */
+
+#include "md5.h"
+#include <string.h>
+
+#undef BYTE_ORDER	/* 1 = big-endian, -1 = little-endian, 0 = unknown */
+#ifdef ARCH_IS_BIG_ENDIAN
+#  define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
+#else
+#  define BYTE_ORDER 0
+#endif
+
+#define T_MASK ((md5_word_t)~0)
+#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
+#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
+#define T3    0x242070db
+#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
+#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
+#define T6    0x4787c62a
+#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
+#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
+#define T9    0x698098d8
+#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
+#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
+#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
+#define T13    0x6b901122
+#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
+#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
+#define T16    0x49b40821
+#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
+#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
+#define T19    0x265e5a51
+#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
+#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
+#define T22    0x02441453
+#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
+#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
+#define T25    0x21e1cde6
+#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
+#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
+#define T28    0x455a14ed
+#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
+#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
+#define T31    0x676f02d9
+#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
+#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
+#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
+#define T35    0x6d9d6122
+#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
+#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
+#define T38    0x4bdecfa9
+#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
+#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
+#define T41    0x289b7ec6
+#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
+#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
+#define T44    0x04881d05
+#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
+#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
+#define T47    0x1fa27cf8
+#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
+#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
+#define T50    0x432aff97
+#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
+#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
+#define T53    0x655b59c3
+#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
+#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
+#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
+#define T57    0x6fa87e4f
+#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
+#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
+#define T60    0x4e0811a1
+#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
+#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
+#define T63    0x2ad7d2bb
+#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)
+
+
+static void
+md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
+{
+    md5_word_t
+	a = pms->abcd[0], b = pms->abcd[1],
+	c = pms->abcd[2], d = pms->abcd[3];
+    md5_word_t t;
+#if BYTE_ORDER > 0
+    /* Define storage only for big-endian CPUs. */
+    md5_word_t X[16];
+#else
+    /* Define storage for little-endian or both types of CPUs. */
+    md5_word_t xbuf[16];
+    const md5_word_t *X;
+#endif
+
+    {
+#if BYTE_ORDER == 0
+	/*
+	 * Determine dynamically whether this is a big-endian or
+	 * little-endian machine, since we can use a more efficient
+	 * algorithm on the latter.
+	 */
+	static const int w = 1;
+
+	if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
+#endif
+#if BYTE_ORDER <= 0		/* little-endian */
+	{
+	    /*
+	     * On little-endian machines, we can process properly aligned
+	     * data without copying it.
+	     */
+	    if (!((data - (const md5_byte_t *)0) & 3)) {
+		/* data are properly aligned */
+		X = (const md5_word_t *)data;
+	    } else {
+		/* not aligned */
+		memcpy(xbuf, data, 64);
+		X = xbuf;
+	    }
+	}
+#endif
+#if BYTE_ORDER == 0
+	else			/* dynamic big-endian */
+#endif
+#if BYTE_ORDER >= 0		/* big-endian */
+	{
+	    /*
+	     * On big-endian machines, we must arrange the bytes in the
+	     * right order.
+	     */
+	    const md5_byte_t *xp = data;
+	    int i;
+
+#  if BYTE_ORDER == 0
+	    X = xbuf;		/* (dynamic only) */
+#  else
+#    define xbuf X		/* (static only) */
+#  endif
+	    for (i = 0; i < 16; ++i, xp += 4)
+		xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
+	}
+#endif
+    }
+
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
+
+    /* Round 1. */
+    /* Let [abcd k s i] denote the operation
+       a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
+#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + F(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+    /* Do the following 16 operations. */
+    SET(a, b, c, d,  0,  7,  T1);
+    SET(d, a, b, c,  1, 12,  T2);
+    SET(c, d, a, b,  2, 17,  T3);
+    SET(b, c, d, a,  3, 22,  T4);
+    SET(a, b, c, d,  4,  7,  T5);
+    SET(d, a, b, c,  5, 12,  T6);
+    SET(c, d, a, b,  6, 17,  T7);
+    SET(b, c, d, a,  7, 22,  T8);
+    SET(a, b, c, d,  8,  7,  T9);
+    SET(d, a, b, c,  9, 12, T10);
+    SET(c, d, a, b, 10, 17, T11);
+    SET(b, c, d, a, 11, 22, T12);
+    SET(a, b, c, d, 12,  7, T13);
+    SET(d, a, b, c, 13, 12, T14);
+    SET(c, d, a, b, 14, 17, T15);
+    SET(b, c, d, a, 15, 22, T16);
+#undef SET
+
+     /* Round 2. */
+     /* Let [abcd k s i] denote the operation
+          a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
+#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + G(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  1,  5, T17);
+    SET(d, a, b, c,  6,  9, T18);
+    SET(c, d, a, b, 11, 14, T19);
+    SET(b, c, d, a,  0, 20, T20);
+    SET(a, b, c, d,  5,  5, T21);
+    SET(d, a, b, c, 10,  9, T22);
+    SET(c, d, a, b, 15, 14, T23);
+    SET(b, c, d, a,  4, 20, T24);
+    SET(a, b, c, d,  9,  5, T25);
+    SET(d, a, b, c, 14,  9, T26);
+    SET(c, d, a, b,  3, 14, T27);
+    SET(b, c, d, a,  8, 20, T28);
+    SET(a, b, c, d, 13,  5, T29);
+    SET(d, a, b, c,  2,  9, T30);
+    SET(c, d, a, b,  7, 14, T31);
+    SET(b, c, d, a, 12, 20, T32);
+#undef SET
+
+     /* Round 3. */
+     /* Let [abcd k s t] denote the operation
+          a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + H(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  5,  4, T33);
+    SET(d, a, b, c,  8, 11, T34);
+    SET(c, d, a, b, 11, 16, T35);
+    SET(b, c, d, a, 14, 23, T36);
+    SET(a, b, c, d,  1,  4, T37);
+    SET(d, a, b, c,  4, 11, T38);
+    SET(c, d, a, b,  7, 16, T39);
+    SET(b, c, d, a, 10, 23, T40);
+    SET(a, b, c, d, 13,  4, T41);
+    SET(d, a, b, c,  0, 11, T42);
+    SET(c, d, a, b,  3, 16, T43);
+    SET(b, c, d, a,  6, 23, T44);
+    SET(a, b, c, d,  9,  4, T45);
+    SET(d, a, b, c, 12, 11, T46);
+    SET(c, d, a, b, 15, 16, T47);
+    SET(b, c, d, a,  2, 23, T48);
+#undef SET
+
+     /* Round 4. */
+     /* Let [abcd k s t] denote the operation
+          a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
+#define I(x, y, z) ((y) ^ ((x) | ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + I(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  0,  6, T49);
+    SET(d, a, b, c,  7, 10, T50);
+    SET(c, d, a, b, 14, 15, T51);
+    SET(b, c, d, a,  5, 21, T52);
+    SET(a, b, c, d, 12,  6, T53);
+    SET(d, a, b, c,  3, 10, T54);
+    SET(c, d, a, b, 10, 15, T55);
+    SET(b, c, d, a,  1, 21, T56);
+    SET(a, b, c, d,  8,  6, T57);
+    SET(d, a, b, c, 15, 10, T58);
+    SET(c, d, a, b,  6, 15, T59);
+    SET(b, c, d, a, 13, 21, T60);
+    SET(a, b, c, d,  4,  6, T61);
+    SET(d, a, b, c, 11, 10, T62);
+    SET(c, d, a, b,  2, 15, T63);
+    SET(b, c, d, a,  9, 21, T64);
+#undef SET
+
+     /* Then perform the following additions. (That is increment each
+        of the four registers by the value it had before this block
+        was started.) */
+    pms->abcd[0] += a;
+    pms->abcd[1] += b;
+    pms->abcd[2] += c;
+    pms->abcd[3] += d;
+}
+
+void
+md5_init(md5_state_t *pms)
+{
+    pms->count[0] = pms->count[1] = 0;
+    pms->abcd[0] = 0x67452301;
+    pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
+    pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
+    pms->abcd[3] = 0x10325476;
+}
+
+void
+md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
+{
+    const md5_byte_t *p = data;
+    int left = nbytes;
+    int offset = (pms->count[0] >> 3) & 63;
+    md5_word_t nbits = (md5_word_t)(nbytes << 3);
+
+    if (nbytes <= 0)
+	return;
+
+    /* Update the message length. */
+    pms->count[1] += nbytes >> 29;
+    pms->count[0] += nbits;
+    if (pms->count[0] < nbits)
+	pms->count[1]++;
+
+    /* Process an initial partial block. */
+    if (offset) {
+	int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
+
+	memcpy(pms->buf + offset, p, copy);
+	if (offset + copy < 64)
+	    return;
+	p += copy;
+	left -= copy;
+	md5_process(pms, pms->buf);
+    }
+
+    /* Process full blocks. */
+    for (; left >= 64; p += 64, left -= 64)
+	md5_process(pms, p);
+
+    /* Process a final partial block. */
+    if (left)
+	memcpy(pms->buf, p, left);
+}
+
+void
+md5_finish(md5_state_t *pms, md5_byte_t digest[16])
+{
+    static const md5_byte_t pad[64] = {
+	0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+    };
+    md5_byte_t data[8];
+    int i;
+
+    /* Save the length before padding. */
+    for (i = 0; i < 8; ++i)
+	data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
+    /* Pad to 56 bytes mod 64. */
+    md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
+    /* Append the length. */
+    md5_append(pms, data, 8);
+    for (i = 0; i < 16; ++i)
+	digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/ThirdParty/md5/md5.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,91 @@
+/*
+  Copyright (C) 1999, 2002 Aladdin Enterprises.  All rights reserved.
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  L. Peter Deutsch
+  ghost@aladdin.com
+
+ */
+/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */
+/*
+  Independent implementation of MD5 (RFC 1321).
+
+  This code implements the MD5 Algorithm defined in RFC 1321, whose
+  text is available at
+	http://www.ietf.org/rfc/rfc1321.txt
+  The code is derived from the text of the RFC, including the test suite
+  (section A.5) but excluding the rest of Appendix A.  It does not include
+  any code or documentation that is identified in the RFC as being
+  copyrighted.
+
+  The original and principal author of md5.h is L. Peter Deutsch
+  <ghost@aladdin.com>.  Other authors are noted in the change history
+  that follows (in reverse chronological order):
+
+  2002-04-13 lpd Removed support for non-ANSI compilers; removed
+	references to Ghostscript; clarified derivation from RFC 1321;
+	now handles byte order either statically or dynamically.
+  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
+	added conditionalization for C++ compilation from Martin
+	Purschke <purschke@bnl.gov>.
+  1999-05-03 lpd Original version.
+ */
+
+#ifndef md5_INCLUDED
+#  define md5_INCLUDED
+
+/*
+ * This package supports both compile-time and run-time determination of CPU
+ * byte order.  If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
+ * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
+ * defined as non-zero, the code will be compiled to run only on big-endian
+ * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
+ * run on either big- or little-endian CPUs, but will run slightly less
+ * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
+ */
+
+typedef unsigned char md5_byte_t; /* 8-bit byte */
+typedef unsigned int md5_word_t; /* 32-bit word */
+
+/* Define the state of the MD5 Algorithm. */
+typedef struct md5_state_s {
+    md5_word_t count[2];	/* message length in bits, lsw first */
+    md5_word_t abcd[4];		/* digest buffer */
+    md5_byte_t buf[64];		/* accumulate block */
+} md5_state_t;
+
+#ifdef __cplusplus
+extern "C" 
+{
+#endif
+
+/* Initialize the algorithm. */
+void md5_init(md5_state_t *pms);
+
+/* Append a string to the message. */
+void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
+
+/* Finish the message and return the digest. */
+void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
+
+#ifdef __cplusplus
+}  /* end extern "C" */
+#endif
+
+#endif /* md5_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/ThirdParty/minizip/NOTES	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,1 @@
+These files come from the "contrib/minizip" directory in zlib 1.2.7.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/ThirdParty/minizip/crypt.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,131 @@
+/* crypt.h -- base code for crypt/uncrypt ZIPfile
+
+
+   Version 1.01e, February 12th, 2005
+
+   Copyright (C) 1998-2005 Gilles Vollant
+
+   This code is a modified version of crypting code in Infozip distribution
+
+   The encryption/decryption parts of this source code (as opposed to the
+   non-echoing password parts) were originally written in Europe.  The
+   whole source package can be freely distributed, including from the USA.
+   (Prior to January 2000, re-export from the US was a violation of US law.)
+
+   This encryption code is a direct transcription of the algorithm from
+   Roger Schlafly, described by Phil Katz in the file appnote.txt.  This
+   file (appnote.txt) is distributed with the PKZIP program (even in the
+   version without encryption capabilities).
+
+   If you don't need crypting in your application, just define symbols
+   NOCRYPT and NOUNCRYPT.
+
+   This code support the "Traditional PKWARE Encryption".
+
+   The new AES encryption added on Zip format by Winzip (see the page
+   http://www.winzip.com/aes_info.htm ) and PKWare PKZip 5.x Strong
+   Encryption is not supported.
+*/
+
+#define CRC32(c, b) ((*(pcrc_32_tab+(((int)(c) ^ (b)) & 0xff))) ^ ((c) >> 8))
+
+/***********************************************************************
+ * Return the next byte in the pseudo-random sequence
+ */
+static int decrypt_byte(unsigned long* pkeys, const unsigned long* pcrc_32_tab)
+{
+    unsigned temp;  /* POTENTIAL BUG:  temp*(temp^1) may overflow in an
+                     * unpredictable manner on 16-bit systems; not a problem
+                     * with any known compiler so far, though */
+
+    temp = ((unsigned)(*(pkeys+2)) & 0xffff) | 2;
+    return (int)(((temp * (temp ^ 1)) >> 8) & 0xff);
+}
+
+/***********************************************************************
+ * Update the encryption keys with the next byte of plain text
+ */
+static int update_keys(unsigned long* pkeys,const unsigned long* pcrc_32_tab,int c)
+{
+    (*(pkeys+0)) = CRC32((*(pkeys+0)), c);
+    (*(pkeys+1)) += (*(pkeys+0)) & 0xff;
+    (*(pkeys+1)) = (*(pkeys+1)) * 134775813L + 1;
+    {
+      register int keyshift = (int)((*(pkeys+1)) >> 24);
+      (*(pkeys+2)) = CRC32((*(pkeys+2)), keyshift);
+    }
+    return c;
+}
+
+
+/***********************************************************************
+ * Initialize the encryption keys and the random header according to
+ * the given password.
+ */
+static void init_keys(const char* passwd,unsigned long* pkeys,const unsigned long* pcrc_32_tab)
+{
+    *(pkeys+0) = 305419896L;
+    *(pkeys+1) = 591751049L;
+    *(pkeys+2) = 878082192L;
+    while (*passwd != '\0') {
+        update_keys(pkeys,pcrc_32_tab,(int)*passwd);
+        passwd++;
+    }
+}
+
+#define zdecode(pkeys,pcrc_32_tab,c) \
+    (update_keys(pkeys,pcrc_32_tab,c ^= decrypt_byte(pkeys,pcrc_32_tab)))
+
+#define zencode(pkeys,pcrc_32_tab,c,t) \
+    (t=decrypt_byte(pkeys,pcrc_32_tab), update_keys(pkeys,pcrc_32_tab,c), t^(c))
+
+#ifdef INCLUDECRYPTINGCODE_IFCRYPTALLOWED
+
+#define RAND_HEAD_LEN  12
+   /* "last resort" source for second part of crypt seed pattern */
+#  ifndef ZCR_SEED2
+#    define ZCR_SEED2 3141592654UL     /* use PI as default pattern */
+#  endif
+
+static int crypthead(const char* passwd,      /* password string */
+                     unsigned char* buf,      /* where to write header */
+                     int bufSize,
+                     unsigned long* pkeys,
+                     const unsigned long* pcrc_32_tab,
+                     unsigned long crcForCrypting)
+{
+    int n;                       /* index in random header */
+    int t;                       /* temporary */
+    int c;                       /* random byte */
+    unsigned char header[RAND_HEAD_LEN-2]; /* random header */
+    static unsigned calls = 0;   /* ensure different random header each time */
+
+    if (bufSize<RAND_HEAD_LEN)
+      return 0;
+
+    /* First generate RAND_HEAD_LEN-2 random bytes. We encrypt the
+     * output of rand() to get less predictability, since rand() is
+     * often poorly implemented.
+     */
+    if (++calls == 1)
+    {
+        srand((unsigned)(time(NULL) ^ ZCR_SEED2));
+    }
+    init_keys(passwd, pkeys, pcrc_32_tab);
+    for (n = 0; n < RAND_HEAD_LEN-2; n++)
+    {
+        c = (rand() >> 7) & 0xff;
+        header[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, c, t);
+    }
+    /* Encrypt random header (last two bytes is high word of crc) */
+    init_keys(passwd, pkeys, pcrc_32_tab);
+    for (n = 0; n < RAND_HEAD_LEN-2; n++)
+    {
+        buf[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, header[n], t);
+    }
+    buf[n++] = (unsigned char)zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 16) & 0xff, t);
+    buf[n++] = (unsigned char)zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 24) & 0xff, t);
+    return n;
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/ThirdParty/minizip/ioapi.c	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,247 @@
+/* ioapi.h -- IO base function header for compress/uncompress .zip
+   part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Modifications for Zip64 support
+         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
+
+         For more info read MiniZip_info.txt
+
+*/
+
+#if defined(_WIN32) && (!(defined(_CRT_SECURE_NO_WARNINGS)))
+        #define _CRT_SECURE_NO_WARNINGS
+#endif
+
+#if defined(__APPLE__) || defined(IOAPI_NO_64)
+// In darwin and perhaps other BSD variants off_t is a 64 bit value, hence no need for specific 64 bit functions
+#define FOPEN_FUNC(filename, mode) fopen(filename, mode)
+#define FTELLO_FUNC(stream) ftello(stream)
+#define FSEEKO_FUNC(stream, offset, origin) fseeko(stream, offset, origin)
+#else
+#define FOPEN_FUNC(filename, mode) fopen64(filename, mode)
+#define FTELLO_FUNC(stream) ftello64(stream)
+#define FSEEKO_FUNC(stream, offset, origin) fseeko64(stream, offset, origin)
+#endif
+
+
+#include "ioapi.h"
+
+voidpf call_zopen64 (const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode)
+{
+    if (pfilefunc->zfile_func64.zopen64_file != NULL)
+        return (*(pfilefunc->zfile_func64.zopen64_file)) (pfilefunc->zfile_func64.opaque,filename,mode);
+    else
+    {
+        return (*(pfilefunc->zopen32_file))(pfilefunc->zfile_func64.opaque,(const char*)filename,mode);
+    }
+}
+
+long call_zseek64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin)
+{
+    if (pfilefunc->zfile_func64.zseek64_file != NULL)
+        return (*(pfilefunc->zfile_func64.zseek64_file)) (pfilefunc->zfile_func64.opaque,filestream,offset,origin);
+    else
+    {
+        uLong offsetTruncated = (uLong)offset;
+        if (offsetTruncated != offset)
+            return -1;
+        else
+            return (*(pfilefunc->zseek32_file))(pfilefunc->zfile_func64.opaque,filestream,offsetTruncated,origin);
+    }
+}
+
+ZPOS64_T call_ztell64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream)
+{
+    if (pfilefunc->zfile_func64.zseek64_file != NULL)
+        return (*(pfilefunc->zfile_func64.ztell64_file)) (pfilefunc->zfile_func64.opaque,filestream);
+    else
+    {
+        uLong tell_uLong = (*(pfilefunc->ztell32_file))(pfilefunc->zfile_func64.opaque,filestream);
+        if ((tell_uLong) == MAXU32)
+            return (ZPOS64_T)-1;
+        else
+            return tell_uLong;
+    }
+}
+
+void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32)
+{
+    p_filefunc64_32->zfile_func64.zopen64_file = NULL;
+    p_filefunc64_32->zopen32_file = p_filefunc32->zopen_file;
+    p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file;
+    p_filefunc64_32->zfile_func64.zread_file = p_filefunc32->zread_file;
+    p_filefunc64_32->zfile_func64.zwrite_file = p_filefunc32->zwrite_file;
+    p_filefunc64_32->zfile_func64.ztell64_file = NULL;
+    p_filefunc64_32->zfile_func64.zseek64_file = NULL;
+    p_filefunc64_32->zfile_func64.zclose_file = p_filefunc32->zclose_file;
+    p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file;
+    p_filefunc64_32->zfile_func64.opaque = p_filefunc32->opaque;
+    p_filefunc64_32->zseek32_file = p_filefunc32->zseek_file;
+    p_filefunc64_32->ztell32_file = p_filefunc32->ztell_file;
+}
+
+
+
+static voidpf  ZCALLBACK fopen_file_func OF((voidpf opaque, const char* filename, int mode));
+static uLong   ZCALLBACK fread_file_func OF((voidpf opaque, voidpf stream, void* buf, uLong size));
+static uLong   ZCALLBACK fwrite_file_func OF((voidpf opaque, voidpf stream, const void* buf,uLong size));
+static ZPOS64_T ZCALLBACK ftell64_file_func OF((voidpf opaque, voidpf stream));
+static long    ZCALLBACK fseek64_file_func OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin));
+static int     ZCALLBACK fclose_file_func OF((voidpf opaque, voidpf stream));
+static int     ZCALLBACK ferror_file_func OF((voidpf opaque, voidpf stream));
+
+static voidpf ZCALLBACK fopen_file_func (voidpf opaque, const char* filename, int mode)
+{
+    FILE* file = NULL;
+    const char* mode_fopen = NULL;
+    if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ)
+        mode_fopen = "rb";
+    else
+    if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
+        mode_fopen = "r+b";
+    else
+    if (mode & ZLIB_FILEFUNC_MODE_CREATE)
+        mode_fopen = "wb";
+
+    if ((filename!=NULL) && (mode_fopen != NULL))
+        file = fopen(filename, mode_fopen);
+    return file;
+}
+
+static voidpf ZCALLBACK fopen64_file_func (voidpf opaque, const void* filename, int mode)
+{
+    FILE* file = NULL;
+    const char* mode_fopen = NULL;
+    if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ)
+        mode_fopen = "rb";
+    else
+    if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
+        mode_fopen = "r+b";
+    else
+    if (mode & ZLIB_FILEFUNC_MODE_CREATE)
+        mode_fopen = "wb";
+
+    if ((filename!=NULL) && (mode_fopen != NULL))
+        file = FOPEN_FUNC((const char*)filename, mode_fopen);
+    return file;
+}
+
+
+static uLong ZCALLBACK fread_file_func (voidpf opaque, voidpf stream, void* buf, uLong size)
+{
+    uLong ret;
+    ret = (uLong)fread(buf, 1, (size_t)size, (FILE *)stream);
+    return ret;
+}
+
+static uLong ZCALLBACK fwrite_file_func (voidpf opaque, voidpf stream, const void* buf, uLong size)
+{
+    uLong ret;
+    ret = (uLong)fwrite(buf, 1, (size_t)size, (FILE *)stream);
+    return ret;
+}
+
+static long ZCALLBACK ftell_file_func (voidpf opaque, voidpf stream)
+{
+    long ret;
+    ret = ftell((FILE *)stream);
+    return ret;
+}
+
+
+static ZPOS64_T ZCALLBACK ftell64_file_func (voidpf opaque, voidpf stream)
+{
+    ZPOS64_T ret;
+    ret = FTELLO_FUNC((FILE *)stream);
+    return ret;
+}
+
+static long ZCALLBACK fseek_file_func (voidpf  opaque, voidpf stream, uLong offset, int origin)
+{
+    int fseek_origin=0;
+    long ret;
+    switch (origin)
+    {
+    case ZLIB_FILEFUNC_SEEK_CUR :
+        fseek_origin = SEEK_CUR;
+        break;
+    case ZLIB_FILEFUNC_SEEK_END :
+        fseek_origin = SEEK_END;
+        break;
+    case ZLIB_FILEFUNC_SEEK_SET :
+        fseek_origin = SEEK_SET;
+        break;
+    default: return -1;
+    }
+    ret = 0;
+    if (fseek((FILE *)stream, offset, fseek_origin) != 0)
+        ret = -1;
+    return ret;
+}
+
+static long ZCALLBACK fseek64_file_func (voidpf  opaque, voidpf stream, ZPOS64_T offset, int origin)
+{
+    int fseek_origin=0;
+    long ret;
+    switch (origin)
+    {
+    case ZLIB_FILEFUNC_SEEK_CUR :
+        fseek_origin = SEEK_CUR;
+        break;
+    case ZLIB_FILEFUNC_SEEK_END :
+        fseek_origin = SEEK_END;
+        break;
+    case ZLIB_FILEFUNC_SEEK_SET :
+        fseek_origin = SEEK_SET;
+        break;
+    default: return -1;
+    }
+    ret = 0;
+
+    if(FSEEKO_FUNC((FILE *)stream, offset, fseek_origin) != 0)
+                        ret = -1;
+
+    return ret;
+}
+
+
+static int ZCALLBACK fclose_file_func (voidpf opaque, voidpf stream)
+{
+    int ret;
+    ret = fclose((FILE *)stream);
+    return ret;
+}
+
+static int ZCALLBACK ferror_file_func (voidpf opaque, voidpf stream)
+{
+    int ret;
+    ret = ferror((FILE *)stream);
+    return ret;
+}
+
+void fill_fopen_filefunc (pzlib_filefunc_def)
+  zlib_filefunc_def* pzlib_filefunc_def;
+{
+    pzlib_filefunc_def->zopen_file = fopen_file_func;
+    pzlib_filefunc_def->zread_file = fread_file_func;
+    pzlib_filefunc_def->zwrite_file = fwrite_file_func;
+    pzlib_filefunc_def->ztell_file = ftell_file_func;
+    pzlib_filefunc_def->zseek_file = fseek_file_func;
+    pzlib_filefunc_def->zclose_file = fclose_file_func;
+    pzlib_filefunc_def->zerror_file = ferror_file_func;
+    pzlib_filefunc_def->opaque = NULL;
+}
+
+void fill_fopen64_filefunc (zlib_filefunc64_def*  pzlib_filefunc_def)
+{
+    pzlib_filefunc_def->zopen64_file = fopen64_file_func;
+    pzlib_filefunc_def->zread_file = fread_file_func;
+    pzlib_filefunc_def->zwrite_file = fwrite_file_func;
+    pzlib_filefunc_def->ztell64_file = ftell64_file_func;
+    pzlib_filefunc_def->zseek64_file = fseek64_file_func;
+    pzlib_filefunc_def->zclose_file = fclose_file_func;
+    pzlib_filefunc_def->zerror_file = ferror_file_func;
+    pzlib_filefunc_def->opaque = NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/ThirdParty/minizip/ioapi.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,208 @@
+/* ioapi.h -- IO base function header for compress/uncompress .zip
+   part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Modifications for Zip64 support
+         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
+
+         For more info read MiniZip_info.txt
+
+         Changes
+
+    Oct-2009 - Defined ZPOS64_T to fpos_t on windows and u_int64_t on linux. (might need to find a better why for this)
+    Oct-2009 - Change to fseeko64, ftello64 and fopen64 so large files would work on linux.
+               More if/def section may be needed to support other platforms
+    Oct-2009 - Defined fxxxx64 calls to normal fopen/ftell/fseek so they would compile on windows.
+                          (but you should use iowin32.c for windows instead)
+
+*/
+
+#ifndef _ZLIBIOAPI64_H
+#define _ZLIBIOAPI64_H
+
+#if (!defined(_WIN32)) && (!defined(WIN32)) && (!defined(__APPLE__))
+
+  // Linux needs this to support file operation on files larger then 4+GB
+  // But might need better if/def to select just the platforms that needs them.
+
+        #ifndef __USE_FILE_OFFSET64
+                #define __USE_FILE_OFFSET64
+        #endif
+        #ifndef __USE_LARGEFILE64
+                #define __USE_LARGEFILE64
+        #endif
+        #ifndef _LARGEFILE64_SOURCE
+                #define _LARGEFILE64_SOURCE
+        #endif
+        #ifndef _FILE_OFFSET_BIT
+                #define _FILE_OFFSET_BIT 64
+        #endif
+
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "zlib.h"
+
+#if defined(USE_FILE32API)
+#define fopen64 fopen
+#define ftello64 ftell
+#define fseeko64 fseek
+#else
+#ifdef __FreeBSD__
+#define fopen64 fopen
+#define ftello64 ftello
+#define fseeko64 fseeko
+#endif
+#ifdef _MSC_VER
+ #define fopen64 fopen
+ #if (_MSC_VER >= 1400) && (!(defined(NO_MSCVER_FILE64_FUNC)))
+  #define ftello64 _ftelli64
+  #define fseeko64 _fseeki64
+ #else // old MSC
+  #define ftello64 ftell
+  #define fseeko64 fseek
+ #endif
+#endif
+#endif
+
+/*
+#ifndef ZPOS64_T
+  #ifdef _WIN32
+                #define ZPOS64_T fpos_t
+  #else
+    #include <stdint.h>
+    #define ZPOS64_T uint64_t
+  #endif
+#endif
+*/
+
+#ifdef HAVE_MINIZIP64_CONF_H
+#include "mz64conf.h"
+#endif
+
+/* a type choosen by DEFINE */
+#ifdef HAVE_64BIT_INT_CUSTOM
+typedef  64BIT_INT_CUSTOM_TYPE ZPOS64_T;
+#else
+#ifdef HAS_STDINT_H
+#include "stdint.h"
+typedef uint64_t ZPOS64_T;
+#else
+
+/* Maximum unsigned 32-bit value used as placeholder for zip64 */
+#define MAXU32 0xffffffff
+
+#if defined(_MSC_VER) || defined(__BORLANDC__)
+typedef unsigned __int64 ZPOS64_T;
+#else
+typedef unsigned long long int ZPOS64_T;
+#endif
+#endif
+#endif
+
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#define ZLIB_FILEFUNC_SEEK_CUR (1)
+#define ZLIB_FILEFUNC_SEEK_END (2)
+#define ZLIB_FILEFUNC_SEEK_SET (0)
+
+#define ZLIB_FILEFUNC_MODE_READ      (1)
+#define ZLIB_FILEFUNC_MODE_WRITE     (2)
+#define ZLIB_FILEFUNC_MODE_READWRITEFILTER (3)
+
+#define ZLIB_FILEFUNC_MODE_EXISTING (4)
+#define ZLIB_FILEFUNC_MODE_CREATE   (8)
+
+
+#ifndef ZCALLBACK
+ #if (defined(WIN32) || defined(_WIN32) || defined (WINDOWS) || defined (_WINDOWS)) && defined(CALLBACK) && defined (USEWINDOWS_CALLBACK)
+   #define ZCALLBACK CALLBACK
+ #else
+   #define ZCALLBACK
+ #endif
+#endif
+
+
+
+
+typedef voidpf   (ZCALLBACK *open_file_func)      OF((voidpf opaque, const char* filename, int mode));
+typedef uLong    (ZCALLBACK *read_file_func)      OF((voidpf opaque, voidpf stream, void* buf, uLong size));
+typedef uLong    (ZCALLBACK *write_file_func)     OF((voidpf opaque, voidpf stream, const void* buf, uLong size));
+typedef int      (ZCALLBACK *close_file_func)     OF((voidpf opaque, voidpf stream));
+typedef int      (ZCALLBACK *testerror_file_func) OF((voidpf opaque, voidpf stream));
+
+typedef long     (ZCALLBACK *tell_file_func)      OF((voidpf opaque, voidpf stream));
+typedef long     (ZCALLBACK *seek_file_func)      OF((voidpf opaque, voidpf stream, uLong offset, int origin));
+
+
+/* here is the "old" 32 bits structure structure */
+typedef struct zlib_filefunc_def_s
+{
+    open_file_func      zopen_file;
+    read_file_func      zread_file;
+    write_file_func     zwrite_file;
+    tell_file_func      ztell_file;
+    seek_file_func      zseek_file;
+    close_file_func     zclose_file;
+    testerror_file_func zerror_file;
+    voidpf              opaque;
+} zlib_filefunc_def;
+
+typedef ZPOS64_T (ZCALLBACK *tell64_file_func)    OF((voidpf opaque, voidpf stream));
+typedef long     (ZCALLBACK *seek64_file_func)    OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin));
+typedef voidpf   (ZCALLBACK *open64_file_func)    OF((voidpf opaque, const void* filename, int mode));
+
+typedef struct zlib_filefunc64_def_s
+{
+    open64_file_func    zopen64_file;
+    read_file_func      zread_file;
+    write_file_func     zwrite_file;
+    tell64_file_func    ztell64_file;
+    seek64_file_func    zseek64_file;
+    close_file_func     zclose_file;
+    testerror_file_func zerror_file;
+    voidpf              opaque;
+} zlib_filefunc64_def;
+
+void fill_fopen64_filefunc OF((zlib_filefunc64_def* pzlib_filefunc_def));
+void fill_fopen_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def));
+
+/* now internal definition, only for zip.c and unzip.h */
+typedef struct zlib_filefunc64_32_def_s
+{
+    zlib_filefunc64_def zfile_func64;
+    open_file_func      zopen32_file;
+    tell_file_func      ztell32_file;
+    seek_file_func      zseek32_file;
+} zlib_filefunc64_32_def;
+
+
+#define ZREAD64(filefunc,filestream,buf,size)     ((*((filefunc).zfile_func64.zread_file))   ((filefunc).zfile_func64.opaque,filestream,buf,size))
+#define ZWRITE64(filefunc,filestream,buf,size)    ((*((filefunc).zfile_func64.zwrite_file))  ((filefunc).zfile_func64.opaque,filestream,buf,size))
+//#define ZTELL64(filefunc,filestream)            ((*((filefunc).ztell64_file)) ((filefunc).opaque,filestream))
+//#define ZSEEK64(filefunc,filestream,pos,mode)   ((*((filefunc).zseek64_file)) ((filefunc).opaque,filestream,pos,mode))
+#define ZCLOSE64(filefunc,filestream)             ((*((filefunc).zfile_func64.zclose_file))  ((filefunc).zfile_func64.opaque,filestream))
+#define ZERROR64(filefunc,filestream)             ((*((filefunc).zfile_func64.zerror_file))  ((filefunc).zfile_func64.opaque,filestream))
+
+voidpf call_zopen64 OF((const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode));
+long    call_zseek64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin));
+ZPOS64_T call_ztell64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream));
+
+void    fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32);
+
+#define ZOPEN64(filefunc,filename,mode)         (call_zopen64((&(filefunc)),(filename),(mode)))
+#define ZTELL64(filefunc,filestream)            (call_ztell64((&(filefunc)),(filestream)))
+#define ZSEEK64(filefunc,filestream,pos,mode)   (call_zseek64((&(filefunc)),(filestream),(pos),(mode)))
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/ThirdParty/minizip/zip.c	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,2007 @@
+/* zip.c -- IO on .zip files using zlib
+   Version 1.1, February 14h, 2010
+   part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Modifications for Zip64 support
+         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
+
+         For more info read MiniZip_info.txt
+
+         Changes
+   Oct-2009 - Mathias Svensson - Remove old C style function prototypes
+   Oct-2009 - Mathias Svensson - Added Zip64 Support when creating new file archives
+   Oct-2009 - Mathias Svensson - Did some code cleanup and refactoring to get better overview of some functions.
+   Oct-2009 - Mathias Svensson - Added zipRemoveExtraInfoBlock to strip extra field data from its ZIP64 data
+                                 It is used when recreting zip archive with RAW when deleting items from a zip.
+                                 ZIP64 data is automaticly added to items that needs it, and existing ZIP64 data need to be removed.
+   Oct-2009 - Mathias Svensson - Added support for BZIP2 as compression mode (bzip2 lib is required)
+   Jan-2010 - back to unzip and minizip 1.0 name scheme, with compatibility layer
+
+*/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include "zlib.h"
+#include "zip.h"
+
+#ifdef STDC
+#  include <stddef.h>
+#  include <string.h>
+#  include <stdlib.h>
+#endif
+#ifdef NO_ERRNO_H
+    extern int errno;
+#else
+#   include <errno.h>
+#endif
+
+
+#ifndef local
+#  define local static
+#endif
+/* compile with -Dlocal if your debugger can't find static symbols */
+
+#ifndef VERSIONMADEBY
+# define VERSIONMADEBY   (0x0) /* platform depedent */
+#endif
+
+#ifndef Z_BUFSIZE
+#define Z_BUFSIZE (64*1024) //(16384)
+#endif
+
+#ifndef Z_MAXFILENAMEINZIP
+#define Z_MAXFILENAMEINZIP (256)
+#endif
+
+#ifndef ALLOC
+# define ALLOC(size) (malloc(size))
+#endif
+#ifndef TRYFREE
+# define TRYFREE(p) {if (p) free(p);}
+#endif
+
+/*
+#define SIZECENTRALDIRITEM (0x2e)
+#define SIZEZIPLOCALHEADER (0x1e)
+*/
+
+/* I've found an old Unix (a SunOS 4.1.3_U1) without all SEEK_* defined.... */
+
+
+// NOT sure that this work on ALL platform
+#define MAKEULONG64(a, b) ((ZPOS64_T)(((unsigned long)(a)) | ((ZPOS64_T)((unsigned long)(b))) << 32))
+
+#ifndef SEEK_CUR
+#define SEEK_CUR    1
+#endif
+
+#ifndef SEEK_END
+#define SEEK_END    2
+#endif
+
+#ifndef SEEK_SET
+#define SEEK_SET    0
+#endif
+
+#ifndef DEF_MEM_LEVEL
+#if MAX_MEM_LEVEL >= 8
+#  define DEF_MEM_LEVEL 8
+#else
+#  define DEF_MEM_LEVEL  MAX_MEM_LEVEL
+#endif
+#endif
+const char zip_copyright[] =" zip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll";
+
+
+#define SIZEDATA_INDATABLOCK (4096-(4*4))
+
+#define LOCALHEADERMAGIC    (0x04034b50)
+#define CENTRALHEADERMAGIC  (0x02014b50)
+#define ENDHEADERMAGIC      (0x06054b50)
+#define ZIP64ENDHEADERMAGIC      (0x6064b50)
+#define ZIP64ENDLOCHEADERMAGIC   (0x7064b50)
+
+#define FLAG_LOCALHEADER_OFFSET (0x06)
+#define CRC_LOCALHEADER_OFFSET  (0x0e)
+
+#define SIZECENTRALHEADER (0x2e) /* 46 */
+
+typedef struct linkedlist_datablock_internal_s
+{
+  struct linkedlist_datablock_internal_s* next_datablock;
+  uLong  avail_in_this_block;
+  uLong  filled_in_this_block;
+  uLong  unused; /* for future use and alignement */
+  unsigned char data[SIZEDATA_INDATABLOCK];
+} linkedlist_datablock_internal;
+
+typedef struct linkedlist_data_s
+{
+    linkedlist_datablock_internal* first_block;
+    linkedlist_datablock_internal* last_block;
+} linkedlist_data;
+
+
+typedef struct
+{
+    z_stream stream;            /* zLib stream structure for inflate */
+#ifdef HAVE_BZIP2
+    bz_stream bstream;          /* bzLib stream structure for bziped */
+#endif
+
+    int  stream_initialised;    /* 1 is stream is initialised */
+    uInt pos_in_buffered_data;  /* last written byte in buffered_data */
+
+    ZPOS64_T pos_local_header;     /* offset of the local header of the file
+                                     currenty writing */
+    char* central_header;       /* central header data for the current file */
+    uLong size_centralExtra;
+    uLong size_centralheader;   /* size of the central header for cur file */
+    uLong size_centralExtraFree; /* Extra bytes allocated to the centralheader but that are not used */
+    uLong flag;                 /* flag of the file currently writing */
+
+    int  method;                /* compression method of file currenty wr.*/
+    int  raw;                   /* 1 for directly writing raw data */
+    Byte buffered_data[Z_BUFSIZE];/* buffer contain compressed data to be writ*/
+    uLong dosDate;
+    uLong crc32;
+    int  encrypt;
+    int  zip64;               /* Add ZIP64 extened information in the extra field */
+    ZPOS64_T pos_zip64extrainfo;
+    ZPOS64_T totalCompressedData;
+    ZPOS64_T totalUncompressedData;
+#ifndef NOCRYPT
+    unsigned long keys[3];     /* keys defining the pseudo-random sequence */
+    const unsigned long* pcrc_32_tab;
+    int crypt_header_size;
+#endif
+} curfile64_info;
+
+typedef struct
+{
+    zlib_filefunc64_32_def z_filefunc;
+    voidpf filestream;        /* io structore of the zipfile */
+    linkedlist_data central_dir;/* datablock with central dir in construction*/
+    int  in_opened_file_inzip;  /* 1 if a file in the zip is currently writ.*/
+    curfile64_info ci;            /* info on the file curretly writing */
+
+    ZPOS64_T begin_pos;            /* position of the beginning of the zipfile */
+    ZPOS64_T add_position_when_writting_offset;
+    ZPOS64_T number_entry;
+
+#ifndef NO_ADDFILEINEXISTINGZIP
+    char *globalcomment;
+#endif
+
+} zip64_internal;
+
+
+#ifndef NOCRYPT
+#define INCLUDECRYPTINGCODE_IFCRYPTALLOWED
+#include "crypt.h"
+#endif
+
+local linkedlist_datablock_internal* allocate_new_datablock()
+{
+    linkedlist_datablock_internal* ldi;
+    ldi = (linkedlist_datablock_internal*)
+                 ALLOC(sizeof(linkedlist_datablock_internal));
+    if (ldi!=NULL)
+    {
+        ldi->next_datablock = NULL ;
+        ldi->filled_in_this_block = 0 ;
+        ldi->avail_in_this_block = SIZEDATA_INDATABLOCK ;
+    }
+    return ldi;
+}
+
+local void free_datablock(linkedlist_datablock_internal* ldi)
+{
+    while (ldi!=NULL)
+    {
+        linkedlist_datablock_internal* ldinext = ldi->next_datablock;
+        TRYFREE(ldi);
+        ldi = ldinext;
+    }
+}
+
+local void init_linkedlist(linkedlist_data* ll)
+{
+    ll->first_block = ll->last_block = NULL;
+}
+
+local void free_linkedlist(linkedlist_data* ll)
+{
+    free_datablock(ll->first_block);
+    ll->first_block = ll->last_block = NULL;
+}
+
+
+local int add_data_in_datablock(linkedlist_data* ll, const void* buf, uLong len)
+{
+    linkedlist_datablock_internal* ldi;
+    const unsigned char* from_copy;
+
+    if (ll==NULL)
+        return ZIP_INTERNALERROR;
+
+    if (ll->last_block == NULL)
+    {
+        ll->first_block = ll->last_block = allocate_new_datablock();
+        if (ll->first_block == NULL)
+            return ZIP_INTERNALERROR;
+    }
+
+    ldi = ll->last_block;
+    from_copy = (unsigned char*)buf;
+
+    while (len>0)
+    {
+        uInt copy_this;
+        uInt i;
+        unsigned char* to_copy;
+
+        if (ldi->avail_in_this_block==0)
+        {
+            ldi->next_datablock = allocate_new_datablock();
+            if (ldi->next_datablock == NULL)
+                return ZIP_INTERNALERROR;
+            ldi = ldi->next_datablock ;
+            ll->last_block = ldi;
+        }
+
+        if (ldi->avail_in_this_block < len)
+            copy_this = (uInt)ldi->avail_in_this_block;
+        else
+            copy_this = (uInt)len;
+
+        to_copy = &(ldi->data[ldi->filled_in_this_block]);
+
+        for (i=0;i<copy_this;i++)
+            *(to_copy+i)=*(from_copy+i);
+
+        ldi->filled_in_this_block += copy_this;
+        ldi->avail_in_this_block -= copy_this;
+        from_copy += copy_this ;
+        len -= copy_this;
+    }
+    return ZIP_OK;
+}
+
+
+
+/****************************************************************************/
+
+#ifndef NO_ADDFILEINEXISTINGZIP
+/* ===========================================================================
+   Inputs a long in LSB order to the given file
+   nbByte == 1, 2 ,4 or 8 (byte, short or long, ZPOS64_T)
+*/
+
+local int zip64local_putValue OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T x, int nbByte));
+local int zip64local_putValue (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T x, int nbByte)
+{
+    unsigned char buf[8];
+    int n;
+    for (n = 0; n < nbByte; n++)
+    {
+        buf[n] = (unsigned char)(x & 0xff);
+        x >>= 8;
+    }
+    if (x != 0)
+      {     /* data overflow - hack for ZIP64 (X Roche) */
+      for (n = 0; n < nbByte; n++)
+        {
+          buf[n] = 0xff;
+        }
+      }
+
+    if (ZWRITE64(*pzlib_filefunc_def,filestream,buf,nbByte)!=(uLong)nbByte)
+        return ZIP_ERRNO;
+    else
+        return ZIP_OK;
+}
+
+local void zip64local_putValue_inmemory OF((void* dest, ZPOS64_T x, int nbByte));
+local void zip64local_putValue_inmemory (void* dest, ZPOS64_T x, int nbByte)
+{
+    unsigned char* buf=(unsigned char*)dest;
+    int n;
+    for (n = 0; n < nbByte; n++) {
+        buf[n] = (unsigned char)(x & 0xff);
+        x >>= 8;
+    }
+
+    if (x != 0)
+    {     /* data overflow - hack for ZIP64 */
+       for (n = 0; n < nbByte; n++)
+       {
+          buf[n] = 0xff;
+       }
+    }
+}
+
+/****************************************************************************/
+
+
+local uLong zip64local_TmzDateToDosDate(const tm_zip* ptm)
+{
+    uLong year = (uLong)ptm->tm_year;
+    if (year>=1980)
+        year-=1980;
+    else if (year>=80)
+        year-=80;
+    return
+      (uLong) (((ptm->tm_mday) + (32 * (ptm->tm_mon+1)) + (512 * year)) << 16) |
+        ((ptm->tm_sec/2) + (32* ptm->tm_min) + (2048 * (uLong)ptm->tm_hour));
+}
+
+
+/****************************************************************************/
+
+local int zip64local_getByte OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, int *pi));
+
+local int zip64local_getByte(const zlib_filefunc64_32_def* pzlib_filefunc_def,voidpf filestream,int* pi)
+{
+    unsigned char c;
+    int err = (int)ZREAD64(*pzlib_filefunc_def,filestream,&c,1);
+    if (err==1)
+    {
+        *pi = (int)c;
+        return ZIP_OK;
+    }
+    else
+    {
+        if (ZERROR64(*pzlib_filefunc_def,filestream))
+            return ZIP_ERRNO;
+        else
+            return ZIP_EOF;
+    }
+}
+
+
+/* ===========================================================================
+   Reads a long in LSB order from the given gz_stream. Sets
+*/
+local int zip64local_getShort OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX));
+
+local int zip64local_getShort (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong* pX)
+{
+    uLong x ;
+    int i = 0;
+    int err;
+
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x = (uLong)i;
+
+    if (err==ZIP_OK)
+        err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x += ((uLong)i)<<8;
+
+    if (err==ZIP_OK)
+        *pX = x;
+    else
+        *pX = 0;
+    return err;
+}
+
+local int zip64local_getLong OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX));
+
+local int zip64local_getLong (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong* pX)
+{
+    uLong x ;
+    int i = 0;
+    int err;
+
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x = (uLong)i;
+
+    if (err==ZIP_OK)
+        err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x += ((uLong)i)<<8;
+
+    if (err==ZIP_OK)
+        err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x += ((uLong)i)<<16;
+
+    if (err==ZIP_OK)
+        err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+    x += ((uLong)i)<<24;
+
+    if (err==ZIP_OK)
+        *pX = x;
+    else
+        *pX = 0;
+    return err;
+}
+
+local int zip64local_getLong64 OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX));
+
+
+local int zip64local_getLong64 (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX)
+{
+  ZPOS64_T x;
+  int i = 0;
+  int err;
+
+  err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x = (ZPOS64_T)i;
+
+  if (err==ZIP_OK)
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x += ((ZPOS64_T)i)<<8;
+
+  if (err==ZIP_OK)
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x += ((ZPOS64_T)i)<<16;
+
+  if (err==ZIP_OK)
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x += ((ZPOS64_T)i)<<24;
+
+  if (err==ZIP_OK)
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x += ((ZPOS64_T)i)<<32;
+
+  if (err==ZIP_OK)
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x += ((ZPOS64_T)i)<<40;
+
+  if (err==ZIP_OK)
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x += ((ZPOS64_T)i)<<48;
+
+  if (err==ZIP_OK)
+    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
+  x += ((ZPOS64_T)i)<<56;
+
+  if (err==ZIP_OK)
+    *pX = x;
+  else
+    *pX = 0;
+
+  return err;
+}
+
+#ifndef BUFREADCOMMENT
+#define BUFREADCOMMENT (0x400)
+#endif
+/*
+  Locate the Central directory of a zipfile (at the end, just before
+    the global comment)
+*/
+local ZPOS64_T zip64local_SearchCentralDir OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream));
+
+local ZPOS64_T zip64local_SearchCentralDir(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)
+{
+  unsigned char* buf;
+  ZPOS64_T uSizeFile;
+  ZPOS64_T uBackRead;
+  ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */
+  ZPOS64_T uPosFound=0;
+
+  if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0)
+    return 0;
+
+
+  uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream);
+
+  if (uMaxBack>uSizeFile)
+    uMaxBack = uSizeFile;
+
+  buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4);
+  if (buf==NULL)
+    return 0;
+
+  uBackRead = 4;
+  while (uBackRead<uMaxBack)
+  {
+    uLong uReadSize;
+    ZPOS64_T uReadPos ;
+    int i;
+    if (uBackRead+BUFREADCOMMENT>uMaxBack)
+      uBackRead = uMaxBack;
+    else
+      uBackRead+=BUFREADCOMMENT;
+    uReadPos = uSizeFile-uBackRead ;
+
+    uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ?
+      (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos);
+    if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0)
+      break;
+
+    if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize)
+      break;
+
+    for (i=(int)uReadSize-3; (i--)>0;)
+      if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) &&
+        ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06))
+      {
+        uPosFound = uReadPos+i;
+        break;
+      }
+
+      if (uPosFound!=0)
+        break;
+  }
+  TRYFREE(buf);
+  return uPosFound;
+}
+
+/*
+Locate the End of Zip64 Central directory locator and from there find the CD of a zipfile (at the end, just before
+the global comment)
+*/
+local ZPOS64_T zip64local_SearchCentralDir64 OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream));
+
+local ZPOS64_T zip64local_SearchCentralDir64(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)
+{
+  unsigned char* buf;
+  ZPOS64_T uSizeFile;
+  ZPOS64_T uBackRead;
+  ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */
+  ZPOS64_T uPosFound=0;
+  uLong uL;
+  ZPOS64_T relativeOffset;
+
+  if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0)
+    return 0;
+
+  uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream);
+
+  if (uMaxBack>uSizeFile)
+    uMaxBack = uSizeFile;
+
+  buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4);
+  if (buf==NULL)
+    return 0;
+
+  uBackRead = 4;
+  while (uBackRead<uMaxBack)
+  {
+    uLong uReadSize;
+    ZPOS64_T uReadPos;
+    int i;
+    if (uBackRead+BUFREADCOMMENT>uMaxBack)
+      uBackRead = uMaxBack;
+    else
+      uBackRead+=BUFREADCOMMENT;
+    uReadPos = uSizeFile-uBackRead ;
+
+    uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ?
+      (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos);
+    if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0)
+      break;
+
+    if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize)
+      break;
+
+    for (i=(int)uReadSize-3; (i--)>0;)
+    {
+      // Signature "0x07064b50" Zip64 end of central directory locater
+      if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && ((*(buf+i+2))==0x06) && ((*(buf+i+3))==0x07))
+      {
+        uPosFound = uReadPos+i;
+        break;
+      }
+    }
+
+      if (uPosFound!=0)
+        break;
+  }
+
+  TRYFREE(buf);
+  if (uPosFound == 0)
+    return 0;
+
+  /* Zip64 end of central directory locator */
+  if (ZSEEK64(*pzlib_filefunc_def,filestream, uPosFound,ZLIB_FILEFUNC_SEEK_SET)!=0)
+    return 0;
+
+  /* the signature, already checked */
+  if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK)
+    return 0;
+
+  /* number of the disk with the start of the zip64 end of  central directory */
+  if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK)
+    return 0;
+  if (uL != 0)
+    return 0;
+
+  /* relative offset of the zip64 end of central directory record */
+  if (zip64local_getLong64(pzlib_filefunc_def,filestream,&relativeOffset)!=ZIP_OK)
+    return 0;
+
+  /* total number of disks */
+  if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK)
+    return 0;
+  if (uL != 1)
+    return 0;
+
+  /* Goto Zip64 end of central directory record */
+  if (ZSEEK64(*pzlib_filefunc_def,filestream, relativeOffset,ZLIB_FILEFUNC_SEEK_SET)!=0)
+    return 0;
+
+  /* the signature */
+  if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK)
+    return 0;
+
+  if (uL != 0x06064b50) // signature of 'Zip64 end of central directory'
+    return 0;
+
+  return relativeOffset;
+}
+
+int LoadCentralDirectoryRecord(zip64_internal* pziinit)
+{
+  int err=ZIP_OK;
+  ZPOS64_T byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/
+
+  ZPOS64_T size_central_dir;     /* size of the central directory  */
+  ZPOS64_T offset_central_dir;   /* offset of start of central directory */
+  ZPOS64_T central_pos;
+  uLong uL;
+
+  uLong number_disk;          /* number of the current dist, used for
+                              spaning ZIP, unsupported, always 0*/
+  uLong number_disk_with_CD;  /* number the the disk with central dir, used
+                              for spaning ZIP, unsupported, always 0*/
+  ZPOS64_T number_entry;
+  ZPOS64_T number_entry_CD;      /* total number of entries in
+                                the central dir
+                                (same than number_entry on nospan) */
+  uLong VersionMadeBy;
+  uLong VersionNeeded;
+  uLong size_comment;
+
+  int hasZIP64Record = 0;
+
+  // check first if we find a ZIP64 record
+  central_pos = zip64local_SearchCentralDir64(&pziinit->z_filefunc,pziinit->filestream);
+  if(central_pos > 0)
+  {
+    hasZIP64Record = 1;
+  }
+  else if(central_pos == 0)
+  {
+    central_pos = zip64local_SearchCentralDir(&pziinit->z_filefunc,pziinit->filestream);
+  }
+
+/* disable to allow appending to empty ZIP archive
+        if (central_pos==0)
+            err=ZIP_ERRNO;
+*/
+
+  if(hasZIP64Record)
+  {
+    ZPOS64_T sizeEndOfCentralDirectory;
+    if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, central_pos, ZLIB_FILEFUNC_SEEK_SET) != 0)
+      err=ZIP_ERRNO;
+
+    /* the signature, already checked */
+    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&uL)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* size of zip64 end of central directory record */
+    if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream, &sizeEndOfCentralDirectory)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* version made by */
+    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &VersionMadeBy)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* version needed to extract */
+    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &VersionNeeded)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* number of this disk */
+    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&number_disk)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* number of the disk with the start of the central directory */
+    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&number_disk_with_CD)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* total number of entries in the central directory on this disk */
+    if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream, &number_entry)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* total number of entries in the central directory */
+    if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&number_entry_CD)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    if ((number_entry_CD!=number_entry) || (number_disk_with_CD!=0) || (number_disk!=0))
+      err=ZIP_BADZIPFILE;
+
+    /* size of the central directory */
+    if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&size_central_dir)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* offset of start of central directory with respect to the
+    starting disk number */
+    if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&offset_central_dir)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    // TODO..
+    // read the comment from the standard central header.
+    size_comment = 0;
+  }
+  else
+  {
+    // Read End of central Directory info
+    if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0)
+      err=ZIP_ERRNO;
+
+    /* the signature, already checked */
+    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&uL)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* number of this disk */
+    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream,&number_disk)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* number of the disk with the start of the central directory */
+    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream,&number_disk_with_CD)!=ZIP_OK)
+      err=ZIP_ERRNO;
+
+    /* total number of entries in the central dir on this disk */
+    number_entry = 0;
+    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK)
+      err=ZIP_ERRNO;
+    else
+      number_entry = uL;
+
+    /* total number of entries in the central dir */
+    number_entry_CD = 0;
+    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK)
+      err=ZIP_ERRNO;
+    else
+      number_entry_CD = uL;
+
+    if ((number_entry_CD!=number_entry) || (number_disk_with_CD!=0) || (number_disk!=0))
+      err=ZIP_BADZIPFILE;
+
+    /* size of the central directory */
+    size_central_dir = 0;
+    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK)
+      err=ZIP_ERRNO;
+    else
+      size_central_dir = uL;
+
+    /* offset of start of central directory with respect to the starting disk number */
+    offset_central_dir = 0;
+    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK)
+      err=ZIP_ERRNO;
+    else
+      offset_central_dir = uL;
+
+
+    /* zipfile global comment length */
+    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &size_comment)!=ZIP_OK)
+      err=ZIP_ERRNO;
+  }
+
+  if ((central_pos<offset_central_dir+size_central_dir) &&
+    (err==ZIP_OK))
+    err=ZIP_BADZIPFILE;
+
+  if (err!=ZIP_OK)
+  {
+    ZCLOSE64(pziinit->z_filefunc, pziinit->filestream);
+    return ZIP_ERRNO;
+  }
+
+  if (size_comment>0)
+  {
+    pziinit->globalcomment = (char*)ALLOC(size_comment+1);
+    if (pziinit->globalcomment)
+    {
+      size_comment = ZREAD64(pziinit->z_filefunc, pziinit->filestream, pziinit->globalcomment,size_comment);
+      pziinit->globalcomment[size_comment]=0;
+    }
+  }
+
+  byte_before_the_zipfile = central_pos - (offset_central_dir+size_central_dir);
+  pziinit->add_position_when_writting_offset = byte_before_the_zipfile;
+
+  {
+    ZPOS64_T size_central_dir_to_read = size_central_dir;
+    size_t buf_size = SIZEDATA_INDATABLOCK;
+    void* buf_read = (void*)ALLOC(buf_size);
+    if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, offset_central_dir + byte_before_the_zipfile, ZLIB_FILEFUNC_SEEK_SET) != 0)
+      err=ZIP_ERRNO;
+
+    while ((size_central_dir_to_read>0) && (err==ZIP_OK))
+    {
+      ZPOS64_T read_this = SIZEDATA_INDATABLOCK;
+      if (read_this > size_central_dir_to_read)
+        read_this = size_central_dir_to_read;
+
+      if (ZREAD64(pziinit->z_filefunc, pziinit->filestream,buf_read,(uLong)read_this) != read_this)
+        err=ZIP_ERRNO;
+
+      if (err==ZIP_OK)
+        err = add_data_in_datablock(&pziinit->central_dir,buf_read, (uLong)read_this);
+
+      size_central_dir_to_read-=read_this;
+    }
+    TRYFREE(buf_read);
+  }
+  pziinit->begin_pos = byte_before_the_zipfile;
+  pziinit->number_entry = number_entry_CD;
+
+  if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, offset_central_dir+byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET) != 0)
+    err=ZIP_ERRNO;
+
+  return err;
+}
+
+
+#endif /* !NO_ADDFILEINEXISTINGZIP*/
+
+
+/************************************************************/
+extern zipFile ZEXPORT zipOpen3 (const void *pathname, int append, zipcharpc* globalcomment, zlib_filefunc64_32_def* pzlib_filefunc64_32_def)
+{
+    zip64_internal ziinit;
+    zip64_internal* zi;
+    int err=ZIP_OK;
+
+    ziinit.z_filefunc.zseek32_file = NULL;
+    ziinit.z_filefunc.ztell32_file = NULL;
+    if (pzlib_filefunc64_32_def==NULL)
+        fill_fopen64_filefunc(&ziinit.z_filefunc.zfile_func64);
+    else
+        ziinit.z_filefunc = *pzlib_filefunc64_32_def;
+
+    ziinit.filestream = ZOPEN64(ziinit.z_filefunc,
+                  pathname,
+                  (append == APPEND_STATUS_CREATE) ?
+                  (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_CREATE) :
+                    (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_EXISTING));
+
+    if (ziinit.filestream == NULL)
+        return NULL;
+
+    if (append == APPEND_STATUS_CREATEAFTER)
+        ZSEEK64(ziinit.z_filefunc,ziinit.filestream,0,SEEK_END);
+
+    ziinit.begin_pos = ZTELL64(ziinit.z_filefunc,ziinit.filestream);
+    ziinit.in_opened_file_inzip = 0;
+    ziinit.ci.stream_initialised = 0;
+    ziinit.number_entry = 0;
+    ziinit.add_position_when_writting_offset = 0;
+    init_linkedlist(&(ziinit.central_dir));
+
+
+
+    zi = (zip64_internal*)ALLOC(sizeof(zip64_internal));
+    if (zi==NULL)
+    {
+        ZCLOSE64(ziinit.z_filefunc,ziinit.filestream);
+        return NULL;
+    }
+
+    /* now we add file in a zipfile */
+#    ifndef NO_ADDFILEINEXISTINGZIP
+    ziinit.globalcomment = NULL;
+    if (append == APPEND_STATUS_ADDINZIP)
+    {
+      // Read and Cache Central Directory Records
+      err = LoadCentralDirectoryRecord(&ziinit);
+    }
+
+    if (globalcomment)
+    {
+      *globalcomment = ziinit.globalcomment;
+    }
+#    endif /* !NO_ADDFILEINEXISTINGZIP*/
+
+    if (err != ZIP_OK)
+    {
+#    ifndef NO_ADDFILEINEXISTINGZIP
+        TRYFREE(ziinit.globalcomment);
+#    endif /* !NO_ADDFILEINEXISTINGZIP*/
+        TRYFREE(zi);
+        return NULL;
+    }
+    else
+    {
+        *zi = ziinit;
+        return (zipFile)zi;
+    }
+}
+
+extern zipFile ZEXPORT zipOpen2 (const char *pathname, int append, zipcharpc* globalcomment, zlib_filefunc_def* pzlib_filefunc32_def)
+{
+    if (pzlib_filefunc32_def != NULL)
+    {
+        zlib_filefunc64_32_def zlib_filefunc64_32_def_fill;
+        fill_zlib_filefunc64_32_def_from_filefunc32(&zlib_filefunc64_32_def_fill,pzlib_filefunc32_def);
+        return zipOpen3(pathname, append, globalcomment, &zlib_filefunc64_32_def_fill);
+    }
+    else
+        return zipOpen3(pathname, append, globalcomment, NULL);
+}
+
+extern zipFile ZEXPORT zipOpen2_64 (const void *pathname, int append, zipcharpc* globalcomment, zlib_filefunc64_def* pzlib_filefunc_def)
+{
+    if (pzlib_filefunc_def != NULL)
+    {
+        zlib_filefunc64_32_def zlib_filefunc64_32_def_fill;
+        zlib_filefunc64_32_def_fill.zfile_func64 = *pzlib_filefunc_def;
+        zlib_filefunc64_32_def_fill.ztell32_file = NULL;
+        zlib_filefunc64_32_def_fill.zseek32_file = NULL;
+        return zipOpen3(pathname, append, globalcomment, &zlib_filefunc64_32_def_fill);
+    }
+    else
+        return zipOpen3(pathname, append, globalcomment, NULL);
+}
+
+
+
+extern zipFile ZEXPORT zipOpen (const char* pathname, int append)
+{
+    return zipOpen3((const void*)pathname,append,NULL,NULL);
+}
+
+extern zipFile ZEXPORT zipOpen64 (const void* pathname, int append)
+{
+    return zipOpen3(pathname,append,NULL,NULL);
+}
+
+int Write_LocalFileHeader(zip64_internal* zi, const char* filename, uInt size_extrafield_local, const void* extrafield_local)
+{
+  /* write the local header */
+  int err;
+  uInt size_filename = (uInt)strlen(filename);
+  uInt size_extrafield = size_extrafield_local;
+
+  err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)LOCALHEADERMAGIC, 4);
+
+  if (err==ZIP_OK)
+  {
+    if(zi->ci.zip64)
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2);/* version needed to extract */
+    else
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)20,2);/* version needed to extract */
+  }
+
+  if (err==ZIP_OK)
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.flag,2);
+
+  if (err==ZIP_OK)
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.method,2);
+
+  if (err==ZIP_OK)
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.dosDate,4);
+
+  // CRC / Compressed size / Uncompressed size will be filled in later and rewritten later
+  if (err==ZIP_OK)
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* crc 32, unknown */
+  if (err==ZIP_OK)
+  {
+    if(zi->ci.zip64)
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xFFFFFFFF,4); /* compressed size, unknown */
+    else
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* compressed size, unknown */
+  }
+  if (err==ZIP_OK)
+  {
+    if(zi->ci.zip64)
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xFFFFFFFF,4); /* uncompressed size, unknown */
+    else
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* uncompressed size, unknown */
+  }
+
+  if (err==ZIP_OK)
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_filename,2);
+
+  if(zi->ci.zip64)
+  {
+    size_extrafield += 20;
+  }
+
+  if (err==ZIP_OK)
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_extrafield,2);
+
+  if ((err==ZIP_OK) && (size_filename > 0))
+  {
+    if (ZWRITE64(zi->z_filefunc,zi->filestream,filename,size_filename)!=size_filename)
+      err = ZIP_ERRNO;
+  }
+
+  if ((err==ZIP_OK) && (size_extrafield_local > 0))
+  {
+    if (ZWRITE64(zi->z_filefunc, zi->filestream, extrafield_local, size_extrafield_local) != size_extrafield_local)
+      err = ZIP_ERRNO;
+  }
+
+
+  if ((err==ZIP_OK) && (zi->ci.zip64))
+  {
+      // write the Zip64 extended info
+      short HeaderID = 1;
+      short DataSize = 16;
+      ZPOS64_T CompressedSize = 0;
+      ZPOS64_T UncompressedSize = 0;
+
+      // Remember position of Zip64 extended info for the local file header. (needed when we update size after done with file)
+      zi->ci.pos_zip64extrainfo = ZTELL64(zi->z_filefunc,zi->filestream);
+
+      err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (short)HeaderID,2);
+      err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (short)DataSize,2);
+
+      err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (ZPOS64_T)UncompressedSize,8);
+      err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (ZPOS64_T)CompressedSize,8);
+  }
+
+  return err;
+}
+
+/*
+ NOTE.
+ When writing RAW the ZIP64 extended information in extrafield_local and extrafield_global needs to be stripped
+ before calling this function it can be done with zipRemoveExtraInfoBlock
+
+ It is not done here because then we need to realloc a new buffer since parameters are 'const' and I want to minimize
+ unnecessary allocations.
+ */
+extern int ZEXPORT zipOpenNewFileInZip4_64 (zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                         const void* extrafield_local, uInt size_extrafield_local,
+                                         const void* extrafield_global, uInt size_extrafield_global,
+                                         const char* comment, int method, int level, int raw,
+                                         int windowBits,int memLevel, int strategy,
+                                         const char* password, uLong crcForCrypting,
+                                         uLong versionMadeBy, uLong flagBase, int zip64)
+{
+    zip64_internal* zi;
+    uInt size_filename;
+    uInt size_comment;
+    uInt i;
+    int err = ZIP_OK;
+
+#    ifdef NOCRYPT
+    (crcForCrypting);
+    if (password != NULL)
+        return ZIP_PARAMERROR;
+#    endif
+
+    if (file == NULL)
+        return ZIP_PARAMERROR;
+
+#ifdef HAVE_BZIP2
+    if ((method!=0) && (method!=Z_DEFLATED) && (method!=Z_BZIP2ED))
+      return ZIP_PARAMERROR;
+#else
+    if ((method!=0) && (method!=Z_DEFLATED))
+      return ZIP_PARAMERROR;
+#endif
+
+    zi = (zip64_internal*)file;
+
+    if (zi->in_opened_file_inzip == 1)
+    {
+        err = zipCloseFileInZip (file);
+        if (err != ZIP_OK)
+            return err;
+    }
+
+    if (filename==NULL)
+        filename="-";
+
+    if (comment==NULL)
+        size_comment = 0;
+    else
+        size_comment = (uInt)strlen(comment);
+
+    size_filename = (uInt)strlen(filename);
+
+    if (zipfi == NULL)
+        zi->ci.dosDate = 0;
+    else
+    {
+        if (zipfi->dosDate != 0)
+            zi->ci.dosDate = zipfi->dosDate;
+        else
+          zi->ci.dosDate = zip64local_TmzDateToDosDate(&zipfi->tmz_date);
+    }
+
+    zi->ci.flag = flagBase;
+    if ((level==8) || (level==9))
+      zi->ci.flag |= 2;
+    if (level==2)
+      zi->ci.flag |= 4;
+    if (level==1)
+      zi->ci.flag |= 6;
+    if (password != NULL)
+      zi->ci.flag |= 1;
+
+    zi->ci.crc32 = 0;
+    zi->ci.method = method;
+    zi->ci.encrypt = 0;
+    zi->ci.stream_initialised = 0;
+    zi->ci.pos_in_buffered_data = 0;
+    zi->ci.raw = raw;
+    zi->ci.pos_local_header = ZTELL64(zi->z_filefunc,zi->filestream);
+
+    zi->ci.size_centralheader = SIZECENTRALHEADER + size_filename + size_extrafield_global + size_comment;
+    zi->ci.size_centralExtraFree = 32; // Extra space we have reserved in case we need to add ZIP64 extra info data
+
+    zi->ci.central_header = (char*)ALLOC((uInt)zi->ci.size_centralheader + zi->ci.size_centralExtraFree);
+
+    zi->ci.size_centralExtra = size_extrafield_global;
+    zip64local_putValue_inmemory(zi->ci.central_header,(uLong)CENTRALHEADERMAGIC,4);
+    /* version info */
+    zip64local_putValue_inmemory(zi->ci.central_header+4,(uLong)versionMadeBy,2);
+    zip64local_putValue_inmemory(zi->ci.central_header+6,(uLong)20,2);
+    zip64local_putValue_inmemory(zi->ci.central_header+8,(uLong)zi->ci.flag,2);
+    zip64local_putValue_inmemory(zi->ci.central_header+10,(uLong)zi->ci.method,2);
+    zip64local_putValue_inmemory(zi->ci.central_header+12,(uLong)zi->ci.dosDate,4);
+    zip64local_putValue_inmemory(zi->ci.central_header+16,(uLong)0,4); /*crc*/
+    zip64local_putValue_inmemory(zi->ci.central_header+20,(uLong)0,4); /*compr size*/
+    zip64local_putValue_inmemory(zi->ci.central_header+24,(uLong)0,4); /*uncompr size*/
+    zip64local_putValue_inmemory(zi->ci.central_header+28,(uLong)size_filename,2);
+    zip64local_putValue_inmemory(zi->ci.central_header+30,(uLong)size_extrafield_global,2);
+    zip64local_putValue_inmemory(zi->ci.central_header+32,(uLong)size_comment,2);
+    zip64local_putValue_inmemory(zi->ci.central_header+34,(uLong)0,2); /*disk nm start*/
+
+    if (zipfi==NULL)
+        zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)0,2);
+    else
+        zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)zipfi->internal_fa,2);
+
+    if (zipfi==NULL)
+        zip64local_putValue_inmemory(zi->ci.central_header+38,(uLong)0,4);
+    else
+        zip64local_putValue_inmemory(zi->ci.central_header+38,(uLong)zipfi->external_fa,4);
+
+    if(zi->ci.pos_local_header >= 0xffffffff)
+      zip64local_putValue_inmemory(zi->ci.central_header+42,(uLong)0xffffffff,4);
+    else
+      zip64local_putValue_inmemory(zi->ci.central_header+42,(uLong)zi->ci.pos_local_header - zi->add_position_when_writting_offset,4);
+
+    for (i=0;i<size_filename;i++)
+        *(zi->ci.central_header+SIZECENTRALHEADER+i) = *(filename+i);
+
+    for (i=0;i<size_extrafield_global;i++)
+        *(zi->ci.central_header+SIZECENTRALHEADER+size_filename+i) =
+              *(((const char*)extrafield_global)+i);
+
+    for (i=0;i<size_comment;i++)
+        *(zi->ci.central_header+SIZECENTRALHEADER+size_filename+
+              size_extrafield_global+i) = *(comment+i);
+    if (zi->ci.central_header == NULL)
+        return ZIP_INTERNALERROR;
+
+    zi->ci.zip64 = zip64;
+    zi->ci.totalCompressedData = 0;
+    zi->ci.totalUncompressedData = 0;
+    zi->ci.pos_zip64extrainfo = 0;
+
+    err = Write_LocalFileHeader(zi, filename, size_extrafield_local, extrafield_local);
+
+#ifdef HAVE_BZIP2
+    zi->ci.bstream.avail_in = (uInt)0;
+    zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE;
+    zi->ci.bstream.next_out = (char*)zi->ci.buffered_data;
+    zi->ci.bstream.total_in_hi32 = 0;
+    zi->ci.bstream.total_in_lo32 = 0;
+    zi->ci.bstream.total_out_hi32 = 0;
+    zi->ci.bstream.total_out_lo32 = 0;
+#endif
+
+    zi->ci.stream.avail_in = (uInt)0;
+    zi->ci.stream.avail_out = (uInt)Z_BUFSIZE;
+    zi->ci.stream.next_out = zi->ci.buffered_data;
+    zi->ci.stream.total_in = 0;
+    zi->ci.stream.total_out = 0;
+    zi->ci.stream.data_type = Z_BINARY;
+
+#ifdef HAVE_BZIP2
+    if ((err==ZIP_OK) && (zi->ci.method == Z_DEFLATED || zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw))
+#else
+    if ((err==ZIP_OK) && (zi->ci.method == Z_DEFLATED) && (!zi->ci.raw))
+#endif
+    {
+        if(zi->ci.method == Z_DEFLATED)
+        {
+          zi->ci.stream.zalloc = (alloc_func)0;
+          zi->ci.stream.zfree = (free_func)0;
+          zi->ci.stream.opaque = (voidpf)0;
+
+          if (windowBits>0)
+              windowBits = -windowBits;
+
+          err = deflateInit2(&zi->ci.stream, level, Z_DEFLATED, windowBits, memLevel, strategy);
+
+          if (err==Z_OK)
+              zi->ci.stream_initialised = Z_DEFLATED;
+        }
+        else if(zi->ci.method == Z_BZIP2ED)
+        {
+#ifdef HAVE_BZIP2
+            // Init BZip stuff here
+          zi->ci.bstream.bzalloc = 0;
+          zi->ci.bstream.bzfree = 0;
+          zi->ci.bstream.opaque = (voidpf)0;
+
+          err = BZ2_bzCompressInit(&zi->ci.bstream, level, 0,35);
+          if(err == BZ_OK)
+            zi->ci.stream_initialised = Z_BZIP2ED;
+#endif
+        }
+
+    }
+
+#    ifndef NOCRYPT
+    zi->ci.crypt_header_size = 0;
+    if ((err==Z_OK) && (password != NULL))
+    {
+        unsigned char bufHead[RAND_HEAD_LEN];
+        unsigned int sizeHead;
+        zi->ci.encrypt = 1;
+        zi->ci.pcrc_32_tab = get_crc_table();
+        /*init_keys(password,zi->ci.keys,zi->ci.pcrc_32_tab);*/
+
+        sizeHead=crypthead(password,bufHead,RAND_HEAD_LEN,zi->ci.keys,zi->ci.pcrc_32_tab,crcForCrypting);
+        zi->ci.crypt_header_size = sizeHead;
+
+        if (ZWRITE64(zi->z_filefunc,zi->filestream,bufHead,sizeHead) != sizeHead)
+                err = ZIP_ERRNO;
+    }
+#    endif
+
+    if (err==Z_OK)
+        zi->in_opened_file_inzip = 1;
+    return err;
+}
+
+extern int ZEXPORT zipOpenNewFileInZip4 (zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                         const void* extrafield_local, uInt size_extrafield_local,
+                                         const void* extrafield_global, uInt size_extrafield_global,
+                                         const char* comment, int method, int level, int raw,
+                                         int windowBits,int memLevel, int strategy,
+                                         const char* password, uLong crcForCrypting,
+                                         uLong versionMadeBy, uLong flagBase)
+{
+    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
+                                 extrafield_local, size_extrafield_local,
+                                 extrafield_global, size_extrafield_global,
+                                 comment, method, level, raw,
+                                 windowBits, memLevel, strategy,
+                                 password, crcForCrypting, versionMadeBy, flagBase, 0);
+}
+
+extern int ZEXPORT zipOpenNewFileInZip3 (zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                         const void* extrafield_local, uInt size_extrafield_local,
+                                         const void* extrafield_global, uInt size_extrafield_global,
+                                         const char* comment, int method, int level, int raw,
+                                         int windowBits,int memLevel, int strategy,
+                                         const char* password, uLong crcForCrypting)
+{
+    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
+                                 extrafield_local, size_extrafield_local,
+                                 extrafield_global, size_extrafield_global,
+                                 comment, method, level, raw,
+                                 windowBits, memLevel, strategy,
+                                 password, crcForCrypting, VERSIONMADEBY, 0, 0);
+}
+
+extern int ZEXPORT zipOpenNewFileInZip3_64(zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                         const void* extrafield_local, uInt size_extrafield_local,
+                                         const void* extrafield_global, uInt size_extrafield_global,
+                                         const char* comment, int method, int level, int raw,
+                                         int windowBits,int memLevel, int strategy,
+                                         const char* password, uLong crcForCrypting, int zip64)
+{
+    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
+                                 extrafield_local, size_extrafield_local,
+                                 extrafield_global, size_extrafield_global,
+                                 comment, method, level, raw,
+                                 windowBits, memLevel, strategy,
+                                 password, crcForCrypting, VERSIONMADEBY, 0, zip64);
+}
+
+extern int ZEXPORT zipOpenNewFileInZip2(zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                        const void* extrafield_local, uInt size_extrafield_local,
+                                        const void* extrafield_global, uInt size_extrafield_global,
+                                        const char* comment, int method, int level, int raw)
+{
+    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
+                                 extrafield_local, size_extrafield_local,
+                                 extrafield_global, size_extrafield_global,
+                                 comment, method, level, raw,
+                                 -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
+                                 NULL, 0, VERSIONMADEBY, 0, 0);
+}
+
+extern int ZEXPORT zipOpenNewFileInZip2_64(zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                        const void* extrafield_local, uInt size_extrafield_local,
+                                        const void* extrafield_global, uInt size_extrafield_global,
+                                        const char* comment, int method, int level, int raw, int zip64)
+{
+    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
+                                 extrafield_local, size_extrafield_local,
+                                 extrafield_global, size_extrafield_global,
+                                 comment, method, level, raw,
+                                 -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
+                                 NULL, 0, VERSIONMADEBY, 0, zip64);
+}
+
+extern int ZEXPORT zipOpenNewFileInZip64 (zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                        const void* extrafield_local, uInt size_extrafield_local,
+                                        const void*extrafield_global, uInt size_extrafield_global,
+                                        const char* comment, int method, int level, int zip64)
+{
+    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
+                                 extrafield_local, size_extrafield_local,
+                                 extrafield_global, size_extrafield_global,
+                                 comment, method, level, 0,
+                                 -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
+                                 NULL, 0, VERSIONMADEBY, 0, zip64);
+}
+
+extern int ZEXPORT zipOpenNewFileInZip (zipFile file, const char* filename, const zip_fileinfo* zipfi,
+                                        const void* extrafield_local, uInt size_extrafield_local,
+                                        const void*extrafield_global, uInt size_extrafield_global,
+                                        const char* comment, int method, int level)
+{
+    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
+                                 extrafield_local, size_extrafield_local,
+                                 extrafield_global, size_extrafield_global,
+                                 comment, method, level, 0,
+                                 -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
+                                 NULL, 0, VERSIONMADEBY, 0, 0);
+}
+
+local int zip64FlushWriteBuffer(zip64_internal* zi)
+{
+    int err=ZIP_OK;
+
+    if (zi->ci.encrypt != 0)
+    {
+#ifndef NOCRYPT
+        uInt i;
+        int t;
+        for (i=0;i<zi->ci.pos_in_buffered_data;i++)
+            zi->ci.buffered_data[i] = zencode(zi->ci.keys, zi->ci.pcrc_32_tab, zi->ci.buffered_data[i],t);
+#endif
+    }
+
+    if (ZWRITE64(zi->z_filefunc,zi->filestream,zi->ci.buffered_data,zi->ci.pos_in_buffered_data) != zi->ci.pos_in_buffered_data)
+      err = ZIP_ERRNO;
+
+    zi->ci.totalCompressedData += zi->ci.pos_in_buffered_data;
+
+#ifdef HAVE_BZIP2
+    if(zi->ci.method == Z_BZIP2ED)
+    {
+      zi->ci.totalUncompressedData += zi->ci.bstream.total_in_lo32;
+      zi->ci.bstream.total_in_lo32 = 0;
+      zi->ci.bstream.total_in_hi32 = 0;
+    }
+    else
+#endif
+    {
+      zi->ci.totalUncompressedData += zi->ci.stream.total_in;
+      zi->ci.stream.total_in = 0;
+    }
+
+
+    zi->ci.pos_in_buffered_data = 0;
+
+    return err;
+}
+
+extern int ZEXPORT zipWriteInFileInZip (zipFile file,const void* buf,unsigned int len)
+{
+    zip64_internal* zi;
+    int err=ZIP_OK;
+
+    if (file == NULL)
+        return ZIP_PARAMERROR;
+    zi = (zip64_internal*)file;
+
+    if (zi->in_opened_file_inzip == 0)
+        return ZIP_PARAMERROR;
+
+    zi->ci.crc32 = crc32(zi->ci.crc32,buf,(uInt)len);
+
+#ifdef HAVE_BZIP2
+    if(zi->ci.method == Z_BZIP2ED && (!zi->ci.raw))
+    {
+      zi->ci.bstream.next_in = (void*)buf;
+      zi->ci.bstream.avail_in = len;
+      err = BZ_RUN_OK;
+
+      while ((err==BZ_RUN_OK) && (zi->ci.bstream.avail_in>0))
+      {
+        if (zi->ci.bstream.avail_out == 0)
+        {
+          if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO)
+            err = ZIP_ERRNO;
+          zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE;
+          zi->ci.bstream.next_out = (char*)zi->ci.buffered_data;
+        }
+
+
+        if(err != BZ_RUN_OK)
+          break;
+
+        if ((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw))
+        {
+          uLong uTotalOutBefore_lo = zi->ci.bstream.total_out_lo32;
+//          uLong uTotalOutBefore_hi = zi->ci.bstream.total_out_hi32;
+          err=BZ2_bzCompress(&zi->ci.bstream,  BZ_RUN);
+
+          zi->ci.pos_in_buffered_data += (uInt)(zi->ci.bstream.total_out_lo32 - uTotalOutBefore_lo) ;
+        }
+      }
+
+      if(err == BZ_RUN_OK)
+        err = ZIP_OK;
+    }
+    else
+#endif
+    {
+      zi->ci.stream.next_in = (Bytef*)buf;
+      zi->ci.stream.avail_in = len;
+
+      while ((err==ZIP_OK) && (zi->ci.stream.avail_in>0))
+      {
+          if (zi->ci.stream.avail_out == 0)
+          {
+              if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO)
+                  err = ZIP_ERRNO;
+              zi->ci.stream.avail_out = (uInt)Z_BUFSIZE;
+              zi->ci.stream.next_out = zi->ci.buffered_data;
+          }
+
+
+          if(err != ZIP_OK)
+              break;
+
+          if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw))
+          {
+              uLong uTotalOutBefore = zi->ci.stream.total_out;
+              err=deflate(&zi->ci.stream,  Z_NO_FLUSH);
+              if(uTotalOutBefore > zi->ci.stream.total_out)
+              {
+                int bBreak = 0;
+                bBreak++;
+              }
+
+              zi->ci.pos_in_buffered_data += (uInt)(zi->ci.stream.total_out - uTotalOutBefore) ;
+          }
+          else
+          {
+              uInt copy_this,i;
+              if (zi->ci.stream.avail_in < zi->ci.stream.avail_out)
+                  copy_this = zi->ci.stream.avail_in;
+              else
+                  copy_this = zi->ci.stream.avail_out;
+
+              for (i = 0; i < copy_this; i++)
+                  *(((char*)zi->ci.stream.next_out)+i) =
+                      *(((const char*)zi->ci.stream.next_in)+i);
+              {
+                  zi->ci.stream.avail_in -= copy_this;
+                  zi->ci.stream.avail_out-= copy_this;
+                  zi->ci.stream.next_in+= copy_this;
+                  zi->ci.stream.next_out+= copy_this;
+                  zi->ci.stream.total_in+= copy_this;
+                  zi->ci.stream.total_out+= copy_this;
+                  zi->ci.pos_in_buffered_data += copy_this;
+              }
+          }
+      }// while(...)
+    }
+
+    return err;
+}
+
+extern int ZEXPORT zipCloseFileInZipRaw (zipFile file, uLong uncompressed_size, uLong crc32)
+{
+    return zipCloseFileInZipRaw64 (file, uncompressed_size, crc32);
+}
+
+extern int ZEXPORT zipCloseFileInZipRaw64 (zipFile file, ZPOS64_T uncompressed_size, uLong crc32)
+{
+    zip64_internal* zi;
+    ZPOS64_T compressed_size;
+    uLong invalidValue = 0xffffffff;
+    short datasize = 0;
+    int err=ZIP_OK;
+
+    if (file == NULL)
+        return ZIP_PARAMERROR;
+    zi = (zip64_internal*)file;
+
+    if (zi->in_opened_file_inzip == 0)
+        return ZIP_PARAMERROR;
+    zi->ci.stream.avail_in = 0;
+
+    if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw))
+                {
+                        while (err==ZIP_OK)
+                        {
+                                uLong uTotalOutBefore;
+                                if (zi->ci.stream.avail_out == 0)
+                                {
+                                        if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO)
+                                                err = ZIP_ERRNO;
+                                        zi->ci.stream.avail_out = (uInt)Z_BUFSIZE;
+                                        zi->ci.stream.next_out = zi->ci.buffered_data;
+                                }
+                                uTotalOutBefore = zi->ci.stream.total_out;
+                                err=deflate(&zi->ci.stream,  Z_FINISH);
+                                zi->ci.pos_in_buffered_data += (uInt)(zi->ci.stream.total_out - uTotalOutBefore) ;
+                        }
+                }
+    else if ((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw))
+    {
+#ifdef HAVE_BZIP2
+      err = BZ_FINISH_OK;
+      while (err==BZ_FINISH_OK)
+      {
+        uLong uTotalOutBefore;
+        if (zi->ci.bstream.avail_out == 0)
+        {
+          if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO)
+            err = ZIP_ERRNO;
+          zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE;
+          zi->ci.bstream.next_out = (char*)zi->ci.buffered_data;
+        }
+        uTotalOutBefore = zi->ci.bstream.total_out_lo32;
+        err=BZ2_bzCompress(&zi->ci.bstream,  BZ_FINISH);
+        if(err == BZ_STREAM_END)
+          err = Z_STREAM_END;
+
+        zi->ci.pos_in_buffered_data += (uInt)(zi->ci.bstream.total_out_lo32 - uTotalOutBefore);
+      }
+
+      if(err == BZ_FINISH_OK)
+        err = ZIP_OK;
+#endif
+    }
+
+    if (err==Z_STREAM_END)
+        err=ZIP_OK; /* this is normal */
+
+    if ((zi->ci.pos_in_buffered_data>0) && (err==ZIP_OK))
+                {
+        if (zip64FlushWriteBuffer(zi)==ZIP_ERRNO)
+            err = ZIP_ERRNO;
+                }
+
+    if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw))
+    {
+        int tmp_err = deflateEnd(&zi->ci.stream);
+        if (err == ZIP_OK)
+            err = tmp_err;
+        zi->ci.stream_initialised = 0;
+    }
+#ifdef HAVE_BZIP2
+    else if((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw))
+    {
+      int tmperr = BZ2_bzCompressEnd(&zi->ci.bstream);
+                        if (err==ZIP_OK)
+                                err = tmperr;
+                        zi->ci.stream_initialised = 0;
+    }
+#endif
+
+    if (!zi->ci.raw)
+    {
+        crc32 = (uLong)zi->ci.crc32;
+        uncompressed_size = zi->ci.totalUncompressedData;
+    }
+    compressed_size = zi->ci.totalCompressedData;
+
+#    ifndef NOCRYPT
+    compressed_size += zi->ci.crypt_header_size;
+#    endif
+
+    // update Current Item crc and sizes,
+    if(compressed_size >= 0xffffffff || uncompressed_size >= 0xffffffff || zi->ci.pos_local_header >= 0xffffffff)
+    {
+      /*version Made by*/
+      zip64local_putValue_inmemory(zi->ci.central_header+4,(uLong)45,2);
+      /*version needed*/
+      zip64local_putValue_inmemory(zi->ci.central_header+6,(uLong)45,2);
+
+    }
+
+    zip64local_putValue_inmemory(zi->ci.central_header+16,crc32,4); /*crc*/
+
+
+    if(compressed_size >= 0xffffffff)
+      zip64local_putValue_inmemory(zi->ci.central_header+20, invalidValue,4); /*compr size*/
+    else
+      zip64local_putValue_inmemory(zi->ci.central_header+20, compressed_size,4); /*compr size*/
+
+    /// set internal file attributes field
+    if (zi->ci.stream.data_type == Z_ASCII)
+        zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)Z_ASCII,2);
+
+    if(uncompressed_size >= 0xffffffff)
+      zip64local_putValue_inmemory(zi->ci.central_header+24, invalidValue,4); /*uncompr size*/
+    else
+      zip64local_putValue_inmemory(zi->ci.central_header+24, uncompressed_size,4); /*uncompr size*/
+
+    // Add ZIP64 extra info field for uncompressed size
+    if(uncompressed_size >= 0xffffffff)
+      datasize += 8;
+
+    // Add ZIP64 extra info field for compressed size
+    if(compressed_size >= 0xffffffff)
+      datasize += 8;
+
+    // Add ZIP64 extra info field for relative offset to local file header of current file
+    if(zi->ci.pos_local_header >= 0xffffffff)
+      datasize += 8;
+
+    if(datasize > 0)
+    {
+      char* p = NULL;
+
+      if((uLong)(datasize + 4) > zi->ci.size_centralExtraFree)
+      {
+        // we can not write more data to the buffer that we have room for.
+        return ZIP_BADZIPFILE;
+      }
+
+      p = zi->ci.central_header + zi->ci.size_centralheader;
+
+      // Add Extra Information Header for 'ZIP64 information'
+      zip64local_putValue_inmemory(p, 0x0001, 2); // HeaderID
+      p += 2;
+      zip64local_putValue_inmemory(p, datasize, 2); // DataSize
+      p += 2;
+
+      if(uncompressed_size >= 0xffffffff)
+      {
+        zip64local_putValue_inmemory(p, uncompressed_size, 8);
+        p += 8;
+      }
+
+      if(compressed_size >= 0xffffffff)
+      {
+        zip64local_putValue_inmemory(p, compressed_size, 8);
+        p += 8;
+      }
+
+      if(zi->ci.pos_local_header >= 0xffffffff)
+      {
+        zip64local_putValue_inmemory(p, zi->ci.pos_local_header, 8);
+        p += 8;
+      }
+
+      // Update how much extra free space we got in the memory buffer
+      // and increase the centralheader size so the new ZIP64 fields are included
+      // ( 4 below is the size of HeaderID and DataSize field )
+      zi->ci.size_centralExtraFree -= datasize + 4;
+      zi->ci.size_centralheader += datasize + 4;
+
+      // Update the extra info size field
+      zi->ci.size_centralExtra += datasize + 4;
+      zip64local_putValue_inmemory(zi->ci.central_header+30,(uLong)zi->ci.size_centralExtra,2);
+    }
+
+    if (err==ZIP_OK)
+        err = add_data_in_datablock(&zi->central_dir, zi->ci.central_header, (uLong)zi->ci.size_centralheader);
+
+    free(zi->ci.central_header);
+
+    if (err==ZIP_OK)
+    {
+        // Update the LocalFileHeader with the new values.
+
+        ZPOS64_T cur_pos_inzip = ZTELL64(zi->z_filefunc,zi->filestream);
+
+        if (ZSEEK64(zi->z_filefunc,zi->filestream, zi->ci.pos_local_header + 14,ZLIB_FILEFUNC_SEEK_SET)!=0)
+            err = ZIP_ERRNO;
+
+        if (err==ZIP_OK)
+            err = zip64local_putValue(&zi->z_filefunc,zi->filestream,crc32,4); /* crc 32, unknown */
+
+        if(uncompressed_size >= 0xffffffff || compressed_size >= 0xffffffff )
+        {
+          if(zi->ci.pos_zip64extrainfo > 0)
+          {
+            // Update the size in the ZIP64 extended field.
+            if (ZSEEK64(zi->z_filefunc,zi->filestream, zi->ci.pos_zip64extrainfo + 4,ZLIB_FILEFUNC_SEEK_SET)!=0)
+              err = ZIP_ERRNO;
+
+            if (err==ZIP_OK) /* compressed size, unknown */
+              err = zip64local_putValue(&zi->z_filefunc, zi->filestream, uncompressed_size, 8);
+
+            if (err==ZIP_OK) /* uncompressed size, unknown */
+              err = zip64local_putValue(&zi->z_filefunc, zi->filestream, compressed_size, 8);
+          }
+          else
+              err = ZIP_BADZIPFILE; // Caller passed zip64 = 0, so no room for zip64 info -> fatal
+        }
+        else
+        {
+          if (err==ZIP_OK) /* compressed size, unknown */
+              err = zip64local_putValue(&zi->z_filefunc,zi->filestream,compressed_size,4);
+
+          if (err==ZIP_OK) /* uncompressed size, unknown */
+              err = zip64local_putValue(&zi->z_filefunc,zi->filestream,uncompressed_size,4);
+        }
+
+        if (ZSEEK64(zi->z_filefunc,zi->filestream, cur_pos_inzip,ZLIB_FILEFUNC_SEEK_SET)!=0)
+            err = ZIP_ERRNO;
+    }
+
+    zi->number_entry ++;
+    zi->in_opened_file_inzip = 0;
+
+    return err;
+}
+
+extern int ZEXPORT zipCloseFileInZip (zipFile file)
+{
+    return zipCloseFileInZipRaw (file,0,0);
+}
+
+int Write_Zip64EndOfCentralDirectoryLocator(zip64_internal* zi, ZPOS64_T zip64eocd_pos_inzip)
+{
+  int err = ZIP_OK;
+  ZPOS64_T pos = zip64eocd_pos_inzip - zi->add_position_when_writting_offset;
+
+  err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ZIP64ENDLOCHEADERMAGIC,4);
+
+  /*num disks*/
+    if (err==ZIP_OK) /* number of the disk with the start of the central directory */
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4);
+
+  /*relative offset*/
+    if (err==ZIP_OK) /* Relative offset to the Zip64EndOfCentralDirectory */
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream, pos,8);
+
+  /*total disks*/ /* Do not support spawning of disk so always say 1 here*/
+    if (err==ZIP_OK) /* number of the disk with the start of the central directory */
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)1,4);
+
+    return err;
+}
+
+int Write_Zip64EndOfCentralDirectoryRecord(zip64_internal* zi, uLong size_centraldir, ZPOS64_T centraldir_pos_inzip)
+{
+  int err = ZIP_OK;
+
+  uLong Zip64DataSize = 44;
+
+  err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ZIP64ENDHEADERMAGIC,4);
+
+  if (err==ZIP_OK) /* size of this 'zip64 end of central directory' */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(ZPOS64_T)Zip64DataSize,8); // why ZPOS64_T of this ?
+
+  if (err==ZIP_OK) /* version made by */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2);
+
+  if (err==ZIP_OK) /* version needed */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2);
+
+  if (err==ZIP_OK) /* number of this disk */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4);
+
+  if (err==ZIP_OK) /* number of the disk with the start of the central directory */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4);
+
+  if (err==ZIP_OK) /* total number of entries in the central dir on this disk */
+    err = zip64local_putValue(&zi->z_filefunc, zi->filestream, zi->number_entry, 8);
+
+  if (err==ZIP_OK) /* total number of entries in the central dir */
+    err = zip64local_putValue(&zi->z_filefunc, zi->filestream, zi->number_entry, 8);
+
+  if (err==ZIP_OK) /* size of the central directory */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(ZPOS64_T)size_centraldir,8);
+
+  if (err==ZIP_OK) /* offset of start of central directory with respect to the starting disk number */
+  {
+    ZPOS64_T pos = centraldir_pos_inzip - zi->add_position_when_writting_offset;
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (ZPOS64_T)pos,8);
+  }
+  return err;
+}
+int Write_EndOfCentralDirectoryRecord(zip64_internal* zi, uLong size_centraldir, ZPOS64_T centraldir_pos_inzip)
+{
+  int err = ZIP_OK;
+
+  /*signature*/
+  err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ENDHEADERMAGIC,4);
+
+  if (err==ZIP_OK) /* number of this disk */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,2);
+
+  if (err==ZIP_OK) /* number of the disk with the start of the central directory */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,2);
+
+  if (err==ZIP_OK) /* total number of entries in the central dir on this disk */
+  {
+    {
+      if(zi->number_entry >= 0xFFFF)
+        err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xffff,2); // use value in ZIP64 record
+      else
+        err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->number_entry,2);
+    }
+  }
+
+  if (err==ZIP_OK) /* total number of entries in the central dir */
+  {
+    if(zi->number_entry >= 0xFFFF)
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xffff,2); // use value in ZIP64 record
+    else
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->number_entry,2);
+  }
+
+  if (err==ZIP_OK) /* size of the central directory */
+    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_centraldir,4);
+
+  if (err==ZIP_OK) /* offset of start of central directory with respect to the starting disk number */
+  {
+    ZPOS64_T pos = centraldir_pos_inzip - zi->add_position_when_writting_offset;
+    if(pos >= 0xffffffff)
+    {
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (uLong)0xffffffff,4);
+    }
+    else
+      err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (uLong)(centraldir_pos_inzip - zi->add_position_when_writting_offset),4);
+  }
+
+   return err;
+}
+
+int Write_GlobalComment(zip64_internal* zi, const char* global_comment)
+{
+  int err = ZIP_OK;
+  uInt size_global_comment = 0;
+
+  if(global_comment != NULL)
+    size_global_comment = (uInt)strlen(global_comment);
+
+  err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_global_comment,2);
+
+  if (err == ZIP_OK && size_global_comment > 0)
+  {
+    if (ZWRITE64(zi->z_filefunc,zi->filestream, global_comment, size_global_comment) != size_global_comment)
+      err = ZIP_ERRNO;
+  }
+  return err;
+}
+
+extern int ZEXPORT zipClose (zipFile file, const char* global_comment)
+{
+    zip64_internal* zi;
+    int err = 0;
+    uLong size_centraldir = 0;
+    ZPOS64_T centraldir_pos_inzip;
+    ZPOS64_T pos;
+
+    if (file == NULL)
+        return ZIP_PARAMERROR;
+
+    zi = (zip64_internal*)file;
+
+    if (zi->in_opened_file_inzip == 1)
+    {
+        err = zipCloseFileInZip (file);
+    }
+
+#ifndef NO_ADDFILEINEXISTINGZIP
+    if (global_comment==NULL)
+        global_comment = zi->globalcomment;
+#endif
+
+    centraldir_pos_inzip = ZTELL64(zi->z_filefunc,zi->filestream);
+
+    if (err==ZIP_OK)
+    {
+        linkedlist_datablock_internal* ldi = zi->central_dir.first_block;
+        while (ldi!=NULL)
+        {
+            if ((err==ZIP_OK) && (ldi->filled_in_this_block>0))
+            {
+                if (ZWRITE64(zi->z_filefunc,zi->filestream, ldi->data, ldi->filled_in_this_block) != ldi->filled_in_this_block)
+                    err = ZIP_ERRNO;
+            }
+
+            size_centraldir += ldi->filled_in_this_block;
+            ldi = ldi->next_datablock;
+        }
+    }
+    free_linkedlist(&(zi->central_dir));
+
+    pos = centraldir_pos_inzip - zi->add_position_when_writting_offset;
+    if(pos >= 0xffffffff || zi->number_entry > 0xFFFF)
+    {
+      ZPOS64_T Zip64EOCDpos = ZTELL64(zi->z_filefunc,zi->filestream);
+      Write_Zip64EndOfCentralDirectoryRecord(zi, size_centraldir, centraldir_pos_inzip);
+
+      Write_Zip64EndOfCentralDirectoryLocator(zi, Zip64EOCDpos);
+    }
+
+    if (err==ZIP_OK)
+      err = Write_EndOfCentralDirectoryRecord(zi, size_centraldir, centraldir_pos_inzip);
+
+    if(err == ZIP_OK)
+      err = Write_GlobalComment(zi, global_comment);
+
+    if (ZCLOSE64(zi->z_filefunc,zi->filestream) != 0)
+        if (err == ZIP_OK)
+            err = ZIP_ERRNO;
+
+#ifndef NO_ADDFILEINEXISTINGZIP
+    TRYFREE(zi->globalcomment);
+#endif
+    TRYFREE(zi);
+
+    return err;
+}
+
+extern int ZEXPORT zipRemoveExtraInfoBlock (char* pData, int* dataLen, short sHeader)
+{
+  char* p = pData;
+  int size = 0;
+  char* pNewHeader;
+  char* pTmp;
+  short header;
+  short dataSize;
+
+  int retVal = ZIP_OK;
+
+  if(pData == NULL || *dataLen < 4)
+    return ZIP_PARAMERROR;
+
+  pNewHeader = (char*)ALLOC(*dataLen);
+  pTmp = pNewHeader;
+
+  while(p < (pData + *dataLen))
+  {
+    header = *(short*)p;
+    dataSize = *(((short*)p)+1);
+
+    if( header == sHeader ) // Header found.
+    {
+      p += dataSize + 4; // skip it. do not copy to temp buffer
+    }
+    else
+    {
+      // Extra Info block should not be removed, So copy it to the temp buffer.
+      memcpy(pTmp, p, dataSize + 4);
+      p += dataSize + 4;
+      size += dataSize + 4;
+    }
+
+  }
+
+  if(size < *dataLen)
+  {
+    // clean old extra info block.
+    memset(pData,0, *dataLen);
+
+    // copy the new extra info block over the old
+    if(size > 0)
+      memcpy(pData, pNewHeader, size);
+
+    // set the new extra info size
+    *dataLen = size;
+
+    retVal = ZIP_OK;
+  }
+  else
+    retVal = ZIP_ERRNO;
+
+  TRYFREE(pNewHeader);
+
+  return retVal;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/ThirdParty/minizip/zip.h	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,362 @@
+/* zip.h -- IO on .zip files using zlib
+   Version 1.1, February 14h, 2010
+   part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
+
+         Modifications for Zip64 support
+         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
+
+         For more info read MiniZip_info.txt
+
+         ---------------------------------------------------------------------------
+
+   Condition of use and distribution are the same than zlib :
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+        ---------------------------------------------------------------------------
+
+        Changes
+
+        See header of zip.h
+
+*/
+
+#ifndef _zip12_H
+#define _zip12_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//#define HAVE_BZIP2
+
+#ifndef _ZLIB_H
+#include "zlib.h"
+#endif
+
+#ifndef _ZLIBIOAPI_H
+#include "ioapi.h"
+#endif
+
+#ifdef HAVE_BZIP2
+#include "bzlib.h"
+#endif
+
+#define Z_BZIP2ED 12
+
+#if defined(STRICTZIP) || defined(STRICTZIPUNZIP)
+/* like the STRICT of WIN32, we define a pointer that cannot be converted
+    from (void*) without cast */
+typedef struct TagzipFile__ { int unused; } zipFile__;
+typedef zipFile__ *zipFile;
+#else
+typedef voidp zipFile;
+#endif
+
+#define ZIP_OK                          (0)
+#define ZIP_EOF                         (0)
+#define ZIP_ERRNO                       (Z_ERRNO)
+#define ZIP_PARAMERROR                  (-102)
+#define ZIP_BADZIPFILE                  (-103)
+#define ZIP_INTERNALERROR               (-104)
+
+#ifndef DEF_MEM_LEVEL
+#  if MAX_MEM_LEVEL >= 8
+#    define DEF_MEM_LEVEL 8
+#  else
+#    define DEF_MEM_LEVEL  MAX_MEM_LEVEL
+#  endif
+#endif
+/* default memLevel */
+
+/* tm_zip contain date/time info */
+typedef struct tm_zip_s
+{
+    uInt tm_sec;            /* seconds after the minute - [0,59] */
+    uInt tm_min;            /* minutes after the hour - [0,59] */
+    uInt tm_hour;           /* hours since midnight - [0,23] */
+    uInt tm_mday;           /* day of the month - [1,31] */
+    uInt tm_mon;            /* months since January - [0,11] */
+    uInt tm_year;           /* years - [1980..2044] */
+} tm_zip;
+
+typedef struct
+{
+    tm_zip      tmz_date;       /* date in understandable format           */
+    uLong       dosDate;       /* if dos_date == 0, tmu_date is used      */
+/*    uLong       flag;        */   /* general purpose bit flag        2 bytes */
+
+    uLong       internal_fa;    /* internal file attributes        2 bytes */
+    uLong       external_fa;    /* external file attributes        4 bytes */
+} zip_fileinfo;
+
+typedef const char* zipcharpc;
+
+
+#define APPEND_STATUS_CREATE        (0)
+#define APPEND_STATUS_CREATEAFTER   (1)
+#define APPEND_STATUS_ADDINZIP      (2)
+
+extern zipFile ZEXPORT zipOpen OF((const char *pathname, int append));
+extern zipFile ZEXPORT zipOpen64 OF((const void *pathname, int append));
+/*
+  Create a zipfile.
+     pathname contain on Windows XP a filename like "c:\\zlib\\zlib113.zip" or on
+       an Unix computer "zlib/zlib113.zip".
+     if the file pathname exist and append==APPEND_STATUS_CREATEAFTER, the zip
+       will be created at the end of the file.
+         (useful if the file contain a self extractor code)
+     if the file pathname exist and append==APPEND_STATUS_ADDINZIP, we will
+       add files in existing zip (be sure you don't add file that doesn't exist)
+     If the zipfile cannot be opened, the return value is NULL.
+     Else, the return value is a zipFile Handle, usable with other function
+       of this zip package.
+*/
+
+/* Note : there is no delete function into a zipfile.
+   If you want delete file into a zipfile, you must open a zipfile, and create another
+   Of couse, you can use RAW reading and writing to copy the file you did not want delte
+*/
+
+extern zipFile ZEXPORT zipOpen2 OF((const char *pathname,
+                                   int append,
+                                   zipcharpc* globalcomment,
+                                   zlib_filefunc_def* pzlib_filefunc_def));
+
+extern zipFile ZEXPORT zipOpen2_64 OF((const void *pathname,
+                                   int append,
+                                   zipcharpc* globalcomment,
+                                   zlib_filefunc64_def* pzlib_filefunc_def));
+
+extern int ZEXPORT zipOpenNewFileInZip OF((zipFile file,
+                       const char* filename,
+                       const zip_fileinfo* zipfi,
+                       const void* extrafield_local,
+                       uInt size_extrafield_local,
+                       const void* extrafield_global,
+                       uInt size_extrafield_global,
+                       const char* comment,
+                       int method,
+                       int level));
+
+extern int ZEXPORT zipOpenNewFileInZip64 OF((zipFile file,
+                       const char* filename,
+                       const zip_fileinfo* zipfi,
+                       const void* extrafield_local,
+                       uInt size_extrafield_local,
+                       const void* extrafield_global,
+                       uInt size_extrafield_global,
+                       const char* comment,
+                       int method,
+                       int level,
+                       int zip64));
+
+/*
+  Open a file in the ZIP for writing.
+  filename : the filename in zip (if NULL, '-' without quote will be used
+  *zipfi contain supplemental information
+  if extrafield_local!=NULL and size_extrafield_local>0, extrafield_local
+    contains the extrafield data the the local header
+  if extrafield_global!=NULL and size_extrafield_global>0, extrafield_global
+    contains the extrafield data the the local header
+  if comment != NULL, comment contain the comment string
+  method contain the compression method (0 for store, Z_DEFLATED for deflate)
+  level contain the level of compression (can be Z_DEFAULT_COMPRESSION)
+  zip64 is set to 1 if a zip64 extended information block should be added to the local file header.
+                    this MUST be '1' if the uncompressed size is >= 0xffffffff.
+
+*/
+
+
+extern int ZEXPORT zipOpenNewFileInZip2 OF((zipFile file,
+                                            const char* filename,
+                                            const zip_fileinfo* zipfi,
+                                            const void* extrafield_local,
+                                            uInt size_extrafield_local,
+                                            const void* extrafield_global,
+                                            uInt size_extrafield_global,
+                                            const char* comment,
+                                            int method,
+                                            int level,
+                                            int raw));
+
+
+extern int ZEXPORT zipOpenNewFileInZip2_64 OF((zipFile file,
+                                            const char* filename,
+                                            const zip_fileinfo* zipfi,
+                                            const void* extrafield_local,
+                                            uInt size_extrafield_local,
+                                            const void* extrafield_global,
+                                            uInt size_extrafield_global,
+                                            const char* comment,
+                                            int method,
+                                            int level,
+                                            int raw,
+                                            int zip64));
+/*
+  Same than zipOpenNewFileInZip, except if raw=1, we write raw file
+ */
+
+extern int ZEXPORT zipOpenNewFileInZip3 OF((zipFile file,
+                                            const char* filename,
+                                            const zip_fileinfo* zipfi,
+                                            const void* extrafield_local,
+                                            uInt size_extrafield_local,
+                                            const void* extrafield_global,
+                                            uInt size_extrafield_global,
+                                            const char* comment,
+                                            int method,
+                                            int level,
+                                            int raw,
+                                            int windowBits,
+                                            int memLevel,
+                                            int strategy,
+                                            const char* password,
+                                            uLong crcForCrypting));
+
+extern int ZEXPORT zipOpenNewFileInZip3_64 OF((zipFile file,
+                                            const char* filename,
+                                            const zip_fileinfo* zipfi,
+                                            const void* extrafield_local,
+                                            uInt size_extrafield_local,
+                                            const void* extrafield_global,
+                                            uInt size_extrafield_global,
+                                            const char* comment,
+                                            int method,
+                                            int level,
+                                            int raw,
+                                            int windowBits,
+                                            int memLevel,
+                                            int strategy,
+                                            const char* password,
+                                            uLong crcForCrypting,
+                                            int zip64
+                                            ));
+
+/*
+  Same than zipOpenNewFileInZip2, except
+    windowBits,memLevel,,strategy : see parameter strategy in deflateInit2
+    password : crypting password (NULL for no crypting)
+    crcForCrypting : crc of file to compress (needed for crypting)
+ */
+
+extern int ZEXPORT zipOpenNewFileInZip4 OF((zipFile file,
+                                            const char* filename,
+                                            const zip_fileinfo* zipfi,
+                                            const void* extrafield_local,
+                                            uInt size_extrafield_local,
+                                            const void* extrafield_global,
+                                            uInt size_extrafield_global,
+                                            const char* comment,
+                                            int method,
+                                            int level,
+                                            int raw,
+                                            int windowBits,
+                                            int memLevel,
+                                            int strategy,
+                                            const char* password,
+                                            uLong crcForCrypting,
+                                            uLong versionMadeBy,
+                                            uLong flagBase
+                                            ));
+
+
+extern int ZEXPORT zipOpenNewFileInZip4_64 OF((zipFile file,
+                                            const char* filename,
+                                            const zip_fileinfo* zipfi,
+                                            const void* extrafield_local,
+                                            uInt size_extrafield_local,
+                                            const void* extrafield_global,
+                                            uInt size_extrafield_global,
+                                            const char* comment,
+                                            int method,
+                                            int level,
+                                            int raw,
+                                            int windowBits,
+                                            int memLevel,
+                                            int strategy,
+                                            const char* password,
+                                            uLong crcForCrypting,
+                                            uLong versionMadeBy,
+                                            uLong flagBase,
+                                            int zip64
+                                            ));
+/*
+  Same than zipOpenNewFileInZip4, except
+    versionMadeBy : value for Version made by field
+    flag : value for flag field (compression level info will be added)
+ */
+
+
+extern int ZEXPORT zipWriteInFileInZip OF((zipFile file,
+                       const void* buf,
+                       unsigned len));
+/*
+  Write data in the zipfile
+*/
+
+extern int ZEXPORT zipCloseFileInZip OF((zipFile file));
+/*
+  Close the current file in the zipfile
+*/
+
+extern int ZEXPORT zipCloseFileInZipRaw OF((zipFile file,
+                                            uLong uncompressed_size,
+                                            uLong crc32));
+
+extern int ZEXPORT zipCloseFileInZipRaw64 OF((zipFile file,
+                                            ZPOS64_T uncompressed_size,
+                                            uLong crc32));
+
+/*
+  Close the current file in the zipfile, for file opened with
+    parameter raw=1 in zipOpenNewFileInZip2
+  uncompressed_size and crc32 are value for the uncompressed size
+*/
+
+extern int ZEXPORT zipClose OF((zipFile file,
+                const char* global_comment));
+/*
+  Close the zipfile
+*/
+
+
+extern int ZEXPORT zipRemoveExtraInfoBlock OF((char* pData, int* dataLen, short sHeader));
+/*
+  zipRemoveExtraInfoBlock -  Added by Mathias Svensson
+
+  Remove extra information block from a extra information data for the local file header or central directory header
+
+  It is needed to remove ZIP64 extra information blocks when before data is written if using RAW mode.
+
+  0x0001 is the signature header for the ZIP64 extra information blocks
+
+  usage.
+                        Remove ZIP64 Extra information from a central director extra field data
+              zipRemoveExtraInfoBlock(pCenDirExtraFieldData, &nCenDirExtraFieldDataLen, 0x0001);
+
+                        Remove ZIP64 Extra information from a Local File Header extra field data
+        zipRemoveExtraInfoBlock(pLocalHeaderExtraFieldData, &nLocalHeaderExtraFieldDataLen, 0x0001);
+*/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _zip64_H */
--- a/Resources/Toolbox.lua	Wed Jun 25 11:56:48 2014 +0200
+++ b/Resources/Toolbox.lua	Thu Jul 10 11:42:32 2014 +0200
@@ -6,7 +6,8 @@
 --]]
 
 function PrintRecursive(s, l, i) -- recursive Print (structure, limit, indent)
-   l = (l) or 100; i = i or "";	-- default item limit, indent string
+   l = (l) or 100;  -- default item limit
+   i = i or "";     -- indent string
    if (l<1) then print "ERROR: Item limit reached."; return l-1 end;
    local ts = type(s);
    if (ts ~= "table") then print (i,ts,s); return l-1 end
@@ -18,4 +19,79 @@
    return l
 end	
 
+
+
+
+function _InitializeJob()
+   _job = {}
+end
+
+
+function _AccessJob()
+   return _job
+end
+
+
+function SendToModality(instanceId, modality)
+   if instanceId == nil then
+      error('Cannot send a nonexistent instance')
+   end
+
+   table.insert(_job, { 
+                   Operation = 'store-scu', 
+                   Instance = instanceId,
+                   Modality = modality 
+                })
+   return instanceId
+end
+
+
+function SendToPeer(instanceId, peer)
+   if instanceId == nil then
+      error('Cannot send a nonexistent instance')
+   end
+
+   table.insert(_job, { 
+                   Operation = 'store-peer', 
+                   Instance = instanceId,
+                   Peer = peer
+                })
+   return instanceId
+end
+
+
+function Delete(instanceId)
+   if instanceId == nil then
+      error('Cannot delete a nonexistent instance')
+   end
+
+   table.insert(_job, { 
+                   Operation = 'delete', 
+                   Instance = instanceId
+                })
+   return nil  -- Forbid chaining
+end
+
+
+function ModifyInstance(instanceId, replacements, removals, removePrivateTags)
+   if instanceId == nil then
+      error('Cannot modify a nonexistent instance')
+   end
+
+   if instanceId == '' then
+      error('Cannot modify twice an instance');
+   end
+
+   table.insert(_job, { 
+                   Operation = 'modify', 
+                   Instance = instanceId,
+                   Replace = replacements, 
+                   Remove = removals,
+                   RemovePrivateTags = removePrivateTags 
+                })
+   return ''  -- Chain with another operation
+end
+
+
+
 print('Lua toolbox installed')
--- a/Resources/VisualStudio/stdint.h	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,247 +0,0 @@
-// ISO C9x  compliant stdint.h for Microsoft Visual Studio
-// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 
-// 
-//  Copyright (c) 2006-2008 Alexander Chemeris
-// 
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-// 
-//   1. Redistributions of source code must retain the above copyright notice,
-//      this list of conditions and the following disclaimer.
-// 
-//   2. Redistributions in binary form must reproduce the above copyright
-//      notice, this list of conditions and the following disclaimer in the
-//      documentation and/or other materials provided with the distribution.
-// 
-//   3. The name of the author may be used to endorse or promote products
-//      derived from this software without specific prior written permission.
-// 
-// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
-// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
-// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
-// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
-// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-// 
-///////////////////////////////////////////////////////////////////////////////
-
-#ifndef _MSC_VER // [
-#error "Use this header only with Microsoft Visual C++ compilers!"
-#endif // _MSC_VER ]
-
-#ifndef _MSC_STDINT_H_ // [
-#define _MSC_STDINT_H_
-
-#if _MSC_VER > 1000
-#pragma once
-#endif
-
-#include <limits.h>
-
-// For Visual Studio 6 in C++ mode and for many Visual Studio versions when
-// compiling for ARM we should wrap <wchar.h> include with 'extern "C++" {}'
-// or compiler give many errors like this:
-//   error C2733: second C linkage of overloaded function 'wmemchr' not allowed
-#ifdef __cplusplus
-extern "C" {
-#endif
-#  include <wchar.h>
-#ifdef __cplusplus
-}
-#endif
-
-// Define _W64 macros to mark types changing their size, like intptr_t.
-#ifndef _W64
-#  if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300
-#     define _W64 __w64
-#  else
-#     define _W64
-#  endif
-#endif
-
-
-// 7.18.1 Integer types
-
-// 7.18.1.1 Exact-width integer types
-
-// Visual Studio 6 and Embedded Visual C++ 4 doesn't
-// realize that, e.g. char has the same size as __int8
-// so we give up on __intX for them.
-#if (_MSC_VER < 1300)
-   typedef signed char       int8_t;
-   typedef signed short      int16_t;
-   typedef signed int        int32_t;
-   typedef unsigned char     uint8_t;
-   typedef unsigned short    uint16_t;
-   typedef unsigned int      uint32_t;
-#else
-   typedef signed __int8     int8_t;
-   typedef signed __int16    int16_t;
-   typedef signed __int32    int32_t;
-   typedef unsigned __int8   uint8_t;
-   typedef unsigned __int16  uint16_t;
-   typedef unsigned __int32  uint32_t;
-#endif
-typedef signed __int64       int64_t;
-typedef unsigned __int64     uint64_t;
-
-
-// 7.18.1.2 Minimum-width integer types
-typedef int8_t    int_least8_t;
-typedef int16_t   int_least16_t;
-typedef int32_t   int_least32_t;
-typedef int64_t   int_least64_t;
-typedef uint8_t   uint_least8_t;
-typedef uint16_t  uint_least16_t;
-typedef uint32_t  uint_least32_t;
-typedef uint64_t  uint_least64_t;
-
-// 7.18.1.3 Fastest minimum-width integer types
-typedef int8_t    int_fast8_t;
-typedef int16_t   int_fast16_t;
-typedef int32_t   int_fast32_t;
-typedef int64_t   int_fast64_t;
-typedef uint8_t   uint_fast8_t;
-typedef uint16_t  uint_fast16_t;
-typedef uint32_t  uint_fast32_t;
-typedef uint64_t  uint_fast64_t;
-
-// 7.18.1.4 Integer types capable of holding object pointers
-#ifdef _WIN64 // [
-   typedef signed __int64    intptr_t;
-   typedef unsigned __int64  uintptr_t;
-#else // _WIN64 ][
-   typedef _W64 signed int   intptr_t;
-   typedef _W64 unsigned int uintptr_t;
-#endif // _WIN64 ]
-
-// 7.18.1.5 Greatest-width integer types
-typedef int64_t   intmax_t;
-typedef uint64_t  uintmax_t;
-
-
-// 7.18.2 Limits of specified-width integer types
-
-#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [   See footnote 220 at page 257 and footnote 221 at page 259
-
-// 7.18.2.1 Limits of exact-width integer types
-#define INT8_MIN     ((int8_t)_I8_MIN)
-#define INT8_MAX     _I8_MAX
-#define INT16_MIN    ((int16_t)_I16_MIN)
-#define INT16_MAX    _I16_MAX
-#define INT32_MIN    ((int32_t)_I32_MIN)
-#define INT32_MAX    _I32_MAX
-#define INT64_MIN    ((int64_t)_I64_MIN)
-#define INT64_MAX    _I64_MAX
-#define UINT8_MAX    _UI8_MAX
-#define UINT16_MAX   _UI16_MAX
-#define UINT32_MAX   _UI32_MAX
-#define UINT64_MAX   _UI64_MAX
-
-// 7.18.2.2 Limits of minimum-width integer types
-#define INT_LEAST8_MIN    INT8_MIN
-#define INT_LEAST8_MAX    INT8_MAX
-#define INT_LEAST16_MIN   INT16_MIN
-#define INT_LEAST16_MAX   INT16_MAX
-#define INT_LEAST32_MIN   INT32_MIN
-#define INT_LEAST32_MAX   INT32_MAX
-#define INT_LEAST64_MIN   INT64_MIN
-#define INT_LEAST64_MAX   INT64_MAX
-#define UINT_LEAST8_MAX   UINT8_MAX
-#define UINT_LEAST16_MAX  UINT16_MAX
-#define UINT_LEAST32_MAX  UINT32_MAX
-#define UINT_LEAST64_MAX  UINT64_MAX
-
-// 7.18.2.3 Limits of fastest minimum-width integer types
-#define INT_FAST8_MIN    INT8_MIN
-#define INT_FAST8_MAX    INT8_MAX
-#define INT_FAST16_MIN   INT16_MIN
-#define INT_FAST16_MAX   INT16_MAX
-#define INT_FAST32_MIN   INT32_MIN
-#define INT_FAST32_MAX   INT32_MAX
-#define INT_FAST64_MIN   INT64_MIN
-#define INT_FAST64_MAX   INT64_MAX
-#define UINT_FAST8_MAX   UINT8_MAX
-#define UINT_FAST16_MAX  UINT16_MAX
-#define UINT_FAST32_MAX  UINT32_MAX
-#define UINT_FAST64_MAX  UINT64_MAX
-
-// 7.18.2.4 Limits of integer types capable of holding object pointers
-#ifdef _WIN64 // [
-#  define INTPTR_MIN   INT64_MIN
-#  define INTPTR_MAX   INT64_MAX
-#  define UINTPTR_MAX  UINT64_MAX
-#else // _WIN64 ][
-#  define INTPTR_MIN   INT32_MIN
-#  define INTPTR_MAX   INT32_MAX
-#  define UINTPTR_MAX  UINT32_MAX
-#endif // _WIN64 ]
-
-// 7.18.2.5 Limits of greatest-width integer types
-#define INTMAX_MIN   INT64_MIN
-#define INTMAX_MAX   INT64_MAX
-#define UINTMAX_MAX  UINT64_MAX
-
-// 7.18.3 Limits of other integer types
-
-#ifdef _WIN64 // [
-#  define PTRDIFF_MIN  _I64_MIN
-#  define PTRDIFF_MAX  _I64_MAX
-#else  // _WIN64 ][
-#  define PTRDIFF_MIN  _I32_MIN
-#  define PTRDIFF_MAX  _I32_MAX
-#endif  // _WIN64 ]
-
-#define SIG_ATOMIC_MIN  INT_MIN
-#define SIG_ATOMIC_MAX  INT_MAX
-
-#ifndef SIZE_MAX // [
-#  ifdef _WIN64 // [
-#     define SIZE_MAX  _UI64_MAX
-#  else // _WIN64 ][
-#     define SIZE_MAX  _UI32_MAX
-#  endif // _WIN64 ]
-#endif // SIZE_MAX ]
-
-// WCHAR_MIN and WCHAR_MAX are also defined in <wchar.h>
-#ifndef WCHAR_MIN // [
-#  define WCHAR_MIN  0
-#endif  // WCHAR_MIN ]
-#ifndef WCHAR_MAX // [
-#  define WCHAR_MAX  _UI16_MAX
-#endif  // WCHAR_MAX ]
-
-#define WINT_MIN  0
-#define WINT_MAX  _UI16_MAX
-
-#endif // __STDC_LIMIT_MACROS ]
-
-
-// 7.18.4 Limits of other integer types
-
-#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [   See footnote 224 at page 260
-
-// 7.18.4.1 Macros for minimum-width integer constants
-
-#define INT8_C(val)  val##i8
-#define INT16_C(val) val##i16
-#define INT32_C(val) val##i32
-#define INT64_C(val) val##i64
-
-#define UINT8_C(val)  val##ui8
-#define UINT16_C(val) val##ui16
-#define UINT32_C(val) val##ui32
-#define UINT64_C(val) val##ui64
-
-// 7.18.4.2 Macros for greatest-width integer constants
-#define INTMAX_C   INT64_C
-#define UINTMAX_C  UINT64_C
-
-#endif // __STDC_CONSTANT_MACROS ]
-
-
-#endif // _MSC_STDINT_H_ ]
--- a/Resources/base64/base64.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,128 +0,0 @@
-/* 
-   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;
-}
--- a/Resources/base64/base64.h	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-#include <string>
-
-std::string base64_encode(const std::string& stringToEncode);
-std::string base64_decode(const std::string& s);
--- a/Resources/md5/md5.c	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,381 +0,0 @@
-/*
-  Copyright (C) 1999, 2000, 2002 Aladdin Enterprises.  All rights reserved.
-
-  This software is provided 'as-is', without any express or implied
-  warranty.  In no event will the authors be held liable for any damages
-  arising from the use of this software.
-
-  Permission is granted to anyone to use this software for any purpose,
-  including commercial applications, and to alter it and redistribute it
-  freely, subject to the following restrictions:
-
-  1. The origin of this software must not be misrepresented; you must not
-     claim that you wrote the original software. If you use this software
-     in a product, an acknowledgment in the product documentation would be
-     appreciated but is not required.
-  2. Altered source versions must be plainly marked as such, and must not be
-     misrepresented as being the original software.
-  3. This notice may not be removed or altered from any source distribution.
-
-  L. Peter Deutsch
-  ghost@aladdin.com
-
- */
-/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */
-/*
-  Independent implementation of MD5 (RFC 1321).
-
-  This code implements the MD5 Algorithm defined in RFC 1321, whose
-  text is available at
-	http://www.ietf.org/rfc/rfc1321.txt
-  The code is derived from the text of the RFC, including the test suite
-  (section A.5) but excluding the rest of Appendix A.  It does not include
-  any code or documentation that is identified in the RFC as being
-  copyrighted.
-
-  The original and principal author of md5.c is L. Peter Deutsch
-  <ghost@aladdin.com>.  Other authors are noted in the change history
-  that follows (in reverse chronological order):
-
-  2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order
-	either statically or dynamically; added missing #include <string.h>
-	in library.
-  2002-03-11 lpd Corrected argument list for main(), and added int return
-	type, in test program and T value program.
-  2002-02-21 lpd Added missing #include <stdio.h> in test program.
-  2000-07-03 lpd Patched to eliminate warnings about "constant is
-	unsigned in ANSI C, signed in traditional"; made test program
-	self-checking.
-  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
-  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
-  1999-05-03 lpd Original version.
- */
-
-#include "md5.h"
-#include <string.h>
-
-#undef BYTE_ORDER	/* 1 = big-endian, -1 = little-endian, 0 = unknown */
-#ifdef ARCH_IS_BIG_ENDIAN
-#  define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
-#else
-#  define BYTE_ORDER 0
-#endif
-
-#define T_MASK ((md5_word_t)~0)
-#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
-#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
-#define T3    0x242070db
-#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
-#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
-#define T6    0x4787c62a
-#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
-#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
-#define T9    0x698098d8
-#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
-#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
-#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
-#define T13    0x6b901122
-#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
-#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
-#define T16    0x49b40821
-#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
-#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
-#define T19    0x265e5a51
-#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
-#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
-#define T22    0x02441453
-#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
-#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
-#define T25    0x21e1cde6
-#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
-#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
-#define T28    0x455a14ed
-#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
-#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
-#define T31    0x676f02d9
-#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
-#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
-#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
-#define T35    0x6d9d6122
-#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
-#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
-#define T38    0x4bdecfa9
-#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
-#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
-#define T41    0x289b7ec6
-#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
-#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
-#define T44    0x04881d05
-#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
-#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
-#define T47    0x1fa27cf8
-#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
-#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
-#define T50    0x432aff97
-#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
-#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
-#define T53    0x655b59c3
-#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
-#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
-#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
-#define T57    0x6fa87e4f
-#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
-#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
-#define T60    0x4e0811a1
-#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
-#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
-#define T63    0x2ad7d2bb
-#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)
-
-
-static void
-md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
-{
-    md5_word_t
-	a = pms->abcd[0], b = pms->abcd[1],
-	c = pms->abcd[2], d = pms->abcd[3];
-    md5_word_t t;
-#if BYTE_ORDER > 0
-    /* Define storage only for big-endian CPUs. */
-    md5_word_t X[16];
-#else
-    /* Define storage for little-endian or both types of CPUs. */
-    md5_word_t xbuf[16];
-    const md5_word_t *X;
-#endif
-
-    {
-#if BYTE_ORDER == 0
-	/*
-	 * Determine dynamically whether this is a big-endian or
-	 * little-endian machine, since we can use a more efficient
-	 * algorithm on the latter.
-	 */
-	static const int w = 1;
-
-	if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
-#endif
-#if BYTE_ORDER <= 0		/* little-endian */
-	{
-	    /*
-	     * On little-endian machines, we can process properly aligned
-	     * data without copying it.
-	     */
-	    if (!((data - (const md5_byte_t *)0) & 3)) {
-		/* data are properly aligned */
-		X = (const md5_word_t *)data;
-	    } else {
-		/* not aligned */
-		memcpy(xbuf, data, 64);
-		X = xbuf;
-	    }
-	}
-#endif
-#if BYTE_ORDER == 0
-	else			/* dynamic big-endian */
-#endif
-#if BYTE_ORDER >= 0		/* big-endian */
-	{
-	    /*
-	     * On big-endian machines, we must arrange the bytes in the
-	     * right order.
-	     */
-	    const md5_byte_t *xp = data;
-	    int i;
-
-#  if BYTE_ORDER == 0
-	    X = xbuf;		/* (dynamic only) */
-#  else
-#    define xbuf X		/* (static only) */
-#  endif
-	    for (i = 0; i < 16; ++i, xp += 4)
-		xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
-	}
-#endif
-    }
-
-#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
-
-    /* Round 1. */
-    /* Let [abcd k s i] denote the operation
-       a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
-#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
-#define SET(a, b, c, d, k, s, Ti)\
-  t = a + F(b,c,d) + X[k] + Ti;\
-  a = ROTATE_LEFT(t, s) + b
-    /* Do the following 16 operations. */
-    SET(a, b, c, d,  0,  7,  T1);
-    SET(d, a, b, c,  1, 12,  T2);
-    SET(c, d, a, b,  2, 17,  T3);
-    SET(b, c, d, a,  3, 22,  T4);
-    SET(a, b, c, d,  4,  7,  T5);
-    SET(d, a, b, c,  5, 12,  T6);
-    SET(c, d, a, b,  6, 17,  T7);
-    SET(b, c, d, a,  7, 22,  T8);
-    SET(a, b, c, d,  8,  7,  T9);
-    SET(d, a, b, c,  9, 12, T10);
-    SET(c, d, a, b, 10, 17, T11);
-    SET(b, c, d, a, 11, 22, T12);
-    SET(a, b, c, d, 12,  7, T13);
-    SET(d, a, b, c, 13, 12, T14);
-    SET(c, d, a, b, 14, 17, T15);
-    SET(b, c, d, a, 15, 22, T16);
-#undef SET
-
-     /* Round 2. */
-     /* Let [abcd k s i] denote the operation
-          a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
-#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
-#define SET(a, b, c, d, k, s, Ti)\
-  t = a + G(b,c,d) + X[k] + Ti;\
-  a = ROTATE_LEFT(t, s) + b
-     /* Do the following 16 operations. */
-    SET(a, b, c, d,  1,  5, T17);
-    SET(d, a, b, c,  6,  9, T18);
-    SET(c, d, a, b, 11, 14, T19);
-    SET(b, c, d, a,  0, 20, T20);
-    SET(a, b, c, d,  5,  5, T21);
-    SET(d, a, b, c, 10,  9, T22);
-    SET(c, d, a, b, 15, 14, T23);
-    SET(b, c, d, a,  4, 20, T24);
-    SET(a, b, c, d,  9,  5, T25);
-    SET(d, a, b, c, 14,  9, T26);
-    SET(c, d, a, b,  3, 14, T27);
-    SET(b, c, d, a,  8, 20, T28);
-    SET(a, b, c, d, 13,  5, T29);
-    SET(d, a, b, c,  2,  9, T30);
-    SET(c, d, a, b,  7, 14, T31);
-    SET(b, c, d, a, 12, 20, T32);
-#undef SET
-
-     /* Round 3. */
-     /* Let [abcd k s t] denote the operation
-          a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
-#define H(x, y, z) ((x) ^ (y) ^ (z))
-#define SET(a, b, c, d, k, s, Ti)\
-  t = a + H(b,c,d) + X[k] + Ti;\
-  a = ROTATE_LEFT(t, s) + b
-     /* Do the following 16 operations. */
-    SET(a, b, c, d,  5,  4, T33);
-    SET(d, a, b, c,  8, 11, T34);
-    SET(c, d, a, b, 11, 16, T35);
-    SET(b, c, d, a, 14, 23, T36);
-    SET(a, b, c, d,  1,  4, T37);
-    SET(d, a, b, c,  4, 11, T38);
-    SET(c, d, a, b,  7, 16, T39);
-    SET(b, c, d, a, 10, 23, T40);
-    SET(a, b, c, d, 13,  4, T41);
-    SET(d, a, b, c,  0, 11, T42);
-    SET(c, d, a, b,  3, 16, T43);
-    SET(b, c, d, a,  6, 23, T44);
-    SET(a, b, c, d,  9,  4, T45);
-    SET(d, a, b, c, 12, 11, T46);
-    SET(c, d, a, b, 15, 16, T47);
-    SET(b, c, d, a,  2, 23, T48);
-#undef SET
-
-     /* Round 4. */
-     /* Let [abcd k s t] denote the operation
-          a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
-#define I(x, y, z) ((y) ^ ((x) | ~(z)))
-#define SET(a, b, c, d, k, s, Ti)\
-  t = a + I(b,c,d) + X[k] + Ti;\
-  a = ROTATE_LEFT(t, s) + b
-     /* Do the following 16 operations. */
-    SET(a, b, c, d,  0,  6, T49);
-    SET(d, a, b, c,  7, 10, T50);
-    SET(c, d, a, b, 14, 15, T51);
-    SET(b, c, d, a,  5, 21, T52);
-    SET(a, b, c, d, 12,  6, T53);
-    SET(d, a, b, c,  3, 10, T54);
-    SET(c, d, a, b, 10, 15, T55);
-    SET(b, c, d, a,  1, 21, T56);
-    SET(a, b, c, d,  8,  6, T57);
-    SET(d, a, b, c, 15, 10, T58);
-    SET(c, d, a, b,  6, 15, T59);
-    SET(b, c, d, a, 13, 21, T60);
-    SET(a, b, c, d,  4,  6, T61);
-    SET(d, a, b, c, 11, 10, T62);
-    SET(c, d, a, b,  2, 15, T63);
-    SET(b, c, d, a,  9, 21, T64);
-#undef SET
-
-     /* Then perform the following additions. (That is increment each
-        of the four registers by the value it had before this block
-        was started.) */
-    pms->abcd[0] += a;
-    pms->abcd[1] += b;
-    pms->abcd[2] += c;
-    pms->abcd[3] += d;
-}
-
-void
-md5_init(md5_state_t *pms)
-{
-    pms->count[0] = pms->count[1] = 0;
-    pms->abcd[0] = 0x67452301;
-    pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
-    pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
-    pms->abcd[3] = 0x10325476;
-}
-
-void
-md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
-{
-    const md5_byte_t *p = data;
-    int left = nbytes;
-    int offset = (pms->count[0] >> 3) & 63;
-    md5_word_t nbits = (md5_word_t)(nbytes << 3);
-
-    if (nbytes <= 0)
-	return;
-
-    /* Update the message length. */
-    pms->count[1] += nbytes >> 29;
-    pms->count[0] += nbits;
-    if (pms->count[0] < nbits)
-	pms->count[1]++;
-
-    /* Process an initial partial block. */
-    if (offset) {
-	int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
-
-	memcpy(pms->buf + offset, p, copy);
-	if (offset + copy < 64)
-	    return;
-	p += copy;
-	left -= copy;
-	md5_process(pms, pms->buf);
-    }
-
-    /* Process full blocks. */
-    for (; left >= 64; p += 64, left -= 64)
-	md5_process(pms, p);
-
-    /* Process a final partial block. */
-    if (left)
-	memcpy(pms->buf, p, left);
-}
-
-void
-md5_finish(md5_state_t *pms, md5_byte_t digest[16])
-{
-    static const md5_byte_t pad[64] = {
-	0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
-    };
-    md5_byte_t data[8];
-    int i;
-
-    /* Save the length before padding. */
-    for (i = 0; i < 8; ++i)
-	data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
-    /* Pad to 56 bytes mod 64. */
-    md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
-    /* Append the length. */
-    md5_append(pms, data, 8);
-    for (i = 0; i < 16; ++i)
-	digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
-}
--- a/Resources/md5/md5.h	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,91 +0,0 @@
-/*
-  Copyright (C) 1999, 2002 Aladdin Enterprises.  All rights reserved.
-
-  This software is provided 'as-is', without any express or implied
-  warranty.  In no event will the authors be held liable for any damages
-  arising from the use of this software.
-
-  Permission is granted to anyone to use this software for any purpose,
-  including commercial applications, and to alter it and redistribute it
-  freely, subject to the following restrictions:
-
-  1. The origin of this software must not be misrepresented; you must not
-     claim that you wrote the original software. If you use this software
-     in a product, an acknowledgment in the product documentation would be
-     appreciated but is not required.
-  2. Altered source versions must be plainly marked as such, and must not be
-     misrepresented as being the original software.
-  3. This notice may not be removed or altered from any source distribution.
-
-  L. Peter Deutsch
-  ghost@aladdin.com
-
- */
-/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */
-/*
-  Independent implementation of MD5 (RFC 1321).
-
-  This code implements the MD5 Algorithm defined in RFC 1321, whose
-  text is available at
-	http://www.ietf.org/rfc/rfc1321.txt
-  The code is derived from the text of the RFC, including the test suite
-  (section A.5) but excluding the rest of Appendix A.  It does not include
-  any code or documentation that is identified in the RFC as being
-  copyrighted.
-
-  The original and principal author of md5.h is L. Peter Deutsch
-  <ghost@aladdin.com>.  Other authors are noted in the change history
-  that follows (in reverse chronological order):
-
-  2002-04-13 lpd Removed support for non-ANSI compilers; removed
-	references to Ghostscript; clarified derivation from RFC 1321;
-	now handles byte order either statically or dynamically.
-  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
-  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
-	added conditionalization for C++ compilation from Martin
-	Purschke <purschke@bnl.gov>.
-  1999-05-03 lpd Original version.
- */
-
-#ifndef md5_INCLUDED
-#  define md5_INCLUDED
-
-/*
- * This package supports both compile-time and run-time determination of CPU
- * byte order.  If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
- * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
- * defined as non-zero, the code will be compiled to run only on big-endian
- * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
- * run on either big- or little-endian CPUs, but will run slightly less
- * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
- */
-
-typedef unsigned char md5_byte_t; /* 8-bit byte */
-typedef unsigned int md5_word_t; /* 32-bit word */
-
-/* Define the state of the MD5 Algorithm. */
-typedef struct md5_state_s {
-    md5_word_t count[2];	/* message length in bits, lsw first */
-    md5_word_t abcd[4];		/* digest buffer */
-    md5_byte_t buf[64];		/* accumulate block */
-} md5_state_t;
-
-#ifdef __cplusplus
-extern "C" 
-{
-#endif
-
-/* Initialize the algorithm. */
-void md5_init(md5_state_t *pms);
-
-/* Append a string to the message. */
-void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
-
-/* Finish the message and return the digest. */
-void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
-
-#ifdef __cplusplus
-}  /* end extern "C" */
-#endif
-
-#endif /* md5_INCLUDED */
--- a/Resources/minizip/NOTES	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-These files come from the "contrib/minizip" directory in zlib 1.2.7.
--- a/Resources/minizip/crypt.h	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,131 +0,0 @@
-/* crypt.h -- base code for crypt/uncrypt ZIPfile
-
-
-   Version 1.01e, February 12th, 2005
-
-   Copyright (C) 1998-2005 Gilles Vollant
-
-   This code is a modified version of crypting code in Infozip distribution
-
-   The encryption/decryption parts of this source code (as opposed to the
-   non-echoing password parts) were originally written in Europe.  The
-   whole source package can be freely distributed, including from the USA.
-   (Prior to January 2000, re-export from the US was a violation of US law.)
-
-   This encryption code is a direct transcription of the algorithm from
-   Roger Schlafly, described by Phil Katz in the file appnote.txt.  This
-   file (appnote.txt) is distributed with the PKZIP program (even in the
-   version without encryption capabilities).
-
-   If you don't need crypting in your application, just define symbols
-   NOCRYPT and NOUNCRYPT.
-
-   This code support the "Traditional PKWARE Encryption".
-
-   The new AES encryption added on Zip format by Winzip (see the page
-   http://www.winzip.com/aes_info.htm ) and PKWare PKZip 5.x Strong
-   Encryption is not supported.
-*/
-
-#define CRC32(c, b) ((*(pcrc_32_tab+(((int)(c) ^ (b)) & 0xff))) ^ ((c) >> 8))
-
-/***********************************************************************
- * Return the next byte in the pseudo-random sequence
- */
-static int decrypt_byte(unsigned long* pkeys, const unsigned long* pcrc_32_tab)
-{
-    unsigned temp;  /* POTENTIAL BUG:  temp*(temp^1) may overflow in an
-                     * unpredictable manner on 16-bit systems; not a problem
-                     * with any known compiler so far, though */
-
-    temp = ((unsigned)(*(pkeys+2)) & 0xffff) | 2;
-    return (int)(((temp * (temp ^ 1)) >> 8) & 0xff);
-}
-
-/***********************************************************************
- * Update the encryption keys with the next byte of plain text
- */
-static int update_keys(unsigned long* pkeys,const unsigned long* pcrc_32_tab,int c)
-{
-    (*(pkeys+0)) = CRC32((*(pkeys+0)), c);
-    (*(pkeys+1)) += (*(pkeys+0)) & 0xff;
-    (*(pkeys+1)) = (*(pkeys+1)) * 134775813L + 1;
-    {
-      register int keyshift = (int)((*(pkeys+1)) >> 24);
-      (*(pkeys+2)) = CRC32((*(pkeys+2)), keyshift);
-    }
-    return c;
-}
-
-
-/***********************************************************************
- * Initialize the encryption keys and the random header according to
- * the given password.
- */
-static void init_keys(const char* passwd,unsigned long* pkeys,const unsigned long* pcrc_32_tab)
-{
-    *(pkeys+0) = 305419896L;
-    *(pkeys+1) = 591751049L;
-    *(pkeys+2) = 878082192L;
-    while (*passwd != '\0') {
-        update_keys(pkeys,pcrc_32_tab,(int)*passwd);
-        passwd++;
-    }
-}
-
-#define zdecode(pkeys,pcrc_32_tab,c) \
-    (update_keys(pkeys,pcrc_32_tab,c ^= decrypt_byte(pkeys,pcrc_32_tab)))
-
-#define zencode(pkeys,pcrc_32_tab,c,t) \
-    (t=decrypt_byte(pkeys,pcrc_32_tab), update_keys(pkeys,pcrc_32_tab,c), t^(c))
-
-#ifdef INCLUDECRYPTINGCODE_IFCRYPTALLOWED
-
-#define RAND_HEAD_LEN  12
-   /* "last resort" source for second part of crypt seed pattern */
-#  ifndef ZCR_SEED2
-#    define ZCR_SEED2 3141592654UL     /* use PI as default pattern */
-#  endif
-
-static int crypthead(const char* passwd,      /* password string */
-                     unsigned char* buf,      /* where to write header */
-                     int bufSize,
-                     unsigned long* pkeys,
-                     const unsigned long* pcrc_32_tab,
-                     unsigned long crcForCrypting)
-{
-    int n;                       /* index in random header */
-    int t;                       /* temporary */
-    int c;                       /* random byte */
-    unsigned char header[RAND_HEAD_LEN-2]; /* random header */
-    static unsigned calls = 0;   /* ensure different random header each time */
-
-    if (bufSize<RAND_HEAD_LEN)
-      return 0;
-
-    /* First generate RAND_HEAD_LEN-2 random bytes. We encrypt the
-     * output of rand() to get less predictability, since rand() is
-     * often poorly implemented.
-     */
-    if (++calls == 1)
-    {
-        srand((unsigned)(time(NULL) ^ ZCR_SEED2));
-    }
-    init_keys(passwd, pkeys, pcrc_32_tab);
-    for (n = 0; n < RAND_HEAD_LEN-2; n++)
-    {
-        c = (rand() >> 7) & 0xff;
-        header[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, c, t);
-    }
-    /* Encrypt random header (last two bytes is high word of crc) */
-    init_keys(passwd, pkeys, pcrc_32_tab);
-    for (n = 0; n < RAND_HEAD_LEN-2; n++)
-    {
-        buf[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, header[n], t);
-    }
-    buf[n++] = (unsigned char)zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 16) & 0xff, t);
-    buf[n++] = (unsigned char)zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 24) & 0xff, t);
-    return n;
-}
-
-#endif
--- a/Resources/minizip/ioapi.c	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,247 +0,0 @@
-/* ioapi.h -- IO base function header for compress/uncompress .zip
-   part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
-
-         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
-
-         Modifications for Zip64 support
-         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
-
-         For more info read MiniZip_info.txt
-
-*/
-
-#if defined(_WIN32) && (!(defined(_CRT_SECURE_NO_WARNINGS)))
-        #define _CRT_SECURE_NO_WARNINGS
-#endif
-
-#if defined(__APPLE__) || defined(IOAPI_NO_64)
-// In darwin and perhaps other BSD variants off_t is a 64 bit value, hence no need for specific 64 bit functions
-#define FOPEN_FUNC(filename, mode) fopen(filename, mode)
-#define FTELLO_FUNC(stream) ftello(stream)
-#define FSEEKO_FUNC(stream, offset, origin) fseeko(stream, offset, origin)
-#else
-#define FOPEN_FUNC(filename, mode) fopen64(filename, mode)
-#define FTELLO_FUNC(stream) ftello64(stream)
-#define FSEEKO_FUNC(stream, offset, origin) fseeko64(stream, offset, origin)
-#endif
-
-
-#include "ioapi.h"
-
-voidpf call_zopen64 (const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode)
-{
-    if (pfilefunc->zfile_func64.zopen64_file != NULL)
-        return (*(pfilefunc->zfile_func64.zopen64_file)) (pfilefunc->zfile_func64.opaque,filename,mode);
-    else
-    {
-        return (*(pfilefunc->zopen32_file))(pfilefunc->zfile_func64.opaque,(const char*)filename,mode);
-    }
-}
-
-long call_zseek64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin)
-{
-    if (pfilefunc->zfile_func64.zseek64_file != NULL)
-        return (*(pfilefunc->zfile_func64.zseek64_file)) (pfilefunc->zfile_func64.opaque,filestream,offset,origin);
-    else
-    {
-        uLong offsetTruncated = (uLong)offset;
-        if (offsetTruncated != offset)
-            return -1;
-        else
-            return (*(pfilefunc->zseek32_file))(pfilefunc->zfile_func64.opaque,filestream,offsetTruncated,origin);
-    }
-}
-
-ZPOS64_T call_ztell64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream)
-{
-    if (pfilefunc->zfile_func64.zseek64_file != NULL)
-        return (*(pfilefunc->zfile_func64.ztell64_file)) (pfilefunc->zfile_func64.opaque,filestream);
-    else
-    {
-        uLong tell_uLong = (*(pfilefunc->ztell32_file))(pfilefunc->zfile_func64.opaque,filestream);
-        if ((tell_uLong) == MAXU32)
-            return (ZPOS64_T)-1;
-        else
-            return tell_uLong;
-    }
-}
-
-void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32)
-{
-    p_filefunc64_32->zfile_func64.zopen64_file = NULL;
-    p_filefunc64_32->zopen32_file = p_filefunc32->zopen_file;
-    p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file;
-    p_filefunc64_32->zfile_func64.zread_file = p_filefunc32->zread_file;
-    p_filefunc64_32->zfile_func64.zwrite_file = p_filefunc32->zwrite_file;
-    p_filefunc64_32->zfile_func64.ztell64_file = NULL;
-    p_filefunc64_32->zfile_func64.zseek64_file = NULL;
-    p_filefunc64_32->zfile_func64.zclose_file = p_filefunc32->zclose_file;
-    p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file;
-    p_filefunc64_32->zfile_func64.opaque = p_filefunc32->opaque;
-    p_filefunc64_32->zseek32_file = p_filefunc32->zseek_file;
-    p_filefunc64_32->ztell32_file = p_filefunc32->ztell_file;
-}
-
-
-
-static voidpf  ZCALLBACK fopen_file_func OF((voidpf opaque, const char* filename, int mode));
-static uLong   ZCALLBACK fread_file_func OF((voidpf opaque, voidpf stream, void* buf, uLong size));
-static uLong   ZCALLBACK fwrite_file_func OF((voidpf opaque, voidpf stream, const void* buf,uLong size));
-static ZPOS64_T ZCALLBACK ftell64_file_func OF((voidpf opaque, voidpf stream));
-static long    ZCALLBACK fseek64_file_func OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin));
-static int     ZCALLBACK fclose_file_func OF((voidpf opaque, voidpf stream));
-static int     ZCALLBACK ferror_file_func OF((voidpf opaque, voidpf stream));
-
-static voidpf ZCALLBACK fopen_file_func (voidpf opaque, const char* filename, int mode)
-{
-    FILE* file = NULL;
-    const char* mode_fopen = NULL;
-    if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ)
-        mode_fopen = "rb";
-    else
-    if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
-        mode_fopen = "r+b";
-    else
-    if (mode & ZLIB_FILEFUNC_MODE_CREATE)
-        mode_fopen = "wb";
-
-    if ((filename!=NULL) && (mode_fopen != NULL))
-        file = fopen(filename, mode_fopen);
-    return file;
-}
-
-static voidpf ZCALLBACK fopen64_file_func (voidpf opaque, const void* filename, int mode)
-{
-    FILE* file = NULL;
-    const char* mode_fopen = NULL;
-    if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ)
-        mode_fopen = "rb";
-    else
-    if (mode & ZLIB_FILEFUNC_MODE_EXISTING)
-        mode_fopen = "r+b";
-    else
-    if (mode & ZLIB_FILEFUNC_MODE_CREATE)
-        mode_fopen = "wb";
-
-    if ((filename!=NULL) && (mode_fopen != NULL))
-        file = FOPEN_FUNC((const char*)filename, mode_fopen);
-    return file;
-}
-
-
-static uLong ZCALLBACK fread_file_func (voidpf opaque, voidpf stream, void* buf, uLong size)
-{
-    uLong ret;
-    ret = (uLong)fread(buf, 1, (size_t)size, (FILE *)stream);
-    return ret;
-}
-
-static uLong ZCALLBACK fwrite_file_func (voidpf opaque, voidpf stream, const void* buf, uLong size)
-{
-    uLong ret;
-    ret = (uLong)fwrite(buf, 1, (size_t)size, (FILE *)stream);
-    return ret;
-}
-
-static long ZCALLBACK ftell_file_func (voidpf opaque, voidpf stream)
-{
-    long ret;
-    ret = ftell((FILE *)stream);
-    return ret;
-}
-
-
-static ZPOS64_T ZCALLBACK ftell64_file_func (voidpf opaque, voidpf stream)
-{
-    ZPOS64_T ret;
-    ret = FTELLO_FUNC((FILE *)stream);
-    return ret;
-}
-
-static long ZCALLBACK fseek_file_func (voidpf  opaque, voidpf stream, uLong offset, int origin)
-{
-    int fseek_origin=0;
-    long ret;
-    switch (origin)
-    {
-    case ZLIB_FILEFUNC_SEEK_CUR :
-        fseek_origin = SEEK_CUR;
-        break;
-    case ZLIB_FILEFUNC_SEEK_END :
-        fseek_origin = SEEK_END;
-        break;
-    case ZLIB_FILEFUNC_SEEK_SET :
-        fseek_origin = SEEK_SET;
-        break;
-    default: return -1;
-    }
-    ret = 0;
-    if (fseek((FILE *)stream, offset, fseek_origin) != 0)
-        ret = -1;
-    return ret;
-}
-
-static long ZCALLBACK fseek64_file_func (voidpf  opaque, voidpf stream, ZPOS64_T offset, int origin)
-{
-    int fseek_origin=0;
-    long ret;
-    switch (origin)
-    {
-    case ZLIB_FILEFUNC_SEEK_CUR :
-        fseek_origin = SEEK_CUR;
-        break;
-    case ZLIB_FILEFUNC_SEEK_END :
-        fseek_origin = SEEK_END;
-        break;
-    case ZLIB_FILEFUNC_SEEK_SET :
-        fseek_origin = SEEK_SET;
-        break;
-    default: return -1;
-    }
-    ret = 0;
-
-    if(FSEEKO_FUNC((FILE *)stream, offset, fseek_origin) != 0)
-                        ret = -1;
-
-    return ret;
-}
-
-
-static int ZCALLBACK fclose_file_func (voidpf opaque, voidpf stream)
-{
-    int ret;
-    ret = fclose((FILE *)stream);
-    return ret;
-}
-
-static int ZCALLBACK ferror_file_func (voidpf opaque, voidpf stream)
-{
-    int ret;
-    ret = ferror((FILE *)stream);
-    return ret;
-}
-
-void fill_fopen_filefunc (pzlib_filefunc_def)
-  zlib_filefunc_def* pzlib_filefunc_def;
-{
-    pzlib_filefunc_def->zopen_file = fopen_file_func;
-    pzlib_filefunc_def->zread_file = fread_file_func;
-    pzlib_filefunc_def->zwrite_file = fwrite_file_func;
-    pzlib_filefunc_def->ztell_file = ftell_file_func;
-    pzlib_filefunc_def->zseek_file = fseek_file_func;
-    pzlib_filefunc_def->zclose_file = fclose_file_func;
-    pzlib_filefunc_def->zerror_file = ferror_file_func;
-    pzlib_filefunc_def->opaque = NULL;
-}
-
-void fill_fopen64_filefunc (zlib_filefunc64_def*  pzlib_filefunc_def)
-{
-    pzlib_filefunc_def->zopen64_file = fopen64_file_func;
-    pzlib_filefunc_def->zread_file = fread_file_func;
-    pzlib_filefunc_def->zwrite_file = fwrite_file_func;
-    pzlib_filefunc_def->ztell64_file = ftell64_file_func;
-    pzlib_filefunc_def->zseek64_file = fseek64_file_func;
-    pzlib_filefunc_def->zclose_file = fclose_file_func;
-    pzlib_filefunc_def->zerror_file = ferror_file_func;
-    pzlib_filefunc_def->opaque = NULL;
-}
--- a/Resources/minizip/ioapi.h	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,208 +0,0 @@
-/* ioapi.h -- IO base function header for compress/uncompress .zip
-   part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
-
-         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
-
-         Modifications for Zip64 support
-         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
-
-         For more info read MiniZip_info.txt
-
-         Changes
-
-    Oct-2009 - Defined ZPOS64_T to fpos_t on windows and u_int64_t on linux. (might need to find a better why for this)
-    Oct-2009 - Change to fseeko64, ftello64 and fopen64 so large files would work on linux.
-               More if/def section may be needed to support other platforms
-    Oct-2009 - Defined fxxxx64 calls to normal fopen/ftell/fseek so they would compile on windows.
-                          (but you should use iowin32.c for windows instead)
-
-*/
-
-#ifndef _ZLIBIOAPI64_H
-#define _ZLIBIOAPI64_H
-
-#if (!defined(_WIN32)) && (!defined(WIN32)) && (!defined(__APPLE__))
-
-  // Linux needs this to support file operation on files larger then 4+GB
-  // But might need better if/def to select just the platforms that needs them.
-
-        #ifndef __USE_FILE_OFFSET64
-                #define __USE_FILE_OFFSET64
-        #endif
-        #ifndef __USE_LARGEFILE64
-                #define __USE_LARGEFILE64
-        #endif
-        #ifndef _LARGEFILE64_SOURCE
-                #define _LARGEFILE64_SOURCE
-        #endif
-        #ifndef _FILE_OFFSET_BIT
-                #define _FILE_OFFSET_BIT 64
-        #endif
-
-#endif
-
-#include <stdio.h>
-#include <stdlib.h>
-#include "zlib.h"
-
-#if defined(USE_FILE32API)
-#define fopen64 fopen
-#define ftello64 ftell
-#define fseeko64 fseek
-#else
-#ifdef __FreeBSD__
-#define fopen64 fopen
-#define ftello64 ftello
-#define fseeko64 fseeko
-#endif
-#ifdef _MSC_VER
- #define fopen64 fopen
- #if (_MSC_VER >= 1400) && (!(defined(NO_MSCVER_FILE64_FUNC)))
-  #define ftello64 _ftelli64
-  #define fseeko64 _fseeki64
- #else // old MSC
-  #define ftello64 ftell
-  #define fseeko64 fseek
- #endif
-#endif
-#endif
-
-/*
-#ifndef ZPOS64_T
-  #ifdef _WIN32
-                #define ZPOS64_T fpos_t
-  #else
-    #include <stdint.h>
-    #define ZPOS64_T uint64_t
-  #endif
-#endif
-*/
-
-#ifdef HAVE_MINIZIP64_CONF_H
-#include "mz64conf.h"
-#endif
-
-/* a type choosen by DEFINE */
-#ifdef HAVE_64BIT_INT_CUSTOM
-typedef  64BIT_INT_CUSTOM_TYPE ZPOS64_T;
-#else
-#ifdef HAS_STDINT_H
-#include "stdint.h"
-typedef uint64_t ZPOS64_T;
-#else
-
-/* Maximum unsigned 32-bit value used as placeholder for zip64 */
-#define MAXU32 0xffffffff
-
-#if defined(_MSC_VER) || defined(__BORLANDC__)
-typedef unsigned __int64 ZPOS64_T;
-#else
-typedef unsigned long long int ZPOS64_T;
-#endif
-#endif
-#endif
-
-
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-
-#define ZLIB_FILEFUNC_SEEK_CUR (1)
-#define ZLIB_FILEFUNC_SEEK_END (2)
-#define ZLIB_FILEFUNC_SEEK_SET (0)
-
-#define ZLIB_FILEFUNC_MODE_READ      (1)
-#define ZLIB_FILEFUNC_MODE_WRITE     (2)
-#define ZLIB_FILEFUNC_MODE_READWRITEFILTER (3)
-
-#define ZLIB_FILEFUNC_MODE_EXISTING (4)
-#define ZLIB_FILEFUNC_MODE_CREATE   (8)
-
-
-#ifndef ZCALLBACK
- #if (defined(WIN32) || defined(_WIN32) || defined (WINDOWS) || defined (_WINDOWS)) && defined(CALLBACK) && defined (USEWINDOWS_CALLBACK)
-   #define ZCALLBACK CALLBACK
- #else
-   #define ZCALLBACK
- #endif
-#endif
-
-
-
-
-typedef voidpf   (ZCALLBACK *open_file_func)      OF((voidpf opaque, const char* filename, int mode));
-typedef uLong    (ZCALLBACK *read_file_func)      OF((voidpf opaque, voidpf stream, void* buf, uLong size));
-typedef uLong    (ZCALLBACK *write_file_func)     OF((voidpf opaque, voidpf stream, const void* buf, uLong size));
-typedef int      (ZCALLBACK *close_file_func)     OF((voidpf opaque, voidpf stream));
-typedef int      (ZCALLBACK *testerror_file_func) OF((voidpf opaque, voidpf stream));
-
-typedef long     (ZCALLBACK *tell_file_func)      OF((voidpf opaque, voidpf stream));
-typedef long     (ZCALLBACK *seek_file_func)      OF((voidpf opaque, voidpf stream, uLong offset, int origin));
-
-
-/* here is the "old" 32 bits structure structure */
-typedef struct zlib_filefunc_def_s
-{
-    open_file_func      zopen_file;
-    read_file_func      zread_file;
-    write_file_func     zwrite_file;
-    tell_file_func      ztell_file;
-    seek_file_func      zseek_file;
-    close_file_func     zclose_file;
-    testerror_file_func zerror_file;
-    voidpf              opaque;
-} zlib_filefunc_def;
-
-typedef ZPOS64_T (ZCALLBACK *tell64_file_func)    OF((voidpf opaque, voidpf stream));
-typedef long     (ZCALLBACK *seek64_file_func)    OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin));
-typedef voidpf   (ZCALLBACK *open64_file_func)    OF((voidpf opaque, const void* filename, int mode));
-
-typedef struct zlib_filefunc64_def_s
-{
-    open64_file_func    zopen64_file;
-    read_file_func      zread_file;
-    write_file_func     zwrite_file;
-    tell64_file_func    ztell64_file;
-    seek64_file_func    zseek64_file;
-    close_file_func     zclose_file;
-    testerror_file_func zerror_file;
-    voidpf              opaque;
-} zlib_filefunc64_def;
-
-void fill_fopen64_filefunc OF((zlib_filefunc64_def* pzlib_filefunc_def));
-void fill_fopen_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def));
-
-/* now internal definition, only for zip.c and unzip.h */
-typedef struct zlib_filefunc64_32_def_s
-{
-    zlib_filefunc64_def zfile_func64;
-    open_file_func      zopen32_file;
-    tell_file_func      ztell32_file;
-    seek_file_func      zseek32_file;
-} zlib_filefunc64_32_def;
-
-
-#define ZREAD64(filefunc,filestream,buf,size)     ((*((filefunc).zfile_func64.zread_file))   ((filefunc).zfile_func64.opaque,filestream,buf,size))
-#define ZWRITE64(filefunc,filestream,buf,size)    ((*((filefunc).zfile_func64.zwrite_file))  ((filefunc).zfile_func64.opaque,filestream,buf,size))
-//#define ZTELL64(filefunc,filestream)            ((*((filefunc).ztell64_file)) ((filefunc).opaque,filestream))
-//#define ZSEEK64(filefunc,filestream,pos,mode)   ((*((filefunc).zseek64_file)) ((filefunc).opaque,filestream,pos,mode))
-#define ZCLOSE64(filefunc,filestream)             ((*((filefunc).zfile_func64.zclose_file))  ((filefunc).zfile_func64.opaque,filestream))
-#define ZERROR64(filefunc,filestream)             ((*((filefunc).zfile_func64.zerror_file))  ((filefunc).zfile_func64.opaque,filestream))
-
-voidpf call_zopen64 OF((const zlib_filefunc64_32_def* pfilefunc,const void*filename,int mode));
-long    call_zseek64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin));
-ZPOS64_T call_ztell64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream));
-
-void    fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32);
-
-#define ZOPEN64(filefunc,filename,mode)         (call_zopen64((&(filefunc)),(filename),(mode)))
-#define ZTELL64(filefunc,filestream)            (call_ztell64((&(filefunc)),(filestream)))
-#define ZSEEK64(filefunc,filestream,pos,mode)   (call_zseek64((&(filefunc)),(filestream),(pos),(mode)))
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
--- a/Resources/minizip/zip.c	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2007 +0,0 @@
-/* zip.c -- IO on .zip files using zlib
-   Version 1.1, February 14h, 2010
-   part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
-
-         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
-
-         Modifications for Zip64 support
-         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
-
-         For more info read MiniZip_info.txt
-
-         Changes
-   Oct-2009 - Mathias Svensson - Remove old C style function prototypes
-   Oct-2009 - Mathias Svensson - Added Zip64 Support when creating new file archives
-   Oct-2009 - Mathias Svensson - Did some code cleanup and refactoring to get better overview of some functions.
-   Oct-2009 - Mathias Svensson - Added zipRemoveExtraInfoBlock to strip extra field data from its ZIP64 data
-                                 It is used when recreting zip archive with RAW when deleting items from a zip.
-                                 ZIP64 data is automaticly added to items that needs it, and existing ZIP64 data need to be removed.
-   Oct-2009 - Mathias Svensson - Added support for BZIP2 as compression mode (bzip2 lib is required)
-   Jan-2010 - back to unzip and minizip 1.0 name scheme, with compatibility layer
-
-*/
-
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-#include "zlib.h"
-#include "zip.h"
-
-#ifdef STDC
-#  include <stddef.h>
-#  include <string.h>
-#  include <stdlib.h>
-#endif
-#ifdef NO_ERRNO_H
-    extern int errno;
-#else
-#   include <errno.h>
-#endif
-
-
-#ifndef local
-#  define local static
-#endif
-/* compile with -Dlocal if your debugger can't find static symbols */
-
-#ifndef VERSIONMADEBY
-# define VERSIONMADEBY   (0x0) /* platform depedent */
-#endif
-
-#ifndef Z_BUFSIZE
-#define Z_BUFSIZE (64*1024) //(16384)
-#endif
-
-#ifndef Z_MAXFILENAMEINZIP
-#define Z_MAXFILENAMEINZIP (256)
-#endif
-
-#ifndef ALLOC
-# define ALLOC(size) (malloc(size))
-#endif
-#ifndef TRYFREE
-# define TRYFREE(p) {if (p) free(p);}
-#endif
-
-/*
-#define SIZECENTRALDIRITEM (0x2e)
-#define SIZEZIPLOCALHEADER (0x1e)
-*/
-
-/* I've found an old Unix (a SunOS 4.1.3_U1) without all SEEK_* defined.... */
-
-
-// NOT sure that this work on ALL platform
-#define MAKEULONG64(a, b) ((ZPOS64_T)(((unsigned long)(a)) | ((ZPOS64_T)((unsigned long)(b))) << 32))
-
-#ifndef SEEK_CUR
-#define SEEK_CUR    1
-#endif
-
-#ifndef SEEK_END
-#define SEEK_END    2
-#endif
-
-#ifndef SEEK_SET
-#define SEEK_SET    0
-#endif
-
-#ifndef DEF_MEM_LEVEL
-#if MAX_MEM_LEVEL >= 8
-#  define DEF_MEM_LEVEL 8
-#else
-#  define DEF_MEM_LEVEL  MAX_MEM_LEVEL
-#endif
-#endif
-const char zip_copyright[] =" zip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll";
-
-
-#define SIZEDATA_INDATABLOCK (4096-(4*4))
-
-#define LOCALHEADERMAGIC    (0x04034b50)
-#define CENTRALHEADERMAGIC  (0x02014b50)
-#define ENDHEADERMAGIC      (0x06054b50)
-#define ZIP64ENDHEADERMAGIC      (0x6064b50)
-#define ZIP64ENDLOCHEADERMAGIC   (0x7064b50)
-
-#define FLAG_LOCALHEADER_OFFSET (0x06)
-#define CRC_LOCALHEADER_OFFSET  (0x0e)
-
-#define SIZECENTRALHEADER (0x2e) /* 46 */
-
-typedef struct linkedlist_datablock_internal_s
-{
-  struct linkedlist_datablock_internal_s* next_datablock;
-  uLong  avail_in_this_block;
-  uLong  filled_in_this_block;
-  uLong  unused; /* for future use and alignement */
-  unsigned char data[SIZEDATA_INDATABLOCK];
-} linkedlist_datablock_internal;
-
-typedef struct linkedlist_data_s
-{
-    linkedlist_datablock_internal* first_block;
-    linkedlist_datablock_internal* last_block;
-} linkedlist_data;
-
-
-typedef struct
-{
-    z_stream stream;            /* zLib stream structure for inflate */
-#ifdef HAVE_BZIP2
-    bz_stream bstream;          /* bzLib stream structure for bziped */
-#endif
-
-    int  stream_initialised;    /* 1 is stream is initialised */
-    uInt pos_in_buffered_data;  /* last written byte in buffered_data */
-
-    ZPOS64_T pos_local_header;     /* offset of the local header of the file
-                                     currenty writing */
-    char* central_header;       /* central header data for the current file */
-    uLong size_centralExtra;
-    uLong size_centralheader;   /* size of the central header for cur file */
-    uLong size_centralExtraFree; /* Extra bytes allocated to the centralheader but that are not used */
-    uLong flag;                 /* flag of the file currently writing */
-
-    int  method;                /* compression method of file currenty wr.*/
-    int  raw;                   /* 1 for directly writing raw data */
-    Byte buffered_data[Z_BUFSIZE];/* buffer contain compressed data to be writ*/
-    uLong dosDate;
-    uLong crc32;
-    int  encrypt;
-    int  zip64;               /* Add ZIP64 extened information in the extra field */
-    ZPOS64_T pos_zip64extrainfo;
-    ZPOS64_T totalCompressedData;
-    ZPOS64_T totalUncompressedData;
-#ifndef NOCRYPT
-    unsigned long keys[3];     /* keys defining the pseudo-random sequence */
-    const unsigned long* pcrc_32_tab;
-    int crypt_header_size;
-#endif
-} curfile64_info;
-
-typedef struct
-{
-    zlib_filefunc64_32_def z_filefunc;
-    voidpf filestream;        /* io structore of the zipfile */
-    linkedlist_data central_dir;/* datablock with central dir in construction*/
-    int  in_opened_file_inzip;  /* 1 if a file in the zip is currently writ.*/
-    curfile64_info ci;            /* info on the file curretly writing */
-
-    ZPOS64_T begin_pos;            /* position of the beginning of the zipfile */
-    ZPOS64_T add_position_when_writting_offset;
-    ZPOS64_T number_entry;
-
-#ifndef NO_ADDFILEINEXISTINGZIP
-    char *globalcomment;
-#endif
-
-} zip64_internal;
-
-
-#ifndef NOCRYPT
-#define INCLUDECRYPTINGCODE_IFCRYPTALLOWED
-#include "crypt.h"
-#endif
-
-local linkedlist_datablock_internal* allocate_new_datablock()
-{
-    linkedlist_datablock_internal* ldi;
-    ldi = (linkedlist_datablock_internal*)
-                 ALLOC(sizeof(linkedlist_datablock_internal));
-    if (ldi!=NULL)
-    {
-        ldi->next_datablock = NULL ;
-        ldi->filled_in_this_block = 0 ;
-        ldi->avail_in_this_block = SIZEDATA_INDATABLOCK ;
-    }
-    return ldi;
-}
-
-local void free_datablock(linkedlist_datablock_internal* ldi)
-{
-    while (ldi!=NULL)
-    {
-        linkedlist_datablock_internal* ldinext = ldi->next_datablock;
-        TRYFREE(ldi);
-        ldi = ldinext;
-    }
-}
-
-local void init_linkedlist(linkedlist_data* ll)
-{
-    ll->first_block = ll->last_block = NULL;
-}
-
-local void free_linkedlist(linkedlist_data* ll)
-{
-    free_datablock(ll->first_block);
-    ll->first_block = ll->last_block = NULL;
-}
-
-
-local int add_data_in_datablock(linkedlist_data* ll, const void* buf, uLong len)
-{
-    linkedlist_datablock_internal* ldi;
-    const unsigned char* from_copy;
-
-    if (ll==NULL)
-        return ZIP_INTERNALERROR;
-
-    if (ll->last_block == NULL)
-    {
-        ll->first_block = ll->last_block = allocate_new_datablock();
-        if (ll->first_block == NULL)
-            return ZIP_INTERNALERROR;
-    }
-
-    ldi = ll->last_block;
-    from_copy = (unsigned char*)buf;
-
-    while (len>0)
-    {
-        uInt copy_this;
-        uInt i;
-        unsigned char* to_copy;
-
-        if (ldi->avail_in_this_block==0)
-        {
-            ldi->next_datablock = allocate_new_datablock();
-            if (ldi->next_datablock == NULL)
-                return ZIP_INTERNALERROR;
-            ldi = ldi->next_datablock ;
-            ll->last_block = ldi;
-        }
-
-        if (ldi->avail_in_this_block < len)
-            copy_this = (uInt)ldi->avail_in_this_block;
-        else
-            copy_this = (uInt)len;
-
-        to_copy = &(ldi->data[ldi->filled_in_this_block]);
-
-        for (i=0;i<copy_this;i++)
-            *(to_copy+i)=*(from_copy+i);
-
-        ldi->filled_in_this_block += copy_this;
-        ldi->avail_in_this_block -= copy_this;
-        from_copy += copy_this ;
-        len -= copy_this;
-    }
-    return ZIP_OK;
-}
-
-
-
-/****************************************************************************/
-
-#ifndef NO_ADDFILEINEXISTINGZIP
-/* ===========================================================================
-   Inputs a long in LSB order to the given file
-   nbByte == 1, 2 ,4 or 8 (byte, short or long, ZPOS64_T)
-*/
-
-local int zip64local_putValue OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T x, int nbByte));
-local int zip64local_putValue (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T x, int nbByte)
-{
-    unsigned char buf[8];
-    int n;
-    for (n = 0; n < nbByte; n++)
-    {
-        buf[n] = (unsigned char)(x & 0xff);
-        x >>= 8;
-    }
-    if (x != 0)
-      {     /* data overflow - hack for ZIP64 (X Roche) */
-      for (n = 0; n < nbByte; n++)
-        {
-          buf[n] = 0xff;
-        }
-      }
-
-    if (ZWRITE64(*pzlib_filefunc_def,filestream,buf,nbByte)!=(uLong)nbByte)
-        return ZIP_ERRNO;
-    else
-        return ZIP_OK;
-}
-
-local void zip64local_putValue_inmemory OF((void* dest, ZPOS64_T x, int nbByte));
-local void zip64local_putValue_inmemory (void* dest, ZPOS64_T x, int nbByte)
-{
-    unsigned char* buf=(unsigned char*)dest;
-    int n;
-    for (n = 0; n < nbByte; n++) {
-        buf[n] = (unsigned char)(x & 0xff);
-        x >>= 8;
-    }
-
-    if (x != 0)
-    {     /* data overflow - hack for ZIP64 */
-       for (n = 0; n < nbByte; n++)
-       {
-          buf[n] = 0xff;
-       }
-    }
-}
-
-/****************************************************************************/
-
-
-local uLong zip64local_TmzDateToDosDate(const tm_zip* ptm)
-{
-    uLong year = (uLong)ptm->tm_year;
-    if (year>=1980)
-        year-=1980;
-    else if (year>=80)
-        year-=80;
-    return
-      (uLong) (((ptm->tm_mday) + (32 * (ptm->tm_mon+1)) + (512 * year)) << 16) |
-        ((ptm->tm_sec/2) + (32* ptm->tm_min) + (2048 * (uLong)ptm->tm_hour));
-}
-
-
-/****************************************************************************/
-
-local int zip64local_getByte OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, int *pi));
-
-local int zip64local_getByte(const zlib_filefunc64_32_def* pzlib_filefunc_def,voidpf filestream,int* pi)
-{
-    unsigned char c;
-    int err = (int)ZREAD64(*pzlib_filefunc_def,filestream,&c,1);
-    if (err==1)
-    {
-        *pi = (int)c;
-        return ZIP_OK;
-    }
-    else
-    {
-        if (ZERROR64(*pzlib_filefunc_def,filestream))
-            return ZIP_ERRNO;
-        else
-            return ZIP_EOF;
-    }
-}
-
-
-/* ===========================================================================
-   Reads a long in LSB order from the given gz_stream. Sets
-*/
-local int zip64local_getShort OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX));
-
-local int zip64local_getShort (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong* pX)
-{
-    uLong x ;
-    int i = 0;
-    int err;
-
-    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
-    x = (uLong)i;
-
-    if (err==ZIP_OK)
-        err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
-    x += ((uLong)i)<<8;
-
-    if (err==ZIP_OK)
-        *pX = x;
-    else
-        *pX = 0;
-    return err;
-}
-
-local int zip64local_getLong OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX));
-
-local int zip64local_getLong (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong* pX)
-{
-    uLong x ;
-    int i = 0;
-    int err;
-
-    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
-    x = (uLong)i;
-
-    if (err==ZIP_OK)
-        err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
-    x += ((uLong)i)<<8;
-
-    if (err==ZIP_OK)
-        err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
-    x += ((uLong)i)<<16;
-
-    if (err==ZIP_OK)
-        err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
-    x += ((uLong)i)<<24;
-
-    if (err==ZIP_OK)
-        *pX = x;
-    else
-        *pX = 0;
-    return err;
-}
-
-local int zip64local_getLong64 OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX));
-
-
-local int zip64local_getLong64 (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX)
-{
-  ZPOS64_T x;
-  int i = 0;
-  int err;
-
-  err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
-  x = (ZPOS64_T)i;
-
-  if (err==ZIP_OK)
-    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
-  x += ((ZPOS64_T)i)<<8;
-
-  if (err==ZIP_OK)
-    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
-  x += ((ZPOS64_T)i)<<16;
-
-  if (err==ZIP_OK)
-    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
-  x += ((ZPOS64_T)i)<<24;
-
-  if (err==ZIP_OK)
-    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
-  x += ((ZPOS64_T)i)<<32;
-
-  if (err==ZIP_OK)
-    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
-  x += ((ZPOS64_T)i)<<40;
-
-  if (err==ZIP_OK)
-    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
-  x += ((ZPOS64_T)i)<<48;
-
-  if (err==ZIP_OK)
-    err = zip64local_getByte(pzlib_filefunc_def,filestream,&i);
-  x += ((ZPOS64_T)i)<<56;
-
-  if (err==ZIP_OK)
-    *pX = x;
-  else
-    *pX = 0;
-
-  return err;
-}
-
-#ifndef BUFREADCOMMENT
-#define BUFREADCOMMENT (0x400)
-#endif
-/*
-  Locate the Central directory of a zipfile (at the end, just before
-    the global comment)
-*/
-local ZPOS64_T zip64local_SearchCentralDir OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream));
-
-local ZPOS64_T zip64local_SearchCentralDir(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)
-{
-  unsigned char* buf;
-  ZPOS64_T uSizeFile;
-  ZPOS64_T uBackRead;
-  ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */
-  ZPOS64_T uPosFound=0;
-
-  if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0)
-    return 0;
-
-
-  uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream);
-
-  if (uMaxBack>uSizeFile)
-    uMaxBack = uSizeFile;
-
-  buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4);
-  if (buf==NULL)
-    return 0;
-
-  uBackRead = 4;
-  while (uBackRead<uMaxBack)
-  {
-    uLong uReadSize;
-    ZPOS64_T uReadPos ;
-    int i;
-    if (uBackRead+BUFREADCOMMENT>uMaxBack)
-      uBackRead = uMaxBack;
-    else
-      uBackRead+=BUFREADCOMMENT;
-    uReadPos = uSizeFile-uBackRead ;
-
-    uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ?
-      (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos);
-    if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0)
-      break;
-
-    if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize)
-      break;
-
-    for (i=(int)uReadSize-3; (i--)>0;)
-      if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) &&
-        ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06))
-      {
-        uPosFound = uReadPos+i;
-        break;
-      }
-
-      if (uPosFound!=0)
-        break;
-  }
-  TRYFREE(buf);
-  return uPosFound;
-}
-
-/*
-Locate the End of Zip64 Central directory locator and from there find the CD of a zipfile (at the end, just before
-the global comment)
-*/
-local ZPOS64_T zip64local_SearchCentralDir64 OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream));
-
-local ZPOS64_T zip64local_SearchCentralDir64(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)
-{
-  unsigned char* buf;
-  ZPOS64_T uSizeFile;
-  ZPOS64_T uBackRead;
-  ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */
-  ZPOS64_T uPosFound=0;
-  uLong uL;
-  ZPOS64_T relativeOffset;
-
-  if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0)
-    return 0;
-
-  uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream);
-
-  if (uMaxBack>uSizeFile)
-    uMaxBack = uSizeFile;
-
-  buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4);
-  if (buf==NULL)
-    return 0;
-
-  uBackRead = 4;
-  while (uBackRead<uMaxBack)
-  {
-    uLong uReadSize;
-    ZPOS64_T uReadPos;
-    int i;
-    if (uBackRead+BUFREADCOMMENT>uMaxBack)
-      uBackRead = uMaxBack;
-    else
-      uBackRead+=BUFREADCOMMENT;
-    uReadPos = uSizeFile-uBackRead ;
-
-    uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ?
-      (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos);
-    if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0)
-      break;
-
-    if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize)
-      break;
-
-    for (i=(int)uReadSize-3; (i--)>0;)
-    {
-      // Signature "0x07064b50" Zip64 end of central directory locater
-      if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && ((*(buf+i+2))==0x06) && ((*(buf+i+3))==0x07))
-      {
-        uPosFound = uReadPos+i;
-        break;
-      }
-    }
-
-      if (uPosFound!=0)
-        break;
-  }
-
-  TRYFREE(buf);
-  if (uPosFound == 0)
-    return 0;
-
-  /* Zip64 end of central directory locator */
-  if (ZSEEK64(*pzlib_filefunc_def,filestream, uPosFound,ZLIB_FILEFUNC_SEEK_SET)!=0)
-    return 0;
-
-  /* the signature, already checked */
-  if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK)
-    return 0;
-
-  /* number of the disk with the start of the zip64 end of  central directory */
-  if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK)
-    return 0;
-  if (uL != 0)
-    return 0;
-
-  /* relative offset of the zip64 end of central directory record */
-  if (zip64local_getLong64(pzlib_filefunc_def,filestream,&relativeOffset)!=ZIP_OK)
-    return 0;
-
-  /* total number of disks */
-  if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK)
-    return 0;
-  if (uL != 1)
-    return 0;
-
-  /* Goto Zip64 end of central directory record */
-  if (ZSEEK64(*pzlib_filefunc_def,filestream, relativeOffset,ZLIB_FILEFUNC_SEEK_SET)!=0)
-    return 0;
-
-  /* the signature */
-  if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK)
-    return 0;
-
-  if (uL != 0x06064b50) // signature of 'Zip64 end of central directory'
-    return 0;
-
-  return relativeOffset;
-}
-
-int LoadCentralDirectoryRecord(zip64_internal* pziinit)
-{
-  int err=ZIP_OK;
-  ZPOS64_T byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/
-
-  ZPOS64_T size_central_dir;     /* size of the central directory  */
-  ZPOS64_T offset_central_dir;   /* offset of start of central directory */
-  ZPOS64_T central_pos;
-  uLong uL;
-
-  uLong number_disk;          /* number of the current dist, used for
-                              spaning ZIP, unsupported, always 0*/
-  uLong number_disk_with_CD;  /* number the the disk with central dir, used
-                              for spaning ZIP, unsupported, always 0*/
-  ZPOS64_T number_entry;
-  ZPOS64_T number_entry_CD;      /* total number of entries in
-                                the central dir
-                                (same than number_entry on nospan) */
-  uLong VersionMadeBy;
-  uLong VersionNeeded;
-  uLong size_comment;
-
-  int hasZIP64Record = 0;
-
-  // check first if we find a ZIP64 record
-  central_pos = zip64local_SearchCentralDir64(&pziinit->z_filefunc,pziinit->filestream);
-  if(central_pos > 0)
-  {
-    hasZIP64Record = 1;
-  }
-  else if(central_pos == 0)
-  {
-    central_pos = zip64local_SearchCentralDir(&pziinit->z_filefunc,pziinit->filestream);
-  }
-
-/* disable to allow appending to empty ZIP archive
-        if (central_pos==0)
-            err=ZIP_ERRNO;
-*/
-
-  if(hasZIP64Record)
-  {
-    ZPOS64_T sizeEndOfCentralDirectory;
-    if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, central_pos, ZLIB_FILEFUNC_SEEK_SET) != 0)
-      err=ZIP_ERRNO;
-
-    /* the signature, already checked */
-    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&uL)!=ZIP_OK)
-      err=ZIP_ERRNO;
-
-    /* size of zip64 end of central directory record */
-    if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream, &sizeEndOfCentralDirectory)!=ZIP_OK)
-      err=ZIP_ERRNO;
-
-    /* version made by */
-    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &VersionMadeBy)!=ZIP_OK)
-      err=ZIP_ERRNO;
-
-    /* version needed to extract */
-    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &VersionNeeded)!=ZIP_OK)
-      err=ZIP_ERRNO;
-
-    /* number of this disk */
-    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&number_disk)!=ZIP_OK)
-      err=ZIP_ERRNO;
-
-    /* number of the disk with the start of the central directory */
-    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&number_disk_with_CD)!=ZIP_OK)
-      err=ZIP_ERRNO;
-
-    /* total number of entries in the central directory on this disk */
-    if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream, &number_entry)!=ZIP_OK)
-      err=ZIP_ERRNO;
-
-    /* total number of entries in the central directory */
-    if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&number_entry_CD)!=ZIP_OK)
-      err=ZIP_ERRNO;
-
-    if ((number_entry_CD!=number_entry) || (number_disk_with_CD!=0) || (number_disk!=0))
-      err=ZIP_BADZIPFILE;
-
-    /* size of the central directory */
-    if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&size_central_dir)!=ZIP_OK)
-      err=ZIP_ERRNO;
-
-    /* offset of start of central directory with respect to the
-    starting disk number */
-    if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&offset_central_dir)!=ZIP_OK)
-      err=ZIP_ERRNO;
-
-    // TODO..
-    // read the comment from the standard central header.
-    size_comment = 0;
-  }
-  else
-  {
-    // Read End of central Directory info
-    if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0)
-      err=ZIP_ERRNO;
-
-    /* the signature, already checked */
-    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&uL)!=ZIP_OK)
-      err=ZIP_ERRNO;
-
-    /* number of this disk */
-    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream,&number_disk)!=ZIP_OK)
-      err=ZIP_ERRNO;
-
-    /* number of the disk with the start of the central directory */
-    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream,&number_disk_with_CD)!=ZIP_OK)
-      err=ZIP_ERRNO;
-
-    /* total number of entries in the central dir on this disk */
-    number_entry = 0;
-    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK)
-      err=ZIP_ERRNO;
-    else
-      number_entry = uL;
-
-    /* total number of entries in the central dir */
-    number_entry_CD = 0;
-    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK)
-      err=ZIP_ERRNO;
-    else
-      number_entry_CD = uL;
-
-    if ((number_entry_CD!=number_entry) || (number_disk_with_CD!=0) || (number_disk!=0))
-      err=ZIP_BADZIPFILE;
-
-    /* size of the central directory */
-    size_central_dir = 0;
-    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK)
-      err=ZIP_ERRNO;
-    else
-      size_central_dir = uL;
-
-    /* offset of start of central directory with respect to the starting disk number */
-    offset_central_dir = 0;
-    if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK)
-      err=ZIP_ERRNO;
-    else
-      offset_central_dir = uL;
-
-
-    /* zipfile global comment length */
-    if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &size_comment)!=ZIP_OK)
-      err=ZIP_ERRNO;
-  }
-
-  if ((central_pos<offset_central_dir+size_central_dir) &&
-    (err==ZIP_OK))
-    err=ZIP_BADZIPFILE;
-
-  if (err!=ZIP_OK)
-  {
-    ZCLOSE64(pziinit->z_filefunc, pziinit->filestream);
-    return ZIP_ERRNO;
-  }
-
-  if (size_comment>0)
-  {
-    pziinit->globalcomment = (char*)ALLOC(size_comment+1);
-    if (pziinit->globalcomment)
-    {
-      size_comment = ZREAD64(pziinit->z_filefunc, pziinit->filestream, pziinit->globalcomment,size_comment);
-      pziinit->globalcomment[size_comment]=0;
-    }
-  }
-
-  byte_before_the_zipfile = central_pos - (offset_central_dir+size_central_dir);
-  pziinit->add_position_when_writting_offset = byte_before_the_zipfile;
-
-  {
-    ZPOS64_T size_central_dir_to_read = size_central_dir;
-    size_t buf_size = SIZEDATA_INDATABLOCK;
-    void* buf_read = (void*)ALLOC(buf_size);
-    if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, offset_central_dir + byte_before_the_zipfile, ZLIB_FILEFUNC_SEEK_SET) != 0)
-      err=ZIP_ERRNO;
-
-    while ((size_central_dir_to_read>0) && (err==ZIP_OK))
-    {
-      ZPOS64_T read_this = SIZEDATA_INDATABLOCK;
-      if (read_this > size_central_dir_to_read)
-        read_this = size_central_dir_to_read;
-
-      if (ZREAD64(pziinit->z_filefunc, pziinit->filestream,buf_read,(uLong)read_this) != read_this)
-        err=ZIP_ERRNO;
-
-      if (err==ZIP_OK)
-        err = add_data_in_datablock(&pziinit->central_dir,buf_read, (uLong)read_this);
-
-      size_central_dir_to_read-=read_this;
-    }
-    TRYFREE(buf_read);
-  }
-  pziinit->begin_pos = byte_before_the_zipfile;
-  pziinit->number_entry = number_entry_CD;
-
-  if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, offset_central_dir+byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET) != 0)
-    err=ZIP_ERRNO;
-
-  return err;
-}
-
-
-#endif /* !NO_ADDFILEINEXISTINGZIP*/
-
-
-/************************************************************/
-extern zipFile ZEXPORT zipOpen3 (const void *pathname, int append, zipcharpc* globalcomment, zlib_filefunc64_32_def* pzlib_filefunc64_32_def)
-{
-    zip64_internal ziinit;
-    zip64_internal* zi;
-    int err=ZIP_OK;
-
-    ziinit.z_filefunc.zseek32_file = NULL;
-    ziinit.z_filefunc.ztell32_file = NULL;
-    if (pzlib_filefunc64_32_def==NULL)
-        fill_fopen64_filefunc(&ziinit.z_filefunc.zfile_func64);
-    else
-        ziinit.z_filefunc = *pzlib_filefunc64_32_def;
-
-    ziinit.filestream = ZOPEN64(ziinit.z_filefunc,
-                  pathname,
-                  (append == APPEND_STATUS_CREATE) ?
-                  (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_CREATE) :
-                    (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_EXISTING));
-
-    if (ziinit.filestream == NULL)
-        return NULL;
-
-    if (append == APPEND_STATUS_CREATEAFTER)
-        ZSEEK64(ziinit.z_filefunc,ziinit.filestream,0,SEEK_END);
-
-    ziinit.begin_pos = ZTELL64(ziinit.z_filefunc,ziinit.filestream);
-    ziinit.in_opened_file_inzip = 0;
-    ziinit.ci.stream_initialised = 0;
-    ziinit.number_entry = 0;
-    ziinit.add_position_when_writting_offset = 0;
-    init_linkedlist(&(ziinit.central_dir));
-
-
-
-    zi = (zip64_internal*)ALLOC(sizeof(zip64_internal));
-    if (zi==NULL)
-    {
-        ZCLOSE64(ziinit.z_filefunc,ziinit.filestream);
-        return NULL;
-    }
-
-    /* now we add file in a zipfile */
-#    ifndef NO_ADDFILEINEXISTINGZIP
-    ziinit.globalcomment = NULL;
-    if (append == APPEND_STATUS_ADDINZIP)
-    {
-      // Read and Cache Central Directory Records
-      err = LoadCentralDirectoryRecord(&ziinit);
-    }
-
-    if (globalcomment)
-    {
-      *globalcomment = ziinit.globalcomment;
-    }
-#    endif /* !NO_ADDFILEINEXISTINGZIP*/
-
-    if (err != ZIP_OK)
-    {
-#    ifndef NO_ADDFILEINEXISTINGZIP
-        TRYFREE(ziinit.globalcomment);
-#    endif /* !NO_ADDFILEINEXISTINGZIP*/
-        TRYFREE(zi);
-        return NULL;
-    }
-    else
-    {
-        *zi = ziinit;
-        return (zipFile)zi;
-    }
-}
-
-extern zipFile ZEXPORT zipOpen2 (const char *pathname, int append, zipcharpc* globalcomment, zlib_filefunc_def* pzlib_filefunc32_def)
-{
-    if (pzlib_filefunc32_def != NULL)
-    {
-        zlib_filefunc64_32_def zlib_filefunc64_32_def_fill;
-        fill_zlib_filefunc64_32_def_from_filefunc32(&zlib_filefunc64_32_def_fill,pzlib_filefunc32_def);
-        return zipOpen3(pathname, append, globalcomment, &zlib_filefunc64_32_def_fill);
-    }
-    else
-        return zipOpen3(pathname, append, globalcomment, NULL);
-}
-
-extern zipFile ZEXPORT zipOpen2_64 (const void *pathname, int append, zipcharpc* globalcomment, zlib_filefunc64_def* pzlib_filefunc_def)
-{
-    if (pzlib_filefunc_def != NULL)
-    {
-        zlib_filefunc64_32_def zlib_filefunc64_32_def_fill;
-        zlib_filefunc64_32_def_fill.zfile_func64 = *pzlib_filefunc_def;
-        zlib_filefunc64_32_def_fill.ztell32_file = NULL;
-        zlib_filefunc64_32_def_fill.zseek32_file = NULL;
-        return zipOpen3(pathname, append, globalcomment, &zlib_filefunc64_32_def_fill);
-    }
-    else
-        return zipOpen3(pathname, append, globalcomment, NULL);
-}
-
-
-
-extern zipFile ZEXPORT zipOpen (const char* pathname, int append)
-{
-    return zipOpen3((const void*)pathname,append,NULL,NULL);
-}
-
-extern zipFile ZEXPORT zipOpen64 (const void* pathname, int append)
-{
-    return zipOpen3(pathname,append,NULL,NULL);
-}
-
-int Write_LocalFileHeader(zip64_internal* zi, const char* filename, uInt size_extrafield_local, const void* extrafield_local)
-{
-  /* write the local header */
-  int err;
-  uInt size_filename = (uInt)strlen(filename);
-  uInt size_extrafield = size_extrafield_local;
-
-  err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)LOCALHEADERMAGIC, 4);
-
-  if (err==ZIP_OK)
-  {
-    if(zi->ci.zip64)
-      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2);/* version needed to extract */
-    else
-      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)20,2);/* version needed to extract */
-  }
-
-  if (err==ZIP_OK)
-    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.flag,2);
-
-  if (err==ZIP_OK)
-    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.method,2);
-
-  if (err==ZIP_OK)
-    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.dosDate,4);
-
-  // CRC / Compressed size / Uncompressed size will be filled in later and rewritten later
-  if (err==ZIP_OK)
-    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* crc 32, unknown */
-  if (err==ZIP_OK)
-  {
-    if(zi->ci.zip64)
-      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xFFFFFFFF,4); /* compressed size, unknown */
-    else
-      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* compressed size, unknown */
-  }
-  if (err==ZIP_OK)
-  {
-    if(zi->ci.zip64)
-      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xFFFFFFFF,4); /* uncompressed size, unknown */
-    else
-      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* uncompressed size, unknown */
-  }
-
-  if (err==ZIP_OK)
-    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_filename,2);
-
-  if(zi->ci.zip64)
-  {
-    size_extrafield += 20;
-  }
-
-  if (err==ZIP_OK)
-    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_extrafield,2);
-
-  if ((err==ZIP_OK) && (size_filename > 0))
-  {
-    if (ZWRITE64(zi->z_filefunc,zi->filestream,filename,size_filename)!=size_filename)
-      err = ZIP_ERRNO;
-  }
-
-  if ((err==ZIP_OK) && (size_extrafield_local > 0))
-  {
-    if (ZWRITE64(zi->z_filefunc, zi->filestream, extrafield_local, size_extrafield_local) != size_extrafield_local)
-      err = ZIP_ERRNO;
-  }
-
-
-  if ((err==ZIP_OK) && (zi->ci.zip64))
-  {
-      // write the Zip64 extended info
-      short HeaderID = 1;
-      short DataSize = 16;
-      ZPOS64_T CompressedSize = 0;
-      ZPOS64_T UncompressedSize = 0;
-
-      // Remember position of Zip64 extended info for the local file header. (needed when we update size after done with file)
-      zi->ci.pos_zip64extrainfo = ZTELL64(zi->z_filefunc,zi->filestream);
-
-      err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (short)HeaderID,2);
-      err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (short)DataSize,2);
-
-      err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (ZPOS64_T)UncompressedSize,8);
-      err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (ZPOS64_T)CompressedSize,8);
-  }
-
-  return err;
-}
-
-/*
- NOTE.
- When writing RAW the ZIP64 extended information in extrafield_local and extrafield_global needs to be stripped
- before calling this function it can be done with zipRemoveExtraInfoBlock
-
- It is not done here because then we need to realloc a new buffer since parameters are 'const' and I want to minimize
- unnecessary allocations.
- */
-extern int ZEXPORT zipOpenNewFileInZip4_64 (zipFile file, const char* filename, const zip_fileinfo* zipfi,
-                                         const void* extrafield_local, uInt size_extrafield_local,
-                                         const void* extrafield_global, uInt size_extrafield_global,
-                                         const char* comment, int method, int level, int raw,
-                                         int windowBits,int memLevel, int strategy,
-                                         const char* password, uLong crcForCrypting,
-                                         uLong versionMadeBy, uLong flagBase, int zip64)
-{
-    zip64_internal* zi;
-    uInt size_filename;
-    uInt size_comment;
-    uInt i;
-    int err = ZIP_OK;
-
-#    ifdef NOCRYPT
-    (crcForCrypting);
-    if (password != NULL)
-        return ZIP_PARAMERROR;
-#    endif
-
-    if (file == NULL)
-        return ZIP_PARAMERROR;
-
-#ifdef HAVE_BZIP2
-    if ((method!=0) && (method!=Z_DEFLATED) && (method!=Z_BZIP2ED))
-      return ZIP_PARAMERROR;
-#else
-    if ((method!=0) && (method!=Z_DEFLATED))
-      return ZIP_PARAMERROR;
-#endif
-
-    zi = (zip64_internal*)file;
-
-    if (zi->in_opened_file_inzip == 1)
-    {
-        err = zipCloseFileInZip (file);
-        if (err != ZIP_OK)
-            return err;
-    }
-
-    if (filename==NULL)
-        filename="-";
-
-    if (comment==NULL)
-        size_comment = 0;
-    else
-        size_comment = (uInt)strlen(comment);
-
-    size_filename = (uInt)strlen(filename);
-
-    if (zipfi == NULL)
-        zi->ci.dosDate = 0;
-    else
-    {
-        if (zipfi->dosDate != 0)
-            zi->ci.dosDate = zipfi->dosDate;
-        else
-          zi->ci.dosDate = zip64local_TmzDateToDosDate(&zipfi->tmz_date);
-    }
-
-    zi->ci.flag = flagBase;
-    if ((level==8) || (level==9))
-      zi->ci.flag |= 2;
-    if (level==2)
-      zi->ci.flag |= 4;
-    if (level==1)
-      zi->ci.flag |= 6;
-    if (password != NULL)
-      zi->ci.flag |= 1;
-
-    zi->ci.crc32 = 0;
-    zi->ci.method = method;
-    zi->ci.encrypt = 0;
-    zi->ci.stream_initialised = 0;
-    zi->ci.pos_in_buffered_data = 0;
-    zi->ci.raw = raw;
-    zi->ci.pos_local_header = ZTELL64(zi->z_filefunc,zi->filestream);
-
-    zi->ci.size_centralheader = SIZECENTRALHEADER + size_filename + size_extrafield_global + size_comment;
-    zi->ci.size_centralExtraFree = 32; // Extra space we have reserved in case we need to add ZIP64 extra info data
-
-    zi->ci.central_header = (char*)ALLOC((uInt)zi->ci.size_centralheader + zi->ci.size_centralExtraFree);
-
-    zi->ci.size_centralExtra = size_extrafield_global;
-    zip64local_putValue_inmemory(zi->ci.central_header,(uLong)CENTRALHEADERMAGIC,4);
-    /* version info */
-    zip64local_putValue_inmemory(zi->ci.central_header+4,(uLong)versionMadeBy,2);
-    zip64local_putValue_inmemory(zi->ci.central_header+6,(uLong)20,2);
-    zip64local_putValue_inmemory(zi->ci.central_header+8,(uLong)zi->ci.flag,2);
-    zip64local_putValue_inmemory(zi->ci.central_header+10,(uLong)zi->ci.method,2);
-    zip64local_putValue_inmemory(zi->ci.central_header+12,(uLong)zi->ci.dosDate,4);
-    zip64local_putValue_inmemory(zi->ci.central_header+16,(uLong)0,4); /*crc*/
-    zip64local_putValue_inmemory(zi->ci.central_header+20,(uLong)0,4); /*compr size*/
-    zip64local_putValue_inmemory(zi->ci.central_header+24,(uLong)0,4); /*uncompr size*/
-    zip64local_putValue_inmemory(zi->ci.central_header+28,(uLong)size_filename,2);
-    zip64local_putValue_inmemory(zi->ci.central_header+30,(uLong)size_extrafield_global,2);
-    zip64local_putValue_inmemory(zi->ci.central_header+32,(uLong)size_comment,2);
-    zip64local_putValue_inmemory(zi->ci.central_header+34,(uLong)0,2); /*disk nm start*/
-
-    if (zipfi==NULL)
-        zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)0,2);
-    else
-        zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)zipfi->internal_fa,2);
-
-    if (zipfi==NULL)
-        zip64local_putValue_inmemory(zi->ci.central_header+38,(uLong)0,4);
-    else
-        zip64local_putValue_inmemory(zi->ci.central_header+38,(uLong)zipfi->external_fa,4);
-
-    if(zi->ci.pos_local_header >= 0xffffffff)
-      zip64local_putValue_inmemory(zi->ci.central_header+42,(uLong)0xffffffff,4);
-    else
-      zip64local_putValue_inmemory(zi->ci.central_header+42,(uLong)zi->ci.pos_local_header - zi->add_position_when_writting_offset,4);
-
-    for (i=0;i<size_filename;i++)
-        *(zi->ci.central_header+SIZECENTRALHEADER+i) = *(filename+i);
-
-    for (i=0;i<size_extrafield_global;i++)
-        *(zi->ci.central_header+SIZECENTRALHEADER+size_filename+i) =
-              *(((const char*)extrafield_global)+i);
-
-    for (i=0;i<size_comment;i++)
-        *(zi->ci.central_header+SIZECENTRALHEADER+size_filename+
-              size_extrafield_global+i) = *(comment+i);
-    if (zi->ci.central_header == NULL)
-        return ZIP_INTERNALERROR;
-
-    zi->ci.zip64 = zip64;
-    zi->ci.totalCompressedData = 0;
-    zi->ci.totalUncompressedData = 0;
-    zi->ci.pos_zip64extrainfo = 0;
-
-    err = Write_LocalFileHeader(zi, filename, size_extrafield_local, extrafield_local);
-
-#ifdef HAVE_BZIP2
-    zi->ci.bstream.avail_in = (uInt)0;
-    zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE;
-    zi->ci.bstream.next_out = (char*)zi->ci.buffered_data;
-    zi->ci.bstream.total_in_hi32 = 0;
-    zi->ci.bstream.total_in_lo32 = 0;
-    zi->ci.bstream.total_out_hi32 = 0;
-    zi->ci.bstream.total_out_lo32 = 0;
-#endif
-
-    zi->ci.stream.avail_in = (uInt)0;
-    zi->ci.stream.avail_out = (uInt)Z_BUFSIZE;
-    zi->ci.stream.next_out = zi->ci.buffered_data;
-    zi->ci.stream.total_in = 0;
-    zi->ci.stream.total_out = 0;
-    zi->ci.stream.data_type = Z_BINARY;
-
-#ifdef HAVE_BZIP2
-    if ((err==ZIP_OK) && (zi->ci.method == Z_DEFLATED || zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw))
-#else
-    if ((err==ZIP_OK) && (zi->ci.method == Z_DEFLATED) && (!zi->ci.raw))
-#endif
-    {
-        if(zi->ci.method == Z_DEFLATED)
-        {
-          zi->ci.stream.zalloc = (alloc_func)0;
-          zi->ci.stream.zfree = (free_func)0;
-          zi->ci.stream.opaque = (voidpf)0;
-
-          if (windowBits>0)
-              windowBits = -windowBits;
-
-          err = deflateInit2(&zi->ci.stream, level, Z_DEFLATED, windowBits, memLevel, strategy);
-
-          if (err==Z_OK)
-              zi->ci.stream_initialised = Z_DEFLATED;
-        }
-        else if(zi->ci.method == Z_BZIP2ED)
-        {
-#ifdef HAVE_BZIP2
-            // Init BZip stuff here
-          zi->ci.bstream.bzalloc = 0;
-          zi->ci.bstream.bzfree = 0;
-          zi->ci.bstream.opaque = (voidpf)0;
-
-          err = BZ2_bzCompressInit(&zi->ci.bstream, level, 0,35);
-          if(err == BZ_OK)
-            zi->ci.stream_initialised = Z_BZIP2ED;
-#endif
-        }
-
-    }
-
-#    ifndef NOCRYPT
-    zi->ci.crypt_header_size = 0;
-    if ((err==Z_OK) && (password != NULL))
-    {
-        unsigned char bufHead[RAND_HEAD_LEN];
-        unsigned int sizeHead;
-        zi->ci.encrypt = 1;
-        zi->ci.pcrc_32_tab = get_crc_table();
-        /*init_keys(password,zi->ci.keys,zi->ci.pcrc_32_tab);*/
-
-        sizeHead=crypthead(password,bufHead,RAND_HEAD_LEN,zi->ci.keys,zi->ci.pcrc_32_tab,crcForCrypting);
-        zi->ci.crypt_header_size = sizeHead;
-
-        if (ZWRITE64(zi->z_filefunc,zi->filestream,bufHead,sizeHead) != sizeHead)
-                err = ZIP_ERRNO;
-    }
-#    endif
-
-    if (err==Z_OK)
-        zi->in_opened_file_inzip = 1;
-    return err;
-}
-
-extern int ZEXPORT zipOpenNewFileInZip4 (zipFile file, const char* filename, const zip_fileinfo* zipfi,
-                                         const void* extrafield_local, uInt size_extrafield_local,
-                                         const void* extrafield_global, uInt size_extrafield_global,
-                                         const char* comment, int method, int level, int raw,
-                                         int windowBits,int memLevel, int strategy,
-                                         const char* password, uLong crcForCrypting,
-                                         uLong versionMadeBy, uLong flagBase)
-{
-    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
-                                 extrafield_local, size_extrafield_local,
-                                 extrafield_global, size_extrafield_global,
-                                 comment, method, level, raw,
-                                 windowBits, memLevel, strategy,
-                                 password, crcForCrypting, versionMadeBy, flagBase, 0);
-}
-
-extern int ZEXPORT zipOpenNewFileInZip3 (zipFile file, const char* filename, const zip_fileinfo* zipfi,
-                                         const void* extrafield_local, uInt size_extrafield_local,
-                                         const void* extrafield_global, uInt size_extrafield_global,
-                                         const char* comment, int method, int level, int raw,
-                                         int windowBits,int memLevel, int strategy,
-                                         const char* password, uLong crcForCrypting)
-{
-    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
-                                 extrafield_local, size_extrafield_local,
-                                 extrafield_global, size_extrafield_global,
-                                 comment, method, level, raw,
-                                 windowBits, memLevel, strategy,
-                                 password, crcForCrypting, VERSIONMADEBY, 0, 0);
-}
-
-extern int ZEXPORT zipOpenNewFileInZip3_64(zipFile file, const char* filename, const zip_fileinfo* zipfi,
-                                         const void* extrafield_local, uInt size_extrafield_local,
-                                         const void* extrafield_global, uInt size_extrafield_global,
-                                         const char* comment, int method, int level, int raw,
-                                         int windowBits,int memLevel, int strategy,
-                                         const char* password, uLong crcForCrypting, int zip64)
-{
-    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
-                                 extrafield_local, size_extrafield_local,
-                                 extrafield_global, size_extrafield_global,
-                                 comment, method, level, raw,
-                                 windowBits, memLevel, strategy,
-                                 password, crcForCrypting, VERSIONMADEBY, 0, zip64);
-}
-
-extern int ZEXPORT zipOpenNewFileInZip2(zipFile file, const char* filename, const zip_fileinfo* zipfi,
-                                        const void* extrafield_local, uInt size_extrafield_local,
-                                        const void* extrafield_global, uInt size_extrafield_global,
-                                        const char* comment, int method, int level, int raw)
-{
-    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
-                                 extrafield_local, size_extrafield_local,
-                                 extrafield_global, size_extrafield_global,
-                                 comment, method, level, raw,
-                                 -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
-                                 NULL, 0, VERSIONMADEBY, 0, 0);
-}
-
-extern int ZEXPORT zipOpenNewFileInZip2_64(zipFile file, const char* filename, const zip_fileinfo* zipfi,
-                                        const void* extrafield_local, uInt size_extrafield_local,
-                                        const void* extrafield_global, uInt size_extrafield_global,
-                                        const char* comment, int method, int level, int raw, int zip64)
-{
-    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
-                                 extrafield_local, size_extrafield_local,
-                                 extrafield_global, size_extrafield_global,
-                                 comment, method, level, raw,
-                                 -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
-                                 NULL, 0, VERSIONMADEBY, 0, zip64);
-}
-
-extern int ZEXPORT zipOpenNewFileInZip64 (zipFile file, const char* filename, const zip_fileinfo* zipfi,
-                                        const void* extrafield_local, uInt size_extrafield_local,
-                                        const void*extrafield_global, uInt size_extrafield_global,
-                                        const char* comment, int method, int level, int zip64)
-{
-    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
-                                 extrafield_local, size_extrafield_local,
-                                 extrafield_global, size_extrafield_global,
-                                 comment, method, level, 0,
-                                 -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
-                                 NULL, 0, VERSIONMADEBY, 0, zip64);
-}
-
-extern int ZEXPORT zipOpenNewFileInZip (zipFile file, const char* filename, const zip_fileinfo* zipfi,
-                                        const void* extrafield_local, uInt size_extrafield_local,
-                                        const void*extrafield_global, uInt size_extrafield_global,
-                                        const char* comment, int method, int level)
-{
-    return zipOpenNewFileInZip4_64 (file, filename, zipfi,
-                                 extrafield_local, size_extrafield_local,
-                                 extrafield_global, size_extrafield_global,
-                                 comment, method, level, 0,
-                                 -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY,
-                                 NULL, 0, VERSIONMADEBY, 0, 0);
-}
-
-local int zip64FlushWriteBuffer(zip64_internal* zi)
-{
-    int err=ZIP_OK;
-
-    if (zi->ci.encrypt != 0)
-    {
-#ifndef NOCRYPT
-        uInt i;
-        int t;
-        for (i=0;i<zi->ci.pos_in_buffered_data;i++)
-            zi->ci.buffered_data[i] = zencode(zi->ci.keys, zi->ci.pcrc_32_tab, zi->ci.buffered_data[i],t);
-#endif
-    }
-
-    if (ZWRITE64(zi->z_filefunc,zi->filestream,zi->ci.buffered_data,zi->ci.pos_in_buffered_data) != zi->ci.pos_in_buffered_data)
-      err = ZIP_ERRNO;
-
-    zi->ci.totalCompressedData += zi->ci.pos_in_buffered_data;
-
-#ifdef HAVE_BZIP2
-    if(zi->ci.method == Z_BZIP2ED)
-    {
-      zi->ci.totalUncompressedData += zi->ci.bstream.total_in_lo32;
-      zi->ci.bstream.total_in_lo32 = 0;
-      zi->ci.bstream.total_in_hi32 = 0;
-    }
-    else
-#endif
-    {
-      zi->ci.totalUncompressedData += zi->ci.stream.total_in;
-      zi->ci.stream.total_in = 0;
-    }
-
-
-    zi->ci.pos_in_buffered_data = 0;
-
-    return err;
-}
-
-extern int ZEXPORT zipWriteInFileInZip (zipFile file,const void* buf,unsigned int len)
-{
-    zip64_internal* zi;
-    int err=ZIP_OK;
-
-    if (file == NULL)
-        return ZIP_PARAMERROR;
-    zi = (zip64_internal*)file;
-
-    if (zi->in_opened_file_inzip == 0)
-        return ZIP_PARAMERROR;
-
-    zi->ci.crc32 = crc32(zi->ci.crc32,buf,(uInt)len);
-
-#ifdef HAVE_BZIP2
-    if(zi->ci.method == Z_BZIP2ED && (!zi->ci.raw))
-    {
-      zi->ci.bstream.next_in = (void*)buf;
-      zi->ci.bstream.avail_in = len;
-      err = BZ_RUN_OK;
-
-      while ((err==BZ_RUN_OK) && (zi->ci.bstream.avail_in>0))
-      {
-        if (zi->ci.bstream.avail_out == 0)
-        {
-          if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO)
-            err = ZIP_ERRNO;
-          zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE;
-          zi->ci.bstream.next_out = (char*)zi->ci.buffered_data;
-        }
-
-
-        if(err != BZ_RUN_OK)
-          break;
-
-        if ((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw))
-        {
-          uLong uTotalOutBefore_lo = zi->ci.bstream.total_out_lo32;
-//          uLong uTotalOutBefore_hi = zi->ci.bstream.total_out_hi32;
-          err=BZ2_bzCompress(&zi->ci.bstream,  BZ_RUN);
-
-          zi->ci.pos_in_buffered_data += (uInt)(zi->ci.bstream.total_out_lo32 - uTotalOutBefore_lo) ;
-        }
-      }
-
-      if(err == BZ_RUN_OK)
-        err = ZIP_OK;
-    }
-    else
-#endif
-    {
-      zi->ci.stream.next_in = (Bytef*)buf;
-      zi->ci.stream.avail_in = len;
-
-      while ((err==ZIP_OK) && (zi->ci.stream.avail_in>0))
-      {
-          if (zi->ci.stream.avail_out == 0)
-          {
-              if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO)
-                  err = ZIP_ERRNO;
-              zi->ci.stream.avail_out = (uInt)Z_BUFSIZE;
-              zi->ci.stream.next_out = zi->ci.buffered_data;
-          }
-
-
-          if(err != ZIP_OK)
-              break;
-
-          if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw))
-          {
-              uLong uTotalOutBefore = zi->ci.stream.total_out;
-              err=deflate(&zi->ci.stream,  Z_NO_FLUSH);
-              if(uTotalOutBefore > zi->ci.stream.total_out)
-              {
-                int bBreak = 0;
-                bBreak++;
-              }
-
-              zi->ci.pos_in_buffered_data += (uInt)(zi->ci.stream.total_out - uTotalOutBefore) ;
-          }
-          else
-          {
-              uInt copy_this,i;
-              if (zi->ci.stream.avail_in < zi->ci.stream.avail_out)
-                  copy_this = zi->ci.stream.avail_in;
-              else
-                  copy_this = zi->ci.stream.avail_out;
-
-              for (i = 0; i < copy_this; i++)
-                  *(((char*)zi->ci.stream.next_out)+i) =
-                      *(((const char*)zi->ci.stream.next_in)+i);
-              {
-                  zi->ci.stream.avail_in -= copy_this;
-                  zi->ci.stream.avail_out-= copy_this;
-                  zi->ci.stream.next_in+= copy_this;
-                  zi->ci.stream.next_out+= copy_this;
-                  zi->ci.stream.total_in+= copy_this;
-                  zi->ci.stream.total_out+= copy_this;
-                  zi->ci.pos_in_buffered_data += copy_this;
-              }
-          }
-      }// while(...)
-    }
-
-    return err;
-}
-
-extern int ZEXPORT zipCloseFileInZipRaw (zipFile file, uLong uncompressed_size, uLong crc32)
-{
-    return zipCloseFileInZipRaw64 (file, uncompressed_size, crc32);
-}
-
-extern int ZEXPORT zipCloseFileInZipRaw64 (zipFile file, ZPOS64_T uncompressed_size, uLong crc32)
-{
-    zip64_internal* zi;
-    ZPOS64_T compressed_size;
-    uLong invalidValue = 0xffffffff;
-    short datasize = 0;
-    int err=ZIP_OK;
-
-    if (file == NULL)
-        return ZIP_PARAMERROR;
-    zi = (zip64_internal*)file;
-
-    if (zi->in_opened_file_inzip == 0)
-        return ZIP_PARAMERROR;
-    zi->ci.stream.avail_in = 0;
-
-    if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw))
-                {
-                        while (err==ZIP_OK)
-                        {
-                                uLong uTotalOutBefore;
-                                if (zi->ci.stream.avail_out == 0)
-                                {
-                                        if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO)
-                                                err = ZIP_ERRNO;
-                                        zi->ci.stream.avail_out = (uInt)Z_BUFSIZE;
-                                        zi->ci.stream.next_out = zi->ci.buffered_data;
-                                }
-                                uTotalOutBefore = zi->ci.stream.total_out;
-                                err=deflate(&zi->ci.stream,  Z_FINISH);
-                                zi->ci.pos_in_buffered_data += (uInt)(zi->ci.stream.total_out - uTotalOutBefore) ;
-                        }
-                }
-    else if ((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw))
-    {
-#ifdef HAVE_BZIP2
-      err = BZ_FINISH_OK;
-      while (err==BZ_FINISH_OK)
-      {
-        uLong uTotalOutBefore;
-        if (zi->ci.bstream.avail_out == 0)
-        {
-          if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO)
-            err = ZIP_ERRNO;
-          zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE;
-          zi->ci.bstream.next_out = (char*)zi->ci.buffered_data;
-        }
-        uTotalOutBefore = zi->ci.bstream.total_out_lo32;
-        err=BZ2_bzCompress(&zi->ci.bstream,  BZ_FINISH);
-        if(err == BZ_STREAM_END)
-          err = Z_STREAM_END;
-
-        zi->ci.pos_in_buffered_data += (uInt)(zi->ci.bstream.total_out_lo32 - uTotalOutBefore);
-      }
-
-      if(err == BZ_FINISH_OK)
-        err = ZIP_OK;
-#endif
-    }
-
-    if (err==Z_STREAM_END)
-        err=ZIP_OK; /* this is normal */
-
-    if ((zi->ci.pos_in_buffered_data>0) && (err==ZIP_OK))
-                {
-        if (zip64FlushWriteBuffer(zi)==ZIP_ERRNO)
-            err = ZIP_ERRNO;
-                }
-
-    if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw))
-    {
-        int tmp_err = deflateEnd(&zi->ci.stream);
-        if (err == ZIP_OK)
-            err = tmp_err;
-        zi->ci.stream_initialised = 0;
-    }
-#ifdef HAVE_BZIP2
-    else if((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw))
-    {
-      int tmperr = BZ2_bzCompressEnd(&zi->ci.bstream);
-                        if (err==ZIP_OK)
-                                err = tmperr;
-                        zi->ci.stream_initialised = 0;
-    }
-#endif
-
-    if (!zi->ci.raw)
-    {
-        crc32 = (uLong)zi->ci.crc32;
-        uncompressed_size = zi->ci.totalUncompressedData;
-    }
-    compressed_size = zi->ci.totalCompressedData;
-
-#    ifndef NOCRYPT
-    compressed_size += zi->ci.crypt_header_size;
-#    endif
-
-    // update Current Item crc and sizes,
-    if(compressed_size >= 0xffffffff || uncompressed_size >= 0xffffffff || zi->ci.pos_local_header >= 0xffffffff)
-    {
-      /*version Made by*/
-      zip64local_putValue_inmemory(zi->ci.central_header+4,(uLong)45,2);
-      /*version needed*/
-      zip64local_putValue_inmemory(zi->ci.central_header+6,(uLong)45,2);
-
-    }
-
-    zip64local_putValue_inmemory(zi->ci.central_header+16,crc32,4); /*crc*/
-
-
-    if(compressed_size >= 0xffffffff)
-      zip64local_putValue_inmemory(zi->ci.central_header+20, invalidValue,4); /*compr size*/
-    else
-      zip64local_putValue_inmemory(zi->ci.central_header+20, compressed_size,4); /*compr size*/
-
-    /// set internal file attributes field
-    if (zi->ci.stream.data_type == Z_ASCII)
-        zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)Z_ASCII,2);
-
-    if(uncompressed_size >= 0xffffffff)
-      zip64local_putValue_inmemory(zi->ci.central_header+24, invalidValue,4); /*uncompr size*/
-    else
-      zip64local_putValue_inmemory(zi->ci.central_header+24, uncompressed_size,4); /*uncompr size*/
-
-    // Add ZIP64 extra info field for uncompressed size
-    if(uncompressed_size >= 0xffffffff)
-      datasize += 8;
-
-    // Add ZIP64 extra info field for compressed size
-    if(compressed_size >= 0xffffffff)
-      datasize += 8;
-
-    // Add ZIP64 extra info field for relative offset to local file header of current file
-    if(zi->ci.pos_local_header >= 0xffffffff)
-      datasize += 8;
-
-    if(datasize > 0)
-    {
-      char* p = NULL;
-
-      if((uLong)(datasize + 4) > zi->ci.size_centralExtraFree)
-      {
-        // we can not write more data to the buffer that we have room for.
-        return ZIP_BADZIPFILE;
-      }
-
-      p = zi->ci.central_header + zi->ci.size_centralheader;
-
-      // Add Extra Information Header for 'ZIP64 information'
-      zip64local_putValue_inmemory(p, 0x0001, 2); // HeaderID
-      p += 2;
-      zip64local_putValue_inmemory(p, datasize, 2); // DataSize
-      p += 2;
-
-      if(uncompressed_size >= 0xffffffff)
-      {
-        zip64local_putValue_inmemory(p, uncompressed_size, 8);
-        p += 8;
-      }
-
-      if(compressed_size >= 0xffffffff)
-      {
-        zip64local_putValue_inmemory(p, compressed_size, 8);
-        p += 8;
-      }
-
-      if(zi->ci.pos_local_header >= 0xffffffff)
-      {
-        zip64local_putValue_inmemory(p, zi->ci.pos_local_header, 8);
-        p += 8;
-      }
-
-      // Update how much extra free space we got in the memory buffer
-      // and increase the centralheader size so the new ZIP64 fields are included
-      // ( 4 below is the size of HeaderID and DataSize field )
-      zi->ci.size_centralExtraFree -= datasize + 4;
-      zi->ci.size_centralheader += datasize + 4;
-
-      // Update the extra info size field
-      zi->ci.size_centralExtra += datasize + 4;
-      zip64local_putValue_inmemory(zi->ci.central_header+30,(uLong)zi->ci.size_centralExtra,2);
-    }
-
-    if (err==ZIP_OK)
-        err = add_data_in_datablock(&zi->central_dir, zi->ci.central_header, (uLong)zi->ci.size_centralheader);
-
-    free(zi->ci.central_header);
-
-    if (err==ZIP_OK)
-    {
-        // Update the LocalFileHeader with the new values.
-
-        ZPOS64_T cur_pos_inzip = ZTELL64(zi->z_filefunc,zi->filestream);
-
-        if (ZSEEK64(zi->z_filefunc,zi->filestream, zi->ci.pos_local_header + 14,ZLIB_FILEFUNC_SEEK_SET)!=0)
-            err = ZIP_ERRNO;
-
-        if (err==ZIP_OK)
-            err = zip64local_putValue(&zi->z_filefunc,zi->filestream,crc32,4); /* crc 32, unknown */
-
-        if(uncompressed_size >= 0xffffffff || compressed_size >= 0xffffffff )
-        {
-          if(zi->ci.pos_zip64extrainfo > 0)
-          {
-            // Update the size in the ZIP64 extended field.
-            if (ZSEEK64(zi->z_filefunc,zi->filestream, zi->ci.pos_zip64extrainfo + 4,ZLIB_FILEFUNC_SEEK_SET)!=0)
-              err = ZIP_ERRNO;
-
-            if (err==ZIP_OK) /* compressed size, unknown */
-              err = zip64local_putValue(&zi->z_filefunc, zi->filestream, uncompressed_size, 8);
-
-            if (err==ZIP_OK) /* uncompressed size, unknown */
-              err = zip64local_putValue(&zi->z_filefunc, zi->filestream, compressed_size, 8);
-          }
-          else
-              err = ZIP_BADZIPFILE; // Caller passed zip64 = 0, so no room for zip64 info -> fatal
-        }
-        else
-        {
-          if (err==ZIP_OK) /* compressed size, unknown */
-              err = zip64local_putValue(&zi->z_filefunc,zi->filestream,compressed_size,4);
-
-          if (err==ZIP_OK) /* uncompressed size, unknown */
-              err = zip64local_putValue(&zi->z_filefunc,zi->filestream,uncompressed_size,4);
-        }
-
-        if (ZSEEK64(zi->z_filefunc,zi->filestream, cur_pos_inzip,ZLIB_FILEFUNC_SEEK_SET)!=0)
-            err = ZIP_ERRNO;
-    }
-
-    zi->number_entry ++;
-    zi->in_opened_file_inzip = 0;
-
-    return err;
-}
-
-extern int ZEXPORT zipCloseFileInZip (zipFile file)
-{
-    return zipCloseFileInZipRaw (file,0,0);
-}
-
-int Write_Zip64EndOfCentralDirectoryLocator(zip64_internal* zi, ZPOS64_T zip64eocd_pos_inzip)
-{
-  int err = ZIP_OK;
-  ZPOS64_T pos = zip64eocd_pos_inzip - zi->add_position_when_writting_offset;
-
-  err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ZIP64ENDLOCHEADERMAGIC,4);
-
-  /*num disks*/
-    if (err==ZIP_OK) /* number of the disk with the start of the central directory */
-      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4);
-
-  /*relative offset*/
-    if (err==ZIP_OK) /* Relative offset to the Zip64EndOfCentralDirectory */
-      err = zip64local_putValue(&zi->z_filefunc,zi->filestream, pos,8);
-
-  /*total disks*/ /* Do not support spawning of disk so always say 1 here*/
-    if (err==ZIP_OK) /* number of the disk with the start of the central directory */
-      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)1,4);
-
-    return err;
-}
-
-int Write_Zip64EndOfCentralDirectoryRecord(zip64_internal* zi, uLong size_centraldir, ZPOS64_T centraldir_pos_inzip)
-{
-  int err = ZIP_OK;
-
-  uLong Zip64DataSize = 44;
-
-  err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ZIP64ENDHEADERMAGIC,4);
-
-  if (err==ZIP_OK) /* size of this 'zip64 end of central directory' */
-    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(ZPOS64_T)Zip64DataSize,8); // why ZPOS64_T of this ?
-
-  if (err==ZIP_OK) /* version made by */
-    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2);
-
-  if (err==ZIP_OK) /* version needed */
-    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2);
-
-  if (err==ZIP_OK) /* number of this disk */
-    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4);
-
-  if (err==ZIP_OK) /* number of the disk with the start of the central directory */
-    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4);
-
-  if (err==ZIP_OK) /* total number of entries in the central dir on this disk */
-    err = zip64local_putValue(&zi->z_filefunc, zi->filestream, zi->number_entry, 8);
-
-  if (err==ZIP_OK) /* total number of entries in the central dir */
-    err = zip64local_putValue(&zi->z_filefunc, zi->filestream, zi->number_entry, 8);
-
-  if (err==ZIP_OK) /* size of the central directory */
-    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(ZPOS64_T)size_centraldir,8);
-
-  if (err==ZIP_OK) /* offset of start of central directory with respect to the starting disk number */
-  {
-    ZPOS64_T pos = centraldir_pos_inzip - zi->add_position_when_writting_offset;
-    err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (ZPOS64_T)pos,8);
-  }
-  return err;
-}
-int Write_EndOfCentralDirectoryRecord(zip64_internal* zi, uLong size_centraldir, ZPOS64_T centraldir_pos_inzip)
-{
-  int err = ZIP_OK;
-
-  /*signature*/
-  err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ENDHEADERMAGIC,4);
-
-  if (err==ZIP_OK) /* number of this disk */
-    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,2);
-
-  if (err==ZIP_OK) /* number of the disk with the start of the central directory */
-    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,2);
-
-  if (err==ZIP_OK) /* total number of entries in the central dir on this disk */
-  {
-    {
-      if(zi->number_entry >= 0xFFFF)
-        err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xffff,2); // use value in ZIP64 record
-      else
-        err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->number_entry,2);
-    }
-  }
-
-  if (err==ZIP_OK) /* total number of entries in the central dir */
-  {
-    if(zi->number_entry >= 0xFFFF)
-      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xffff,2); // use value in ZIP64 record
-    else
-      err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->number_entry,2);
-  }
-
-  if (err==ZIP_OK) /* size of the central directory */
-    err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_centraldir,4);
-
-  if (err==ZIP_OK) /* offset of start of central directory with respect to the starting disk number */
-  {
-    ZPOS64_T pos = centraldir_pos_inzip - zi->add_position_when_writting_offset;
-    if(pos >= 0xffffffff)
-    {
-      err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (uLong)0xffffffff,4);
-    }
-    else
-      err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (uLong)(centraldir_pos_inzip - zi->add_position_when_writting_offset),4);
-  }
-
-   return err;
-}
-
-int Write_GlobalComment(zip64_internal* zi, const char* global_comment)
-{
-  int err = ZIP_OK;
-  uInt size_global_comment = 0;
-
-  if(global_comment != NULL)
-    size_global_comment = (uInt)strlen(global_comment);
-
-  err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_global_comment,2);
-
-  if (err == ZIP_OK && size_global_comment > 0)
-  {
-    if (ZWRITE64(zi->z_filefunc,zi->filestream, global_comment, size_global_comment) != size_global_comment)
-      err = ZIP_ERRNO;
-  }
-  return err;
-}
-
-extern int ZEXPORT zipClose (zipFile file, const char* global_comment)
-{
-    zip64_internal* zi;
-    int err = 0;
-    uLong size_centraldir = 0;
-    ZPOS64_T centraldir_pos_inzip;
-    ZPOS64_T pos;
-
-    if (file == NULL)
-        return ZIP_PARAMERROR;
-
-    zi = (zip64_internal*)file;
-
-    if (zi->in_opened_file_inzip == 1)
-    {
-        err = zipCloseFileInZip (file);
-    }
-
-#ifndef NO_ADDFILEINEXISTINGZIP
-    if (global_comment==NULL)
-        global_comment = zi->globalcomment;
-#endif
-
-    centraldir_pos_inzip = ZTELL64(zi->z_filefunc,zi->filestream);
-
-    if (err==ZIP_OK)
-    {
-        linkedlist_datablock_internal* ldi = zi->central_dir.first_block;
-        while (ldi!=NULL)
-        {
-            if ((err==ZIP_OK) && (ldi->filled_in_this_block>0))
-            {
-                if (ZWRITE64(zi->z_filefunc,zi->filestream, ldi->data, ldi->filled_in_this_block) != ldi->filled_in_this_block)
-                    err = ZIP_ERRNO;
-            }
-
-            size_centraldir += ldi->filled_in_this_block;
-            ldi = ldi->next_datablock;
-        }
-    }
-    free_linkedlist(&(zi->central_dir));
-
-    pos = centraldir_pos_inzip - zi->add_position_when_writting_offset;
-    if(pos >= 0xffffffff || zi->number_entry > 0xFFFF)
-    {
-      ZPOS64_T Zip64EOCDpos = ZTELL64(zi->z_filefunc,zi->filestream);
-      Write_Zip64EndOfCentralDirectoryRecord(zi, size_centraldir, centraldir_pos_inzip);
-
-      Write_Zip64EndOfCentralDirectoryLocator(zi, Zip64EOCDpos);
-    }
-
-    if (err==ZIP_OK)
-      err = Write_EndOfCentralDirectoryRecord(zi, size_centraldir, centraldir_pos_inzip);
-
-    if(err == ZIP_OK)
-      err = Write_GlobalComment(zi, global_comment);
-
-    if (ZCLOSE64(zi->z_filefunc,zi->filestream) != 0)
-        if (err == ZIP_OK)
-            err = ZIP_ERRNO;
-
-#ifndef NO_ADDFILEINEXISTINGZIP
-    TRYFREE(zi->globalcomment);
-#endif
-    TRYFREE(zi);
-
-    return err;
-}
-
-extern int ZEXPORT zipRemoveExtraInfoBlock (char* pData, int* dataLen, short sHeader)
-{
-  char* p = pData;
-  int size = 0;
-  char* pNewHeader;
-  char* pTmp;
-  short header;
-  short dataSize;
-
-  int retVal = ZIP_OK;
-
-  if(pData == NULL || *dataLen < 4)
-    return ZIP_PARAMERROR;
-
-  pNewHeader = (char*)ALLOC(*dataLen);
-  pTmp = pNewHeader;
-
-  while(p < (pData + *dataLen))
-  {
-    header = *(short*)p;
-    dataSize = *(((short*)p)+1);
-
-    if( header == sHeader ) // Header found.
-    {
-      p += dataSize + 4; // skip it. do not copy to temp buffer
-    }
-    else
-    {
-      // Extra Info block should not be removed, So copy it to the temp buffer.
-      memcpy(pTmp, p, dataSize + 4);
-      p += dataSize + 4;
-      size += dataSize + 4;
-    }
-
-  }
-
-  if(size < *dataLen)
-  {
-    // clean old extra info block.
-    memset(pData,0, *dataLen);
-
-    // copy the new extra info block over the old
-    if(size > 0)
-      memcpy(pData, pNewHeader, size);
-
-    // set the new extra info size
-    *dataLen = size;
-
-    retVal = ZIP_OK;
-  }
-  else
-    retVal = ZIP_ERRNO;
-
-  TRYFREE(pNewHeader);
-
-  return retVal;
-}
--- a/Resources/minizip/zip.h	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,362 +0,0 @@
-/* zip.h -- IO on .zip files using zlib
-   Version 1.1, February 14h, 2010
-   part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html )
-
-         Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html )
-
-         Modifications for Zip64 support
-         Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com )
-
-         For more info read MiniZip_info.txt
-
-         ---------------------------------------------------------------------------
-
-   Condition of use and distribution are the same than zlib :
-
-  This software is provided 'as-is', without any express or implied
-  warranty.  In no event will the authors be held liable for any damages
-  arising from the use of this software.
-
-  Permission is granted to anyone to use this software for any purpose,
-  including commercial applications, and to alter it and redistribute it
-  freely, subject to the following restrictions:
-
-  1. The origin of this software must not be misrepresented; you must not
-     claim that you wrote the original software. If you use this software
-     in a product, an acknowledgment in the product documentation would be
-     appreciated but is not required.
-  2. Altered source versions must be plainly marked as such, and must not be
-     misrepresented as being the original software.
-  3. This notice may not be removed or altered from any source distribution.
-
-        ---------------------------------------------------------------------------
-
-        Changes
-
-        See header of zip.h
-
-*/
-
-#ifndef _zip12_H
-#define _zip12_H
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-//#define HAVE_BZIP2
-
-#ifndef _ZLIB_H
-#include "zlib.h"
-#endif
-
-#ifndef _ZLIBIOAPI_H
-#include "ioapi.h"
-#endif
-
-#ifdef HAVE_BZIP2
-#include "bzlib.h"
-#endif
-
-#define Z_BZIP2ED 12
-
-#if defined(STRICTZIP) || defined(STRICTZIPUNZIP)
-/* like the STRICT of WIN32, we define a pointer that cannot be converted
-    from (void*) without cast */
-typedef struct TagzipFile__ { int unused; } zipFile__;
-typedef zipFile__ *zipFile;
-#else
-typedef voidp zipFile;
-#endif
-
-#define ZIP_OK                          (0)
-#define ZIP_EOF                         (0)
-#define ZIP_ERRNO                       (Z_ERRNO)
-#define ZIP_PARAMERROR                  (-102)
-#define ZIP_BADZIPFILE                  (-103)
-#define ZIP_INTERNALERROR               (-104)
-
-#ifndef DEF_MEM_LEVEL
-#  if MAX_MEM_LEVEL >= 8
-#    define DEF_MEM_LEVEL 8
-#  else
-#    define DEF_MEM_LEVEL  MAX_MEM_LEVEL
-#  endif
-#endif
-/* default memLevel */
-
-/* tm_zip contain date/time info */
-typedef struct tm_zip_s
-{
-    uInt tm_sec;            /* seconds after the minute - [0,59] */
-    uInt tm_min;            /* minutes after the hour - [0,59] */
-    uInt tm_hour;           /* hours since midnight - [0,23] */
-    uInt tm_mday;           /* day of the month - [1,31] */
-    uInt tm_mon;            /* months since January - [0,11] */
-    uInt tm_year;           /* years - [1980..2044] */
-} tm_zip;
-
-typedef struct
-{
-    tm_zip      tmz_date;       /* date in understandable format           */
-    uLong       dosDate;       /* if dos_date == 0, tmu_date is used      */
-/*    uLong       flag;        */   /* general purpose bit flag        2 bytes */
-
-    uLong       internal_fa;    /* internal file attributes        2 bytes */
-    uLong       external_fa;    /* external file attributes        4 bytes */
-} zip_fileinfo;
-
-typedef const char* zipcharpc;
-
-
-#define APPEND_STATUS_CREATE        (0)
-#define APPEND_STATUS_CREATEAFTER   (1)
-#define APPEND_STATUS_ADDINZIP      (2)
-
-extern zipFile ZEXPORT zipOpen OF((const char *pathname, int append));
-extern zipFile ZEXPORT zipOpen64 OF((const void *pathname, int append));
-/*
-  Create a zipfile.
-     pathname contain on Windows XP a filename like "c:\\zlib\\zlib113.zip" or on
-       an Unix computer "zlib/zlib113.zip".
-     if the file pathname exist and append==APPEND_STATUS_CREATEAFTER, the zip
-       will be created at the end of the file.
-         (useful if the file contain a self extractor code)
-     if the file pathname exist and append==APPEND_STATUS_ADDINZIP, we will
-       add files in existing zip (be sure you don't add file that doesn't exist)
-     If the zipfile cannot be opened, the return value is NULL.
-     Else, the return value is a zipFile Handle, usable with other function
-       of this zip package.
-*/
-
-/* Note : there is no delete function into a zipfile.
-   If you want delete file into a zipfile, you must open a zipfile, and create another
-   Of couse, you can use RAW reading and writing to copy the file you did not want delte
-*/
-
-extern zipFile ZEXPORT zipOpen2 OF((const char *pathname,
-                                   int append,
-                                   zipcharpc* globalcomment,
-                                   zlib_filefunc_def* pzlib_filefunc_def));
-
-extern zipFile ZEXPORT zipOpen2_64 OF((const void *pathname,
-                                   int append,
-                                   zipcharpc* globalcomment,
-                                   zlib_filefunc64_def* pzlib_filefunc_def));
-
-extern int ZEXPORT zipOpenNewFileInZip OF((zipFile file,
-                       const char* filename,
-                       const zip_fileinfo* zipfi,
-                       const void* extrafield_local,
-                       uInt size_extrafield_local,
-                       const void* extrafield_global,
-                       uInt size_extrafield_global,
-                       const char* comment,
-                       int method,
-                       int level));
-
-extern int ZEXPORT zipOpenNewFileInZip64 OF((zipFile file,
-                       const char* filename,
-                       const zip_fileinfo* zipfi,
-                       const void* extrafield_local,
-                       uInt size_extrafield_local,
-                       const void* extrafield_global,
-                       uInt size_extrafield_global,
-                       const char* comment,
-                       int method,
-                       int level,
-                       int zip64));
-
-/*
-  Open a file in the ZIP for writing.
-  filename : the filename in zip (if NULL, '-' without quote will be used
-  *zipfi contain supplemental information
-  if extrafield_local!=NULL and size_extrafield_local>0, extrafield_local
-    contains the extrafield data the the local header
-  if extrafield_global!=NULL and size_extrafield_global>0, extrafield_global
-    contains the extrafield data the the local header
-  if comment != NULL, comment contain the comment string
-  method contain the compression method (0 for store, Z_DEFLATED for deflate)
-  level contain the level of compression (can be Z_DEFAULT_COMPRESSION)
-  zip64 is set to 1 if a zip64 extended information block should be added to the local file header.
-                    this MUST be '1' if the uncompressed size is >= 0xffffffff.
-
-*/
-
-
-extern int ZEXPORT zipOpenNewFileInZip2 OF((zipFile file,
-                                            const char* filename,
-                                            const zip_fileinfo* zipfi,
-                                            const void* extrafield_local,
-                                            uInt size_extrafield_local,
-                                            const void* extrafield_global,
-                                            uInt size_extrafield_global,
-                                            const char* comment,
-                                            int method,
-                                            int level,
-                                            int raw));
-
-
-extern int ZEXPORT zipOpenNewFileInZip2_64 OF((zipFile file,
-                                            const char* filename,
-                                            const zip_fileinfo* zipfi,
-                                            const void* extrafield_local,
-                                            uInt size_extrafield_local,
-                                            const void* extrafield_global,
-                                            uInt size_extrafield_global,
-                                            const char* comment,
-                                            int method,
-                                            int level,
-                                            int raw,
-                                            int zip64));
-/*
-  Same than zipOpenNewFileInZip, except if raw=1, we write raw file
- */
-
-extern int ZEXPORT zipOpenNewFileInZip3 OF((zipFile file,
-                                            const char* filename,
-                                            const zip_fileinfo* zipfi,
-                                            const void* extrafield_local,
-                                            uInt size_extrafield_local,
-                                            const void* extrafield_global,
-                                            uInt size_extrafield_global,
-                                            const char* comment,
-                                            int method,
-                                            int level,
-                                            int raw,
-                                            int windowBits,
-                                            int memLevel,
-                                            int strategy,
-                                            const char* password,
-                                            uLong crcForCrypting));
-
-extern int ZEXPORT zipOpenNewFileInZip3_64 OF((zipFile file,
-                                            const char* filename,
-                                            const zip_fileinfo* zipfi,
-                                            const void* extrafield_local,
-                                            uInt size_extrafield_local,
-                                            const void* extrafield_global,
-                                            uInt size_extrafield_global,
-                                            const char* comment,
-                                            int method,
-                                            int level,
-                                            int raw,
-                                            int windowBits,
-                                            int memLevel,
-                                            int strategy,
-                                            const char* password,
-                                            uLong crcForCrypting,
-                                            int zip64
-                                            ));
-
-/*
-  Same than zipOpenNewFileInZip2, except
-    windowBits,memLevel,,strategy : see parameter strategy in deflateInit2
-    password : crypting password (NULL for no crypting)
-    crcForCrypting : crc of file to compress (needed for crypting)
- */
-
-extern int ZEXPORT zipOpenNewFileInZip4 OF((zipFile file,
-                                            const char* filename,
-                                            const zip_fileinfo* zipfi,
-                                            const void* extrafield_local,
-                                            uInt size_extrafield_local,
-                                            const void* extrafield_global,
-                                            uInt size_extrafield_global,
-                                            const char* comment,
-                                            int method,
-                                            int level,
-                                            int raw,
-                                            int windowBits,
-                                            int memLevel,
-                                            int strategy,
-                                            const char* password,
-                                            uLong crcForCrypting,
-                                            uLong versionMadeBy,
-                                            uLong flagBase
-                                            ));
-
-
-extern int ZEXPORT zipOpenNewFileInZip4_64 OF((zipFile file,
-                                            const char* filename,
-                                            const zip_fileinfo* zipfi,
-                                            const void* extrafield_local,
-                                            uInt size_extrafield_local,
-                                            const void* extrafield_global,
-                                            uInt size_extrafield_global,
-                                            const char* comment,
-                                            int method,
-                                            int level,
-                                            int raw,
-                                            int windowBits,
-                                            int memLevel,
-                                            int strategy,
-                                            const char* password,
-                                            uLong crcForCrypting,
-                                            uLong versionMadeBy,
-                                            uLong flagBase,
-                                            int zip64
-                                            ));
-/*
-  Same than zipOpenNewFileInZip4, except
-    versionMadeBy : value for Version made by field
-    flag : value for flag field (compression level info will be added)
- */
-
-
-extern int ZEXPORT zipWriteInFileInZip OF((zipFile file,
-                       const void* buf,
-                       unsigned len));
-/*
-  Write data in the zipfile
-*/
-
-extern int ZEXPORT zipCloseFileInZip OF((zipFile file));
-/*
-  Close the current file in the zipfile
-*/
-
-extern int ZEXPORT zipCloseFileInZipRaw OF((zipFile file,
-                                            uLong uncompressed_size,
-                                            uLong crc32));
-
-extern int ZEXPORT zipCloseFileInZipRaw64 OF((zipFile file,
-                                            ZPOS64_T uncompressed_size,
-                                            uLong crc32));
-
-/*
-  Close the current file in the zipfile, for file opened with
-    parameter raw=1 in zipOpenNewFileInZip2
-  uncompressed_size and crc32 are value for the uncompressed size
-*/
-
-extern int ZEXPORT zipClose OF((zipFile file,
-                const char* global_comment));
-/*
-  Close the zipfile
-*/
-
-
-extern int ZEXPORT zipRemoveExtraInfoBlock OF((char* pData, int* dataLen, short sHeader));
-/*
-  zipRemoveExtraInfoBlock -  Added by Mathias Svensson
-
-  Remove extra information block from a extra information data for the local file header or central directory header
-
-  It is needed to remove ZIP64 extra information blocks when before data is written if using RAW mode.
-
-  0x0001 is the signature header for the ZIP64 extra information blocks
-
-  usage.
-                        Remove ZIP64 Extra information from a central director extra field data
-              zipRemoveExtraInfoBlock(pCenDirExtraFieldData, &nCenDirExtraFieldDataLen, 0x0001);
-
-                        Remove ZIP64 Extra information from a Local File Header extra field data
-        zipRemoveExtraInfoBlock(pLocalHeaderExtraFieldData, &nLocalHeaderExtraFieldDataLen, 0x0001);
-*/
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* _zip64_H */
--- a/UnitTestsSources/DicomMap.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,130 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
- **/
-
-
-#include "PrecompiledHeadersUnitTests.h"
-#include "gtest/gtest.h"
-
-#include "../Core/Uuid.h"
-#include "../Core/OrthancException.h"
-#include "../Core/DicomFormat/DicomMap.h"
-#include "../Core/DicomFormat/DicomNullValue.h"
-
-#include <memory>
-
-using namespace Orthanc;
-
-TEST(DicomMap, MainTags)
-{
-  ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID));
-  ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID, ResourceType_Patient));
-  ASSERT_FALSE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID, ResourceType_Study));
-
-  ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_STUDY_INSTANCE_UID));
-  ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_ACCESSION_NUMBER));
-  ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_SERIES_INSTANCE_UID));
-  ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_SOP_INSTANCE_UID));
-
-  std::set<DicomTag> s;
-  DicomMap::GetMainDicomTags(s);
-  ASSERT_TRUE(s.end() != s.find(DICOM_TAG_PATIENT_ID));
-  ASSERT_TRUE(s.end() != s.find(DICOM_TAG_STUDY_INSTANCE_UID));
-  ASSERT_TRUE(s.end() != s.find(DICOM_TAG_ACCESSION_NUMBER));
-  ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SERIES_INSTANCE_UID));
-  ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SOP_INSTANCE_UID));
-
-  DicomMap::GetMainDicomTags(s, ResourceType_Patient);
-  ASSERT_TRUE(s.end() != s.find(DICOM_TAG_PATIENT_ID));
-  ASSERT_TRUE(s.end() == s.find(DICOM_TAG_STUDY_INSTANCE_UID));
-
-  DicomMap::GetMainDicomTags(s, ResourceType_Study);
-  ASSERT_TRUE(s.end() != s.find(DICOM_TAG_STUDY_INSTANCE_UID));
-  ASSERT_TRUE(s.end() != s.find(DICOM_TAG_ACCESSION_NUMBER));
-  ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID));
-
-  DicomMap::GetMainDicomTags(s, ResourceType_Series);
-  ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SERIES_INSTANCE_UID));
-  ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID));
-
-  DicomMap::GetMainDicomTags(s, ResourceType_Instance);
-  ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SOP_INSTANCE_UID));
-  ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID));
-}
-
-
-TEST(DicomMap, Tags)
-{
-  DicomMap m;
-  ASSERT_FALSE(m.HasTag(DICOM_TAG_PATIENT_NAME));
-  ASSERT_FALSE(m.HasTag(0x0010, 0x0010));
-  m.SetValue(0x0010, 0x0010, "PatientName");
-  ASSERT_TRUE(m.HasTag(DICOM_TAG_PATIENT_NAME));
-  ASSERT_TRUE(m.HasTag(0x0010, 0x0010));
-
-  ASSERT_FALSE(m.HasTag(DICOM_TAG_PATIENT_ID));
-  m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID");
-  ASSERT_TRUE(m.HasTag(0x0010, 0x0020));
-  m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID2");
-  ASSERT_EQ("PatientID2", m.GetValue(0x0010, 0x0020).AsString());
-
-  m.Remove(DICOM_TAG_PATIENT_ID);
-  ASSERT_THROW(m.GetValue(0x0010, 0x0020), OrthancException);
-
-  std::auto_ptr<DicomMap> mm(m.Clone());
-  ASSERT_EQ("PatientName", mm->GetValue(DICOM_TAG_PATIENT_NAME).AsString());  
-
-  m.SetValue(DICOM_TAG_PATIENT_ID, "Hello");
-  ASSERT_THROW(mm->GetValue(DICOM_TAG_PATIENT_ID), OrthancException);
-  mm->CopyTagIfExists(m, DICOM_TAG_PATIENT_ID);
-  ASSERT_EQ("Hello", mm->GetValue(DICOM_TAG_PATIENT_ID).AsString());  
-
-  DicomNullValue v;
-  ASSERT_TRUE(v.IsNull());
-}
-
-
-TEST(DicomMap, FindTemplates)
-{
-  DicomMap m;
-
-  DicomMap::SetupFindPatientTemplate(m);
-  ASSERT_TRUE(m.HasTag(DICOM_TAG_PATIENT_ID));
-
-  DicomMap::SetupFindStudyTemplate(m);
-  ASSERT_TRUE(m.HasTag(DICOM_TAG_STUDY_INSTANCE_UID));
-  ASSERT_TRUE(m.HasTag(DICOM_TAG_ACCESSION_NUMBER));
-
-  DicomMap::SetupFindSeriesTemplate(m);
-  ASSERT_TRUE(m.HasTag(DICOM_TAG_SERIES_INSTANCE_UID));
-
-  DicomMap::SetupFindInstanceTemplate(m);
-  ASSERT_TRUE(m.HasTag(DICOM_TAG_SOP_INSTANCE_UID));
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/DicomMapTests.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,189 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "PrecompiledHeadersUnitTests.h"
+#include "gtest/gtest.h"
+
+#include "../Core/Uuid.h"
+#include "../Core/OrthancException.h"
+#include "../Core/DicomFormat/DicomMap.h"
+#include "../Core/DicomFormat/DicomNullValue.h"
+#include "../OrthancServer/FromDcmtkBridge.h"
+
+#include <memory>
+
+using namespace Orthanc;
+
+TEST(DicomMap, MainTags)
+{
+  ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID));
+  ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID, ResourceType_Patient));
+  ASSERT_FALSE(DicomMap::IsMainDicomTag(DICOM_TAG_PATIENT_ID, ResourceType_Study));
+
+  ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_STUDY_INSTANCE_UID));
+  ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_ACCESSION_NUMBER));
+  ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_SERIES_INSTANCE_UID));
+  ASSERT_TRUE(DicomMap::IsMainDicomTag(DICOM_TAG_SOP_INSTANCE_UID));
+
+  std::set<DicomTag> s;
+  DicomMap::GetMainDicomTags(s);
+  ASSERT_TRUE(s.end() != s.find(DICOM_TAG_PATIENT_ID));
+  ASSERT_TRUE(s.end() != s.find(DICOM_TAG_STUDY_INSTANCE_UID));
+  ASSERT_TRUE(s.end() != s.find(DICOM_TAG_ACCESSION_NUMBER));
+  ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SERIES_INSTANCE_UID));
+  ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SOP_INSTANCE_UID));
+
+  DicomMap::GetMainDicomTags(s, ResourceType_Patient);
+  ASSERT_TRUE(s.end() != s.find(DICOM_TAG_PATIENT_ID));
+  ASSERT_TRUE(s.end() == s.find(DICOM_TAG_STUDY_INSTANCE_UID));
+
+  DicomMap::GetMainDicomTags(s, ResourceType_Study);
+  ASSERT_TRUE(s.end() != s.find(DICOM_TAG_STUDY_INSTANCE_UID));
+  ASSERT_TRUE(s.end() != s.find(DICOM_TAG_ACCESSION_NUMBER));
+  ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID));
+
+  DicomMap::GetMainDicomTags(s, ResourceType_Series);
+  ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SERIES_INSTANCE_UID));
+  ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID));
+
+  DicomMap::GetMainDicomTags(s, ResourceType_Instance);
+  ASSERT_TRUE(s.end() != s.find(DICOM_TAG_SOP_INSTANCE_UID));
+  ASSERT_TRUE(s.end() == s.find(DICOM_TAG_PATIENT_ID));
+}
+
+
+TEST(DicomMap, Tags)
+{
+  DicomMap m;
+  ASSERT_FALSE(m.HasTag(DICOM_TAG_PATIENT_NAME));
+  ASSERT_FALSE(m.HasTag(0x0010, 0x0010));
+  m.SetValue(0x0010, 0x0010, "PatientName");
+  ASSERT_TRUE(m.HasTag(DICOM_TAG_PATIENT_NAME));
+  ASSERT_TRUE(m.HasTag(0x0010, 0x0010));
+
+  ASSERT_FALSE(m.HasTag(DICOM_TAG_PATIENT_ID));
+  m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID");
+  ASSERT_TRUE(m.HasTag(0x0010, 0x0020));
+  m.SetValue(DICOM_TAG_PATIENT_ID, "PatientID2");
+  ASSERT_EQ("PatientID2", m.GetValue(0x0010, 0x0020).AsString());
+
+  m.Remove(DICOM_TAG_PATIENT_ID);
+  ASSERT_THROW(m.GetValue(0x0010, 0x0020), OrthancException);
+
+  std::auto_ptr<DicomMap> mm(m.Clone());
+  ASSERT_EQ("PatientName", mm->GetValue(DICOM_TAG_PATIENT_NAME).AsString());  
+
+  m.SetValue(DICOM_TAG_PATIENT_ID, "Hello");
+  ASSERT_THROW(mm->GetValue(DICOM_TAG_PATIENT_ID), OrthancException);
+  mm->CopyTagIfExists(m, DICOM_TAG_PATIENT_ID);
+  ASSERT_EQ("Hello", mm->GetValue(DICOM_TAG_PATIENT_ID).AsString());  
+
+  DicomNullValue v;
+  ASSERT_TRUE(v.IsNull());
+}
+
+
+TEST(DicomMap, FindTemplates)
+{
+  DicomMap m;
+
+  DicomMap::SetupFindPatientTemplate(m);
+  ASSERT_TRUE(m.HasTag(DICOM_TAG_PATIENT_ID));
+
+  DicomMap::SetupFindStudyTemplate(m);
+  ASSERT_TRUE(m.HasTag(DICOM_TAG_STUDY_INSTANCE_UID));
+  ASSERT_TRUE(m.HasTag(DICOM_TAG_ACCESSION_NUMBER));
+
+  DicomMap::SetupFindSeriesTemplate(m);
+  ASSERT_TRUE(m.HasTag(DICOM_TAG_SERIES_INSTANCE_UID));
+
+  DicomMap::SetupFindInstanceTemplate(m);
+  ASSERT_TRUE(m.HasTag(DICOM_TAG_SOP_INSTANCE_UID));
+}
+
+
+
+
+static void TestModule(ResourceType level)
+{
+  std::set<DicomTag> module, main;
+  DicomTag::GetTagsForModule(module, level);
+  DicomMap::GetMainDicomTags(main, level);
+  
+  // The main dicom tags are a subset of the module
+  for (std::set<DicomTag>::const_iterator it = main.begin(); it != main.end(); it++)
+  {
+    bool ok = module.find(*it) != module.end();
+
+    // Exceptions for the Series level
+    /*if ((//
+          *it == DicomTag(0x, 0x) && 
+          level == ResourceType_Series))
+    {
+      ok = true;
+      }*/
+
+    // Exceptions for the Instance level
+    if ((/* Accession number, from Image module */
+          *it == DicomTag(0x0020, 0x0012) && 
+          level == ResourceType_Instance) ||
+        (/* Image Index, from PET Image module */
+          *it == DicomTag(0x0054, 0x1330) && 
+          level == ResourceType_Instance) ||
+        (/* Temporal Position Identifier, from MR Image module */
+          *it == DicomTag(0x0020, 0x0100) && 
+          level == ResourceType_Instance) ||
+        (/* Number of Frames, from Multi-frame module attributes, related to Image IOD */
+          *it == DicomTag(0x0028, 0x0008) && 
+          level == ResourceType_Instance ))
+    {
+      ok = true;
+    }
+
+    if (!ok)
+    {
+      std::cout << it->Format() << ": " << FromDcmtkBridge::GetName(*it)
+                << " not expected at level " << EnumerationToString(level) << std::endl;
+    }
+
+    EXPECT_TRUE(ok);
+  }
+}
+
+
+TEST(DicomMap, Modules)
+{
+  TestModule(ResourceType_Patient);
+  TestModule(ResourceType_Study);
+  //TestModule(ResourceType_Series);   // TODO
+  TestModule(ResourceType_Instance);
+}
--- a/UnitTestsSources/FileStorage.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,228 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
- **/
-
-
-#include "PrecompiledHeadersUnitTests.h"
-#include "gtest/gtest.h"
-
-#include <ctype.h>
-#include <glog/logging.h>
-
-#include "../Core/FileStorage/FileStorage.h"
-#include "../OrthancServer/ServerIndex.h"
-#include "../Core/Toolbox.h"
-#include "../Core/OrthancException.h"
-#include "../Core/Uuid.h"
-#include "../Core/HttpServer/FilesystemHttpSender.h"
-#include "../Core/HttpServer/BufferHttpSender.h"
-#include "../Core/FileStorage/FileStorageAccessor.h"
-#include "../Core/FileStorage/CompressedFileStorageAccessor.h"
-
-using namespace Orthanc;
-
-
-static void StringToVector(std::vector<uint8_t>& v,
-                           const std::string& s)
-{
-  v.resize(s.size());
-  for (size_t i = 0; i < s.size(); i++)
-    v[i] = s[i];
-}
-
-
-TEST(FileStorage, Basic)
-{
-  FileStorage s("UnitTestsStorage");
-
-  std::string data = Toolbox::GenerateUuid();
-  std::string uid = s.Create(data);
-  std::string d;
-  s.ReadFile(d, uid);
-  ASSERT_EQ(d.size(), data.size());
-  ASSERT_FALSE(memcmp(&d[0], &data[0], data.size()));
-  ASSERT_EQ(s.GetCompressedSize(uid), data.size());
-}
-
-TEST(FileStorage, Basic2)
-{
-  FileStorage s("UnitTestsStorage");
-
-  std::vector<uint8_t> data;
-  StringToVector(data, Toolbox::GenerateUuid());
-  std::string uid = s.Create(data);
-  std::string d;
-  s.ReadFile(d, uid);
-  ASSERT_EQ(d.size(), data.size());
-  ASSERT_FALSE(memcmp(&d[0], &data[0], data.size()));
-  ASSERT_EQ(s.GetCompressedSize(uid), data.size());
-}
-
-TEST(FileStorage, EndToEnd)
-{
-  FileStorage s("UnitTestsStorage");
-  s.Clear();
-
-  std::list<std::string> u;
-  for (unsigned int i = 0; i < 10; i++)
-  {
-    u.push_back(s.Create(Toolbox::GenerateUuid()));
-  }
-
-  std::set<std::string> ss;
-  s.ListAllFiles(ss);
-  ASSERT_EQ(10u, ss.size());
-  
-  unsigned int c = 0;
-  for (std::list<std::string>::iterator
-         i = u.begin(); i != u.end(); i++, c++)
-  {
-    ASSERT_TRUE(ss.find(*i) != ss.end());
-    if (c < 5)
-      s.Remove(*i);
-  }
-
-  s.ListAllFiles(ss);
-  ASSERT_EQ(5u, ss.size());
-
-  s.Clear();
-  s.ListAllFiles(ss);
-  ASSERT_EQ(0u, ss.size());
-}
-
-
-TEST(FileStorageAccessor, Simple)
-{
-  FileStorage s("UnitTestsStorage");
-  FileStorageAccessor accessor(s);
-
-  std::string data = "Hello world";
-  FileInfo info = accessor.Write(data, FileContentType_Dicom);
-  
-  std::string r;
-  accessor.Read(r, info.GetUuid());
-
-  ASSERT_EQ(data, r);
-  ASSERT_EQ(CompressionType_None, info.GetCompressionType());
-  ASSERT_EQ(11u, info.GetUncompressedSize());
-  ASSERT_EQ(11u, info.GetCompressedSize());
-  ASSERT_EQ(FileContentType_Dicom, info.GetContentType());
-}
-
-
-TEST(FileStorageAccessor, NoCompression)
-{
-  FileStorage s("UnitTestsStorage");
-  CompressedFileStorageAccessor accessor(s);
-
-  accessor.SetCompressionForNextOperations(CompressionType_None);
-  std::string data = "Hello world";
-  FileInfo info = accessor.Write(data, FileContentType_Dicom);
-  
-  std::string r;
-  accessor.Read(r, info.GetUuid());
-
-  ASSERT_EQ(data, r);
-  ASSERT_EQ(CompressionType_None, info.GetCompressionType());
-  ASSERT_EQ(11u, info.GetUncompressedSize());
-  ASSERT_EQ(11u, info.GetCompressedSize());
-  ASSERT_EQ(FileContentType_Dicom, info.GetContentType());
-}
-
-
-TEST(FileStorageAccessor, NoCompression2)
-{
-  FileStorage s("UnitTestsStorage");
-  CompressedFileStorageAccessor accessor(s);
-
-  accessor.SetCompressionForNextOperations(CompressionType_None);
-  std::vector<uint8_t> data;
-  StringToVector(data, "Hello world");
-  FileInfo info = accessor.Write(data, FileContentType_Dicom);
-  
-  std::string r;
-  accessor.Read(r, info.GetUuid());
-
-  ASSERT_EQ(0, memcmp(&r[0], &data[0], data.size()));
-  ASSERT_EQ(CompressionType_None, info.GetCompressionType());
-  ASSERT_EQ(11u, info.GetUncompressedSize());
-  ASSERT_EQ(11u, info.GetCompressedSize());
-  ASSERT_EQ(FileContentType_Dicom, info.GetContentType());
-}
-
-
-TEST(FileStorageAccessor, Compression)
-{
-  FileStorage s("UnitTestsStorage");
-  CompressedFileStorageAccessor accessor(s);
-
-  accessor.SetCompressionForNextOperations(CompressionType_Zlib);
-  std::string data = "Hello world";
-  FileInfo info = accessor.Write(data, FileContentType_Dicom);
-  
-  std::string r;
-  accessor.Read(r, info.GetUuid());
-
-  ASSERT_EQ(data, r);
-  ASSERT_EQ(CompressionType_Zlib, info.GetCompressionType());
-  ASSERT_EQ(11u, info.GetUncompressedSize());
-  ASSERT_EQ(FileContentType_Dicom, info.GetContentType());
-}
-
-
-TEST(FileStorageAccessor, Mix)
-{
-  FileStorage s("UnitTestsStorage");
-  CompressedFileStorageAccessor accessor(s);
-
-  std::string r;
-  std::string compressedData = "Hello";
-  std::string uncompressedData = "HelloWorld";
-
-  accessor.SetCompressionForNextOperations(CompressionType_Zlib);
-  FileInfo compressedInfo = accessor.Write(compressedData, FileContentType_Dicom);
-  
-  accessor.SetCompressionForNextOperations(CompressionType_None);
-  FileInfo uncompressedInfo = accessor.Write(uncompressedData, FileContentType_Dicom);
-  
-  accessor.SetCompressionForNextOperations(CompressionType_Zlib);
-  accessor.Read(r, compressedInfo.GetUuid());
-  ASSERT_EQ(compressedData, r);
-
-  accessor.SetCompressionForNextOperations(CompressionType_None);
-  accessor.Read(r, compressedInfo.GetUuid());
-  ASSERT_NE(compressedData, r);
-
-  /*
-  // This test is too slow on Windows
-  accessor.SetCompressionForNextOperations(CompressionType_Zlib);
-  ASSERT_THROW(accessor.Read(r, uncompressedInfo.GetUuid()), OrthancException);
-  */
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/FileStorageTests.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,228 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "PrecompiledHeadersUnitTests.h"
+#include "gtest/gtest.h"
+
+#include <ctype.h>
+#include <glog/logging.h>
+
+#include "../Core/FileStorage/FileStorage.h"
+#include "../OrthancServer/ServerIndex.h"
+#include "../Core/Toolbox.h"
+#include "../Core/OrthancException.h"
+#include "../Core/Uuid.h"
+#include "../Core/HttpServer/FilesystemHttpSender.h"
+#include "../Core/HttpServer/BufferHttpSender.h"
+#include "../Core/FileStorage/FileStorageAccessor.h"
+#include "../Core/FileStorage/CompressedFileStorageAccessor.h"
+
+using namespace Orthanc;
+
+
+static void StringToVector(std::vector<uint8_t>& v,
+                           const std::string& s)
+{
+  v.resize(s.size());
+  for (size_t i = 0; i < s.size(); i++)
+    v[i] = s[i];
+}
+
+
+TEST(FileStorage, Basic)
+{
+  FileStorage s("UnitTestsStorage");
+
+  std::string data = Toolbox::GenerateUuid();
+  std::string uid = s.Create(data);
+  std::string d;
+  s.ReadFile(d, uid);
+  ASSERT_EQ(d.size(), data.size());
+  ASSERT_FALSE(memcmp(&d[0], &data[0], data.size()));
+  ASSERT_EQ(s.GetCompressedSize(uid), data.size());
+}
+
+TEST(FileStorage, Basic2)
+{
+  FileStorage s("UnitTestsStorage");
+
+  std::vector<uint8_t> data;
+  StringToVector(data, Toolbox::GenerateUuid());
+  std::string uid = s.Create(data);
+  std::string d;
+  s.ReadFile(d, uid);
+  ASSERT_EQ(d.size(), data.size());
+  ASSERT_FALSE(memcmp(&d[0], &data[0], data.size()));
+  ASSERT_EQ(s.GetCompressedSize(uid), data.size());
+}
+
+TEST(FileStorage, EndToEnd)
+{
+  FileStorage s("UnitTestsStorage");
+  s.Clear();
+
+  std::list<std::string> u;
+  for (unsigned int i = 0; i < 10; i++)
+  {
+    u.push_back(s.Create(Toolbox::GenerateUuid()));
+  }
+
+  std::set<std::string> ss;
+  s.ListAllFiles(ss);
+  ASSERT_EQ(10u, ss.size());
+  
+  unsigned int c = 0;
+  for (std::list<std::string>::iterator
+         i = u.begin(); i != u.end(); i++, c++)
+  {
+    ASSERT_TRUE(ss.find(*i) != ss.end());
+    if (c < 5)
+      s.Remove(*i);
+  }
+
+  s.ListAllFiles(ss);
+  ASSERT_EQ(5u, ss.size());
+
+  s.Clear();
+  s.ListAllFiles(ss);
+  ASSERT_EQ(0u, ss.size());
+}
+
+
+TEST(FileStorageAccessor, Simple)
+{
+  FileStorage s("UnitTestsStorage");
+  FileStorageAccessor accessor(s);
+
+  std::string data = "Hello world";
+  FileInfo info = accessor.Write(data, FileContentType_Dicom);
+  
+  std::string r;
+  accessor.Read(r, info.GetUuid());
+
+  ASSERT_EQ(data, r);
+  ASSERT_EQ(CompressionType_None, info.GetCompressionType());
+  ASSERT_EQ(11u, info.GetUncompressedSize());
+  ASSERT_EQ(11u, info.GetCompressedSize());
+  ASSERT_EQ(FileContentType_Dicom, info.GetContentType());
+}
+
+
+TEST(FileStorageAccessor, NoCompression)
+{
+  FileStorage s("UnitTestsStorage");
+  CompressedFileStorageAccessor accessor(s);
+
+  accessor.SetCompressionForNextOperations(CompressionType_None);
+  std::string data = "Hello world";
+  FileInfo info = accessor.Write(data, FileContentType_Dicom);
+  
+  std::string r;
+  accessor.Read(r, info.GetUuid());
+
+  ASSERT_EQ(data, r);
+  ASSERT_EQ(CompressionType_None, info.GetCompressionType());
+  ASSERT_EQ(11u, info.GetUncompressedSize());
+  ASSERT_EQ(11u, info.GetCompressedSize());
+  ASSERT_EQ(FileContentType_Dicom, info.GetContentType());
+}
+
+
+TEST(FileStorageAccessor, NoCompression2)
+{
+  FileStorage s("UnitTestsStorage");
+  CompressedFileStorageAccessor accessor(s);
+
+  accessor.SetCompressionForNextOperations(CompressionType_None);
+  std::vector<uint8_t> data;
+  StringToVector(data, "Hello world");
+  FileInfo info = accessor.Write(data, FileContentType_Dicom);
+  
+  std::string r;
+  accessor.Read(r, info.GetUuid());
+
+  ASSERT_EQ(0, memcmp(&r[0], &data[0], data.size()));
+  ASSERT_EQ(CompressionType_None, info.GetCompressionType());
+  ASSERT_EQ(11u, info.GetUncompressedSize());
+  ASSERT_EQ(11u, info.GetCompressedSize());
+  ASSERT_EQ(FileContentType_Dicom, info.GetContentType());
+}
+
+
+TEST(FileStorageAccessor, Compression)
+{
+  FileStorage s("UnitTestsStorage");
+  CompressedFileStorageAccessor accessor(s);
+
+  accessor.SetCompressionForNextOperations(CompressionType_Zlib);
+  std::string data = "Hello world";
+  FileInfo info = accessor.Write(data, FileContentType_Dicom);
+  
+  std::string r;
+  accessor.Read(r, info.GetUuid());
+
+  ASSERT_EQ(data, r);
+  ASSERT_EQ(CompressionType_Zlib, info.GetCompressionType());
+  ASSERT_EQ(11u, info.GetUncompressedSize());
+  ASSERT_EQ(FileContentType_Dicom, info.GetContentType());
+}
+
+
+TEST(FileStorageAccessor, Mix)
+{
+  FileStorage s("UnitTestsStorage");
+  CompressedFileStorageAccessor accessor(s);
+
+  std::string r;
+  std::string compressedData = "Hello";
+  std::string uncompressedData = "HelloWorld";
+
+  accessor.SetCompressionForNextOperations(CompressionType_Zlib);
+  FileInfo compressedInfo = accessor.Write(compressedData, FileContentType_Dicom);
+  
+  accessor.SetCompressionForNextOperations(CompressionType_None);
+  FileInfo uncompressedInfo = accessor.Write(uncompressedData, FileContentType_Dicom);
+  
+  accessor.SetCompressionForNextOperations(CompressionType_Zlib);
+  accessor.Read(r, compressedInfo.GetUuid());
+  ASSERT_EQ(compressedData, r);
+
+  accessor.SetCompressionForNextOperations(CompressionType_None);
+  accessor.Read(r, compressedInfo.GetUuid());
+  ASSERT_NE(compressedData, r);
+
+  /*
+  // This test is too slow on Windows
+  accessor.SetCompressionForNextOperations(CompressionType_Zlib);
+  ASSERT_THROW(accessor.Read(r, uncompressedInfo.GetUuid()), OrthancException);
+  */
+}
--- a/UnitTestsSources/FromDcmtk.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,147 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
- **/
-
-
-#include "PrecompiledHeadersUnitTests.h"
-#include "gtest/gtest.h"
-
-#include "../OrthancServer/FromDcmtkBridge.h"
-#include "../OrthancServer/OrthancInitialization.h"
-#include "../OrthancServer/DicomModification.h"
-#include "../Core/OrthancException.h"
-#include "../Core/ImageFormats/ImageBuffer.h"
-#include "../Core/ImageFormats/PngReader.h"
-#include "../Core/ImageFormats/PngWriter.h"
-
-using namespace Orthanc;
-
-TEST(DicomFormat, Tag)
-{
-  ASSERT_EQ("PatientName", FromDcmtkBridge::GetName(DicomTag(0x0010, 0x0010)));
-
-  DicomTag t = FromDcmtkBridge::ParseTag("SeriesDescription");
-  ASSERT_EQ(0x0008, t.GetGroup());
-  ASSERT_EQ(0x103E, t.GetElement());
-
-  t = FromDcmtkBridge::ParseTag("0020-e040");
-  ASSERT_EQ(0x0020, t.GetGroup());
-  ASSERT_EQ(0xe040, t.GetElement());
-
-  // Test ==() and !=() operators
-  ASSERT_TRUE(DICOM_TAG_PATIENT_ID == DicomTag(0x0010, 0x0020));
-  ASSERT_FALSE(DICOM_TAG_PATIENT_ID != DicomTag(0x0010, 0x0020));
-}
-
-
-TEST(DicomModification, Basic)
-{
-  DicomModification m;
-  m.SetupAnonymization();
-  //m.SetLevel(DicomRootLevel_Study);
-  //m.Replace(DICOM_TAG_PATIENT_ID, "coucou");
-  //m.Replace(DICOM_TAG_PATIENT_NAME, "coucou");
-
-  ParsedDicomFile o;
-  o.SaveToFile("UnitTestsResults/anon.dcm");
-
-  for (int i = 0; i < 10; i++)
-  {
-    char b[1024];
-    sprintf(b, "UnitTestsResults/anon%06d.dcm", i);
-    std::auto_ptr<ParsedDicomFile> f(o.Clone());
-    if (i > 4)
-      o.Replace(DICOM_TAG_SERIES_INSTANCE_UID, "coucou");
-    m.Apply(*f);
-    f->SaveToFile(b);
-  }
-}
-
-
-#include <dcmtk/dcmdata/dcuid.h>
-
-TEST(DicomModification, Png)
-{
-  // Red dot in http://en.wikipedia.org/wiki/Data_URI_scheme (RGBA image)
-  std::string s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
-
-  std::string m, c;
-  Toolbox::DecodeDataUriScheme(m, c, s);
-
-  ASSERT_EQ("image/png", m);
-  ASSERT_EQ(116, c.size());
-
-  std::string cc;
-  Toolbox::DecodeBase64(cc, c);
-  PngReader reader;
-  reader.ReadFromMemory(cc);
-
-  ASSERT_EQ(5, reader.GetHeight());
-  ASSERT_EQ(5, reader.GetWidth());
-  ASSERT_EQ(PixelFormat_RGBA32, reader.GetFormat());
-
-  ParsedDicomFile o;
-  o.EmbedImage(s);
-  o.SaveToFile("UnitTestsResults/png1.dcm");
-
-  // Red dot, without alpha channel
-  s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDTcIn2+8BgAAACJJREFUCNdj/P//PwMjIwME/P/P+J8BBTAxEOL/R9Lx/z8AynoKAXOeiV8AAAAASUVORK5CYII=";
-  o.EmbedImage(s);
-  o.SaveToFile("UnitTestsResults/png2.dcm");
-
-  // Check box in Graylevel8
-  s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDDcB53FulQAAAElJREFUGNNtj0sSAEEEQ1+U+185s1CtmRkblQ9CZldsKHJDk6DLGLJa6chjh0ooQmpjXMM86zPwydGEj6Ed/UGykkEM8X+p3u8/8LcOJIWLGeMAAAAASUVORK5CYII=";
-  o.EmbedImage(s);
-  //o.Replace(DICOM_TAG_SOP_CLASS_UID, UID_DigitalXRayImageStorageForProcessing);
-  o.SaveToFile("UnitTestsResults/png3.dcm");
-
-
-  {
-    // Gradient in Graylevel16
-
-    ImageBuffer img;
-    img.SetWidth(256);
-    img.SetHeight(256);
-    img.SetFormat(PixelFormat_Grayscale16);
-
-    int v = 0;
-    for (unsigned int y = 0; y < img.GetHeight(); y++)
-    {
-      uint16_t *p = reinterpret_cast<uint16_t*>(img.GetAccessor().GetRow(y));
-      for (unsigned int x = 0; x < img.GetWidth(); x++, p++, v++)
-      {
-        *p = v;
-      }
-    }
-
-    o.EmbedImage(img.GetAccessor());
-    o.SaveToFile("UnitTestsResults/png4.dcm");
-  }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/FromDcmtkTests.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,178 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "PrecompiledHeadersUnitTests.h"
+#include "gtest/gtest.h"
+
+#include "../OrthancServer/FromDcmtkBridge.h"
+#include "../OrthancServer/OrthancInitialization.h"
+#include "../OrthancServer/DicomModification.h"
+#include "../Core/OrthancException.h"
+#include "../Core/ImageFormats/ImageBuffer.h"
+#include "../Core/ImageFormats/PngReader.h"
+#include "../Core/ImageFormats/PngWriter.h"
+#include "../Core/Uuid.h"
+
+using namespace Orthanc;
+
+TEST(DicomFormat, Tag)
+{
+  ASSERT_EQ("PatientName", FromDcmtkBridge::GetName(DicomTag(0x0010, 0x0010)));
+
+  DicomTag t = FromDcmtkBridge::ParseTag("SeriesDescription");
+  ASSERT_EQ(0x0008, t.GetGroup());
+  ASSERT_EQ(0x103E, t.GetElement());
+
+  t = FromDcmtkBridge::ParseTag("0020-e040");
+  ASSERT_EQ(0x0020, t.GetGroup());
+  ASSERT_EQ(0xe040, t.GetElement());
+
+  // Test ==() and !=() operators
+  ASSERT_TRUE(DICOM_TAG_PATIENT_ID == DicomTag(0x0010, 0x0020));
+  ASSERT_FALSE(DICOM_TAG_PATIENT_ID != DicomTag(0x0010, 0x0020));
+}
+
+
+TEST(DicomModification, Basic)
+{
+  DicomModification m;
+  m.SetupAnonymization();
+  //m.SetLevel(DicomRootLevel_Study);
+  //m.Replace(DICOM_TAG_PATIENT_ID, "coucou");
+  //m.Replace(DICOM_TAG_PATIENT_NAME, "coucou");
+
+  ParsedDicomFile o;
+  o.SaveToFile("UnitTestsResults/anon.dcm");
+
+  for (int i = 0; i < 10; i++)
+  {
+    char b[1024];
+    sprintf(b, "UnitTestsResults/anon%06d.dcm", i);
+    std::auto_ptr<ParsedDicomFile> f(o.Clone());
+    if (i > 4)
+      o.Replace(DICOM_TAG_SERIES_INSTANCE_UID, "coucou");
+    m.Apply(*f);
+    f->SaveToFile(b);
+  }
+}
+
+
+TEST(DicomModification, Anonymization)
+{
+  const DicomTag privateTag(0x0045, 0x0010);
+  ASSERT_TRUE(FromDcmtkBridge::IsPrivateTag(privateTag));
+
+  ParsedDicomFile o;
+  o.Replace(DICOM_TAG_PATIENT_NAME, "coucou");
+  o.Replace(privateTag, "private tag");
+
+  std::string s;
+  ASSERT_TRUE(o.GetTagValue(s, DICOM_TAG_PATIENT_NAME));
+  ASSERT_FALSE(Toolbox::IsUuid(s));
+
+  DicomModification m;
+  m.SetupAnonymization();
+  m.Keep(privateTag);
+
+  m.Apply(o);
+
+  ASSERT_TRUE(o.GetTagValue(s, DICOM_TAG_PATIENT_NAME));
+  ASSERT_TRUE(Toolbox::IsUuid(s));
+  ASSERT_TRUE(o.GetTagValue(s, privateTag));
+  ASSERT_EQ("private tag", s);
+  
+  m.SetupAnonymization();
+  m.Apply(o);
+  ASSERT_FALSE(o.GetTagValue(s, privateTag));
+}
+
+
+#include <dcmtk/dcmdata/dcuid.h>
+
+TEST(DicomModification, Png)
+{
+  // Red dot in http://en.wikipedia.org/wiki/Data_URI_scheme (RGBA image)
+  std::string s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
+
+  std::string m, c;
+  Toolbox::DecodeDataUriScheme(m, c, s);
+
+  ASSERT_EQ("image/png", m);
+  ASSERT_EQ(116, c.size());
+
+  std::string cc;
+  Toolbox::DecodeBase64(cc, c);
+  PngReader reader;
+  reader.ReadFromMemory(cc);
+
+  ASSERT_EQ(5, reader.GetHeight());
+  ASSERT_EQ(5, reader.GetWidth());
+  ASSERT_EQ(PixelFormat_RGBA32, reader.GetFormat());
+
+  ParsedDicomFile o;
+  o.EmbedImage(s);
+  o.SaveToFile("UnitTestsResults/png1.dcm");
+
+  // Red dot, without alpha channel
+  s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDTcIn2+8BgAAACJJREFUCNdj/P//PwMjIwME/P/P+J8BBTAxEOL/R9Lx/z8AynoKAXOeiV8AAAAASUVORK5CYII=";
+  o.EmbedImage(s);
+  o.SaveToFile("UnitTestsResults/png2.dcm");
+
+  // Check box in Graylevel8
+  s = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUGDDcB53FulQAAAElJREFUGNNtj0sSAEEEQ1+U+185s1CtmRkblQ9CZldsKHJDk6DLGLJa6chjh0ooQmpjXMM86zPwydGEj6Ed/UGykkEM8X+p3u8/8LcOJIWLGeMAAAAASUVORK5CYII=";
+  o.EmbedImage(s);
+  //o.Replace(DICOM_TAG_SOP_CLASS_UID, UID_DigitalXRayImageStorageForProcessing);
+  o.SaveToFile("UnitTestsResults/png3.dcm");
+
+
+  {
+    // Gradient in Graylevel16
+
+    ImageBuffer img;
+    img.SetWidth(256);
+    img.SetHeight(256);
+    img.SetFormat(PixelFormat_Grayscale16);
+
+    int v = 0;
+    for (unsigned int y = 0; y < img.GetHeight(); y++)
+    {
+      uint16_t *p = reinterpret_cast<uint16_t*>(img.GetAccessor().GetRow(y));
+      for (unsigned int x = 0; x < img.GetWidth(); x++, p++, v++)
+      {
+        *p = v;
+      }
+    }
+
+    o.EmbedImage(img.GetAccessor());
+    o.SaveToFile("UnitTestsResults/png4.dcm");
+  }
+}
--- a/UnitTestsSources/JpegLossless.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
- **/
-
-
-#include "PrecompiledHeadersUnitTests.h"
-#include "gtest/gtest.h"
-
-#include "../OrthancServer/Internals/DicomImageDecoder.h"
-
-#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1
-
-#include <dcmtk/dcmdata/dcfilefo.h>
-
-#include "../OrthancServer/ParsedDicomFile.h"
-#include "../Core/OrthancException.h"
-#include "../Core/ImageFormats/ImageBuffer.h"
-#include "../Core/ImageFormats/PngWriter.h"
-
-using namespace Orthanc;
-
-
-
-// TODO Write a test
-
-
-#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/JpegLosslessTests.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,54 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "PrecompiledHeadersUnitTests.h"
+#include "gtest/gtest.h"
+
+#include "../OrthancServer/Internals/DicomImageDecoder.h"
+
+#if ORTHANC_JPEG_LOSSLESS_ENABLED == 1
+
+#include <dcmtk/dcmdata/dcfilefo.h>
+
+#include "../OrthancServer/ParsedDicomFile.h"
+#include "../Core/OrthancException.h"
+#include "../Core/ImageFormats/ImageBuffer.h"
+#include "../Core/ImageFormats/PngWriter.h"
+
+using namespace Orthanc;
+
+
+
+// TODO Write a test
+
+
+#endif
--- a/UnitTestsSources/Lua.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,136 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
- **/
-
-
-#include "PrecompiledHeadersUnitTests.h"
-#include "gtest/gtest.h"
-
-#include "../Core/Lua/LuaFunctionCall.h"
-
-
-TEST(Lua, Json)
-{
-  Orthanc::LuaContext lua;
-  lua.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX);
-  lua.Execute("a={}");
-  lua.Execute("a['x'] = 10");
-  lua.Execute("a['y'] = {}");
-  lua.Execute("a['y'][1] = 20");
-  lua.Execute("a['y'][2] = 20");
-  lua.Execute("PrintRecursive(a)");
-
-  lua.Execute("function f(a) print(a.bool) return a.bool,20,30,40,50,60 end");
-
-  Json::Value v, vv, o;
-  //v["a"] = "b";
-  v.append("hello");
-  v.append("world");
-  v.append("42");
-  vv.append("sub");
-  vv.append("set");
-  v.append(vv);
-  o = Json::objectValue;
-  o["x"] = 10;
-  o["y"] = 20;
-  o["z"] = 20.5f;
-  v.append(o);
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "PrintRecursive");
-    f.PushJSON(v);
-    f.Execute();
-  }
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "f");
-    f.PushJSON(o);
-    ASSERT_THROW(f.ExecutePredicate(), Orthanc::LuaException);
-  }
-
-  o["bool"] = false;
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "f");
-    f.PushJSON(o);
-    ASSERT_FALSE(f.ExecutePredicate());
-  }
-
-  o["bool"] = true;
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "f");
-    f.PushJSON(o);
-    ASSERT_TRUE(f.ExecutePredicate());
-  }
-}
-
-
-TEST(Lua, Existing)
-{
-  Orthanc::LuaContext lua;
-  lua.Execute("a={}");
-  lua.Execute("function f() end");
-
-  ASSERT_TRUE(lua.IsExistingFunction("f"));
-  ASSERT_FALSE(lua.IsExistingFunction("a"));
-  ASSERT_FALSE(lua.IsExistingFunction("Dummy"));
-}
-
-
-TEST(Lua, Simple)
-{
-  Orthanc::LuaContext lua;
-  lua.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX);
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "PrintRecursive");
-    f.PushString("hello");
-    f.Execute();
-  }
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "PrintRecursive");
-    f.PushBoolean(true);
-    f.Execute();
-  }
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "PrintRecursive");
-    f.PushInteger(42);
-    f.Execute();
-  }
-
-  {
-    Orthanc::LuaFunctionCall f(lua, "PrintRecursive");
-    f.PushDouble(3.1415);
-    f.Execute();
-  }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/LuaTests.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,234 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "PrecompiledHeadersUnitTests.h"
+#include "gtest/gtest.h"
+
+#include "../Core/Lua/LuaFunctionCall.h"
+
+#include <boost/lexical_cast.hpp>
+
+
+TEST(Lua, Json)
+{
+  Orthanc::LuaContext lua;
+  lua.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX);
+  lua.Execute("a={}");
+  lua.Execute("a['x'] = 10");
+  lua.Execute("a['y'] = {}");
+  lua.Execute("a['y'][1] = 20");
+  lua.Execute("a['y'][2] = 20");
+  lua.Execute("PrintRecursive(a)");
+
+  lua.Execute("function f(a) print(a.bool) return a.bool,20,30,40,50,60 end");
+
+  Json::Value v, vv, o;
+  //v["a"] = "b";
+  v.append("hello");
+  v.append("world");
+  v.append("42");
+  vv.append("sub");
+  vv.append("set");
+  v.append(vv);
+  o = Json::objectValue;
+  o["x"] = 10;
+  o["y"] = 20;
+  o["z"] = 20.5f;
+  v.append(o);
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "PrintRecursive");
+    f.PushJson(v);
+    f.Execute();
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "f");
+    f.PushJson(o);
+    ASSERT_THROW(f.ExecutePredicate(), Orthanc::LuaException);
+  }
+
+  o["bool"] = false;
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "f");
+    f.PushJson(o);
+    ASSERT_FALSE(f.ExecutePredicate());
+  }
+
+  o["bool"] = true;
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "f");
+    f.PushJson(o);
+    ASSERT_TRUE(f.ExecutePredicate());
+  }
+}
+
+
+TEST(Lua, Existing)
+{
+  Orthanc::LuaContext lua;
+  lua.Execute("a={}");
+  lua.Execute("function f() end");
+
+  ASSERT_TRUE(lua.IsExistingFunction("f"));
+  ASSERT_FALSE(lua.IsExistingFunction("a"));
+  ASSERT_FALSE(lua.IsExistingFunction("Dummy"));
+}
+
+
+TEST(Lua, Simple)
+{
+  Orthanc::LuaContext lua;
+  lua.Execute(Orthanc::EmbeddedResources::LUA_TOOLBOX);
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "PrintRecursive");
+    f.PushString("hello");
+    f.Execute();
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "PrintRecursive");
+    f.PushBoolean(true);
+    f.Execute();
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "PrintRecursive");
+    f.PushInteger(42);
+    f.Execute();
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "PrintRecursive");
+    f.PushDouble(3.1415);
+    f.Execute();
+  }
+}
+
+
+TEST(Lua, ReturnJson)
+{
+  Json::Value b = Json::objectValue;
+  b["a"] = 42;
+  b["b"] = 44;
+  b["c"] = 43;
+
+  Json::Value c = Json::arrayValue;
+  c.append("test3");
+  c.append("test1");
+  c.append("test2");
+
+  Json::Value a = Json::objectValue;
+  a["Hello"] = "World";
+  a["List"] = Json::arrayValue;
+  a["List"].append(b);
+  a["List"].append(c);
+
+  Orthanc::LuaContext lua;
+
+  // This is the identity function (it simply returns its input)
+  lua.Execute("function identity(a) return a end");
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "identity");
+    f.PushJson("hello");
+    Json::Value v;
+    f.ExecuteToJson(v);
+    ASSERT_EQ("hello", v.asString());
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "identity");
+    f.PushJson(42.25);
+    Json::Value v;
+    f.ExecuteToJson(v);
+    ASSERT_FLOAT_EQ(42.25f, v.asFloat());
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "identity");
+    Json::Value vv = Json::arrayValue;
+    f.PushJson(vv);
+    Json::Value v;
+    f.ExecuteToJson(v);
+    ASSERT_EQ(Json::arrayValue, v.type());
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "identity");
+    Json::Value vv = Json::objectValue;
+    f.PushJson(vv);
+    Json::Value v;
+    f.ExecuteToJson(v);
+    // Lua does not make the distinction between empty lists and empty objects
+    ASSERT_EQ(Json::arrayValue, v.type());
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "identity");
+    f.PushJson(b);
+    Json::Value v;
+    f.ExecuteToJson(v);
+    ASSERT_EQ(Json::objectValue, v.type());
+    ASSERT_FLOAT_EQ(42.0f, v["a"].asFloat());
+    ASSERT_FLOAT_EQ(44.0f, v["b"].asFloat());
+    ASSERT_FLOAT_EQ(43.0f, v["c"].asFloat());
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "identity");
+    f.PushJson(c);
+    Json::Value v;
+    f.ExecuteToJson(v);
+    ASSERT_EQ(Json::arrayValue, v.type());
+    ASSERT_EQ("test3", v[0].asString());
+    ASSERT_EQ("test1", v[1].asString());
+    ASSERT_EQ("test2", v[2].asString());
+  }
+
+  {
+    Orthanc::LuaFunctionCall f(lua, "identity");
+    f.PushJson(a);
+    Json::Value v;
+    f.ExecuteToJson(v);
+    ASSERT_EQ("World", v["Hello"].asString());
+    ASSERT_EQ(42, v["List"][0]["a"].asInt());
+    ASSERT_EQ(44, v["List"][0]["b"].asInt());
+    ASSERT_EQ(43, v["List"][0]["c"].asInt());
+    ASSERT_EQ("test3", v["List"][1][0].asString());
+    ASSERT_EQ("test1", v["List"][1][1].asString());
+    ASSERT_EQ("test2", v["List"][1][2].asString());
+  }
+}
--- a/UnitTestsSources/MemoryCache.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,230 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
- **/
-
-
-#include "PrecompiledHeadersUnitTests.h"
-#include "gtest/gtest.h"
-
-#include <glog/logging.h>
-#include <memory>
-#include <boost/thread.hpp>
-#include <boost/lexical_cast.hpp>
-#include "../Core/IDynamicObject.h"
-#include "../Core/Cache/MemoryCache.h"
-
-
-TEST(LRU, Basic)
-{
-  Orthanc::LeastRecentlyUsedIndex<std::string> r;
-  
-  r.Add("d");
-  r.Add("a");
-  r.Add("c");
-  r.Add("b");
-
-  r.MakeMostRecent("a");
-  r.MakeMostRecent("d");
-  r.MakeMostRecent("b");
-  r.MakeMostRecent("c");
-  r.MakeMostRecent("d");
-  r.MakeMostRecent("c");
-
-  ASSERT_EQ("a", r.GetOldest());
-  ASSERT_EQ("a", r.RemoveOldest());
-  ASSERT_EQ("b", r.GetOldest());
-  ASSERT_EQ("b", r.RemoveOldest());
-  ASSERT_EQ("d", r.GetOldest());
-  ASSERT_EQ("d", r.RemoveOldest());
-  ASSERT_EQ("c", r.GetOldest());
-  ASSERT_EQ("c", r.RemoveOldest());
-
-  ASSERT_TRUE(r.IsEmpty());
-
-  ASSERT_THROW(r.GetOldest(), Orthanc::OrthancException);
-  ASSERT_THROW(r.RemoveOldest(), Orthanc::OrthancException);
-}
-
-
-TEST(LRU, Payload)
-{
-  Orthanc::LeastRecentlyUsedIndex<std::string, int> r;
-  
-  r.Add("a", 420);
-  r.Add("b", 421);
-  r.Add("c", 422);
-  r.Add("d", 423);
-
-  r.MakeMostRecent("a");
-  r.MakeMostRecent("d");
-  r.MakeMostRecent("b");
-  r.MakeMostRecent("c");
-  r.MakeMostRecent("d");
-  r.MakeMostRecent("c");
-
-  ASSERT_TRUE(r.Contains("b"));
-  ASSERT_EQ(421, r.Invalidate("b"));
-  ASSERT_FALSE(r.Contains("b"));
-
-  int p;
-  ASSERT_TRUE(r.Contains("a", p)); ASSERT_EQ(420, p);
-  ASSERT_TRUE(r.Contains("c", p)); ASSERT_EQ(422, p);
-  ASSERT_TRUE(r.Contains("d", p)); ASSERT_EQ(423, p);
-
-  ASSERT_EQ("a", r.GetOldest());
-  ASSERT_EQ(420, r.GetOldestPayload());
-  ASSERT_EQ("a", r.RemoveOldest(p)); ASSERT_EQ(420, p);
-
-  ASSERT_EQ("d", r.GetOldest());
-  ASSERT_EQ(423, r.GetOldestPayload());
-  ASSERT_EQ("d", r.RemoveOldest(p)); ASSERT_EQ(423, p);
-
-  ASSERT_EQ("c", r.GetOldest());
-  ASSERT_EQ(422, r.GetOldestPayload());
-  ASSERT_EQ("c", r.RemoveOldest(p)); ASSERT_EQ(422, p);
-
-  ASSERT_TRUE(r.IsEmpty());
-}
-
-
-TEST(LRU, PayloadUpdate)
-{
-  Orthanc::LeastRecentlyUsedIndex<std::string, int> r;
-  
-  r.Add("a", 420);
-  r.Add("b", 421);
-  r.Add("d", 423);
-
-  r.MakeMostRecent("a", 424);
-  r.MakeMostRecent("d", 421);
-
-  ASSERT_EQ("b", r.GetOldest());
-  ASSERT_EQ(421, r.GetOldestPayload());
-  r.RemoveOldest();
-
-  ASSERT_EQ("a", r.GetOldest());
-  ASSERT_EQ(424, r.GetOldestPayload());
-  r.RemoveOldest();
-
-  ASSERT_EQ("d", r.GetOldest());
-  ASSERT_EQ(421, r.GetOldestPayload());
-  r.RemoveOldest();
-
-  ASSERT_TRUE(r.IsEmpty());
-}
-
-
-
-TEST(LRU, PayloadUpdateBis)
-{
-  Orthanc::LeastRecentlyUsedIndex<std::string, int> r;
-  
-  r.AddOrMakeMostRecent("a", 420);
-  r.AddOrMakeMostRecent("b", 421);
-  r.AddOrMakeMostRecent("d", 423);
-  r.AddOrMakeMostRecent("a", 424);
-  r.AddOrMakeMostRecent("d", 421);
-
-  ASSERT_EQ("b", r.GetOldest());
-  ASSERT_EQ(421, r.GetOldestPayload());
-  r.RemoveOldest();
-
-  ASSERT_EQ("a", r.GetOldest());
-  ASSERT_EQ(424, r.GetOldestPayload());
-  r.RemoveOldest();
-
-  ASSERT_EQ("d", r.GetOldest());
-  ASSERT_EQ(421, r.GetOldestPayload());
-  r.RemoveOldest();
-
-  ASSERT_TRUE(r.IsEmpty());
-}
-
-
-
-
-namespace
-{
-  class Integer : public Orthanc::IDynamicObject
-  {
-  private:
-    std::string& log_;
-    int value_;
-
-  public:
-    Integer(std::string& log, int v) : log_(log), value_(v)
-    {
-    }
-
-    virtual ~Integer()
-    {
-      LOG(INFO) << "Removing cache entry for " << value_;
-      log_ += boost::lexical_cast<std::string>(value_) + " ";
-    }
-
-    int GetValue() const 
-    {
-      return value_;
-    }
-  };
-
-  class IntegerProvider : public Orthanc::ICachePageProvider
-  {
-  public:
-    std::string log_;
-
-    Orthanc::IDynamicObject* Provide(const std::string& s)
-    {
-      LOG(INFO) << "Providing " << s;
-      return new Integer(log_, boost::lexical_cast<int>(s));
-    }
-  };
-}
-
-
-TEST(MemoryCache, Basic)
-{
-  IntegerProvider provider;
-
-  {
-    Orthanc::MemoryCache cache(provider, 3);
-    cache.Access("42");  // 42 -> exit
-    cache.Access("43");  // 43, 42 -> exit
-    cache.Access("45");  // 45, 43, 42 -> exit
-    cache.Access("42");  // 42, 45, 43 -> exit
-    cache.Access("43");  // 43, 42, 45 -> exit
-    cache.Access("47");  // 45 is removed; 47, 43, 42 -> exit 
-    cache.Access("44");  // 42 is removed; 44, 47, 43 -> exit
-    cache.Access("42");  // 43 is removed; 42, 44, 47 -> exit
-    // Closing the cache: 47, 44, 42 are successively removed
-  }
-
-  ASSERT_EQ("45 42 43 47 44 42 ", provider.log_);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/MemoryCacheTests.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,230 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "PrecompiledHeadersUnitTests.h"
+#include "gtest/gtest.h"
+
+#include <glog/logging.h>
+#include <memory>
+#include <boost/thread.hpp>
+#include <boost/lexical_cast.hpp>
+#include "../Core/IDynamicObject.h"
+#include "../Core/Cache/MemoryCache.h"
+
+
+TEST(LRU, Basic)
+{
+  Orthanc::LeastRecentlyUsedIndex<std::string> r;
+  
+  r.Add("d");
+  r.Add("a");
+  r.Add("c");
+  r.Add("b");
+
+  r.MakeMostRecent("a");
+  r.MakeMostRecent("d");
+  r.MakeMostRecent("b");
+  r.MakeMostRecent("c");
+  r.MakeMostRecent("d");
+  r.MakeMostRecent("c");
+
+  ASSERT_EQ("a", r.GetOldest());
+  ASSERT_EQ("a", r.RemoveOldest());
+  ASSERT_EQ("b", r.GetOldest());
+  ASSERT_EQ("b", r.RemoveOldest());
+  ASSERT_EQ("d", r.GetOldest());
+  ASSERT_EQ("d", r.RemoveOldest());
+  ASSERT_EQ("c", r.GetOldest());
+  ASSERT_EQ("c", r.RemoveOldest());
+
+  ASSERT_TRUE(r.IsEmpty());
+
+  ASSERT_THROW(r.GetOldest(), Orthanc::OrthancException);
+  ASSERT_THROW(r.RemoveOldest(), Orthanc::OrthancException);
+}
+
+
+TEST(LRU, Payload)
+{
+  Orthanc::LeastRecentlyUsedIndex<std::string, int> r;
+  
+  r.Add("a", 420);
+  r.Add("b", 421);
+  r.Add("c", 422);
+  r.Add("d", 423);
+
+  r.MakeMostRecent("a");
+  r.MakeMostRecent("d");
+  r.MakeMostRecent("b");
+  r.MakeMostRecent("c");
+  r.MakeMostRecent("d");
+  r.MakeMostRecent("c");
+
+  ASSERT_TRUE(r.Contains("b"));
+  ASSERT_EQ(421, r.Invalidate("b"));
+  ASSERT_FALSE(r.Contains("b"));
+
+  int p;
+  ASSERT_TRUE(r.Contains("a", p)); ASSERT_EQ(420, p);
+  ASSERT_TRUE(r.Contains("c", p)); ASSERT_EQ(422, p);
+  ASSERT_TRUE(r.Contains("d", p)); ASSERT_EQ(423, p);
+
+  ASSERT_EQ("a", r.GetOldest());
+  ASSERT_EQ(420, r.GetOldestPayload());
+  ASSERT_EQ("a", r.RemoveOldest(p)); ASSERT_EQ(420, p);
+
+  ASSERT_EQ("d", r.GetOldest());
+  ASSERT_EQ(423, r.GetOldestPayload());
+  ASSERT_EQ("d", r.RemoveOldest(p)); ASSERT_EQ(423, p);
+
+  ASSERT_EQ("c", r.GetOldest());
+  ASSERT_EQ(422, r.GetOldestPayload());
+  ASSERT_EQ("c", r.RemoveOldest(p)); ASSERT_EQ(422, p);
+
+  ASSERT_TRUE(r.IsEmpty());
+}
+
+
+TEST(LRU, PayloadUpdate)
+{
+  Orthanc::LeastRecentlyUsedIndex<std::string, int> r;
+  
+  r.Add("a", 420);
+  r.Add("b", 421);
+  r.Add("d", 423);
+
+  r.MakeMostRecent("a", 424);
+  r.MakeMostRecent("d", 421);
+
+  ASSERT_EQ("b", r.GetOldest());
+  ASSERT_EQ(421, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_EQ("a", r.GetOldest());
+  ASSERT_EQ(424, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_EQ("d", r.GetOldest());
+  ASSERT_EQ(421, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_TRUE(r.IsEmpty());
+}
+
+
+
+TEST(LRU, PayloadUpdateBis)
+{
+  Orthanc::LeastRecentlyUsedIndex<std::string, int> r;
+  
+  r.AddOrMakeMostRecent("a", 420);
+  r.AddOrMakeMostRecent("b", 421);
+  r.AddOrMakeMostRecent("d", 423);
+  r.AddOrMakeMostRecent("a", 424);
+  r.AddOrMakeMostRecent("d", 421);
+
+  ASSERT_EQ("b", r.GetOldest());
+  ASSERT_EQ(421, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_EQ("a", r.GetOldest());
+  ASSERT_EQ(424, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_EQ("d", r.GetOldest());
+  ASSERT_EQ(421, r.GetOldestPayload());
+  r.RemoveOldest();
+
+  ASSERT_TRUE(r.IsEmpty());
+}
+
+
+
+
+namespace
+{
+  class Integer : public Orthanc::IDynamicObject
+  {
+  private:
+    std::string& log_;
+    int value_;
+
+  public:
+    Integer(std::string& log, int v) : log_(log), value_(v)
+    {
+    }
+
+    virtual ~Integer()
+    {
+      LOG(INFO) << "Removing cache entry for " << value_;
+      log_ += boost::lexical_cast<std::string>(value_) + " ";
+    }
+
+    int GetValue() const 
+    {
+      return value_;
+    }
+  };
+
+  class IntegerProvider : public Orthanc::ICachePageProvider
+  {
+  public:
+    std::string log_;
+
+    Orthanc::IDynamicObject* Provide(const std::string& s)
+    {
+      LOG(INFO) << "Providing " << s;
+      return new Integer(log_, boost::lexical_cast<int>(s));
+    }
+  };
+}
+
+
+TEST(MemoryCache, Basic)
+{
+  IntegerProvider provider;
+
+  {
+    Orthanc::MemoryCache cache(provider, 3);
+    cache.Access("42");  // 42 -> exit
+    cache.Access("43");  // 43, 42 -> exit
+    cache.Access("45");  // 45, 43, 42 -> exit
+    cache.Access("42");  // 42, 45, 43 -> exit
+    cache.Access("43");  // 43, 42, 45 -> exit
+    cache.Access("47");  // 45 is removed; 47, 43, 42 -> exit 
+    cache.Access("44");  // 42 is removed; 44, 47, 43 -> exit
+    cache.Access("42");  // 43 is removed; 42, 44, 47 -> exit
+    // Closing the cache: 47, 44, 42 are successively removed
+  }
+
+  ASSERT_EQ("45 42 43 47 44 42 ", provider.log_);
+}
--- a/UnitTestsSources/MultiThreading.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,276 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
- **/
-
-
-#include "PrecompiledHeadersUnitTests.h"
-#include "gtest/gtest.h"
-
-#include <glog/logging.h>
-
-#include "../Core/OrthancException.h"
-#include "../Core/Toolbox.h"
-#include "../Core/MultiThreading/ArrayFilledByThreads.h"
-#include "../Core/MultiThreading/Locker.h"
-#include "../Core/MultiThreading/Mutex.h"
-#include "../Core/MultiThreading/ReaderWriterLock.h"
-#include "../Core/MultiThreading/ThreadedCommandProcessor.h"
-
-using namespace Orthanc;
-
-namespace
-{
-  class DynamicInteger : public ICommand
-  {
-  private:
-    int value_;
-    std::set<int>& target_;
-
-  public:
-    DynamicInteger(int value, std::set<int>& target) : 
-      value_(value), target_(target)
-    {
-    }
-
-    int GetValue() const
-    {
-      return value_;
-    }
-
-    virtual bool Execute()
-    {
-      static boost::mutex mutex;
-      boost::mutex::scoped_lock lock(mutex);
-      target_.insert(value_);
-      return true;
-    }
-  };
-
-  class MyFiller : public ArrayFilledByThreads::IFiller
-  {
-  private:
-    int size_;
-    unsigned int created_;
-    std::set<int> set_;
-
-  public:
-    MyFiller(int size) : size_(size), created_(0)
-    {
-    }
-
-    virtual size_t GetFillerSize()
-    {
-      return size_;
-    }
-
-    virtual IDynamicObject* GetFillerItem(size_t index)
-    {
-      static boost::mutex mutex;
-      boost::mutex::scoped_lock lock(mutex);
-      created_++;
-      return new DynamicInteger(index * 2, set_);
-    }
-
-    unsigned int GetCreatedCount() const
-    {
-      return created_;
-    }
-
-    std::set<int> GetSet()
-    {
-      return set_;
-    }    
-  };
-}
-
-
-
-
-TEST(MultiThreading, SharedMessageQueueBasic)
-{
-  std::set<int> s;
-
-  SharedMessageQueue q;
-  ASSERT_TRUE(q.WaitEmpty(0));
-  q.Enqueue(new DynamicInteger(10, s));
-  ASSERT_FALSE(q.WaitEmpty(1));
-  q.Enqueue(new DynamicInteger(20, s));
-  q.Enqueue(new DynamicInteger(30, s));
-  q.Enqueue(new DynamicInteger(40, s));
-
-  std::auto_ptr<DynamicInteger> i;
-  i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(10, i->GetValue());
-  i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(20, i->GetValue());
-  i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(30, i->GetValue());
-  ASSERT_FALSE(q.WaitEmpty(1));
-  i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(40, i->GetValue());
-  ASSERT_TRUE(q.WaitEmpty(0));
-  ASSERT_EQ(NULL, q.Dequeue(1));
-}
-
-
-TEST(MultiThreading, SharedMessageQueueClean)
-{
-  std::set<int> s;
-
-  try
-  {
-    SharedMessageQueue q;
-    q.Enqueue(new DynamicInteger(10, s));
-    q.Enqueue(new DynamicInteger(20, s));  
-    throw OrthancException("Nope");
-  }
-  catch (OrthancException&)
-  {
-  }
-}
-
-
-TEST(MultiThreading, ArrayFilledByThreadEmpty)
-{
-  MyFiller f(0);
-  ArrayFilledByThreads a(f);
-  a.SetThreadCount(1);
-  ASSERT_EQ(0, a.GetSize());
-}
-
-
-TEST(MultiThreading, ArrayFilledByThread1)
-{
-  MyFiller f(100);
-  ArrayFilledByThreads a(f);
-  a.SetThreadCount(1);
-  ASSERT_EQ(100, a.GetSize());
-  for (size_t i = 0; i < a.GetSize(); i++)
-  {
-    ASSERT_EQ(2 * i, dynamic_cast<DynamicInteger&>(a.GetItem(i)).GetValue());
-  }
-}
-
-
-TEST(MultiThreading, ArrayFilledByThread4)
-{
-  MyFiller f(100);
-  ArrayFilledByThreads a(f);
-  a.SetThreadCount(4);
-  ASSERT_EQ(100, a.GetSize());
-  for (size_t i = 0; i < a.GetSize(); i++)
-  {
-    ASSERT_EQ(2 * i, dynamic_cast<DynamicInteger&>(a.GetItem(i)).GetValue());
-  }
-
-  ASSERT_EQ(100u, f.GetCreatedCount());
-
-  a.Invalidate();
-
-  ASSERT_EQ(100, a.GetSize());
-  ASSERT_EQ(200u, f.GetCreatedCount());
-  ASSERT_EQ(4u, a.GetThreadCount());
-  ASSERT_TRUE(f.GetSet().empty());
-
-  for (size_t i = 0; i < a.GetSize(); i++)
-  {
-    ASSERT_EQ(2 * i, dynamic_cast<DynamicInteger&>(a.GetItem(i)).GetValue());
-  }
-}
-
-
-TEST(MultiThreading, CommandProcessor)
-{
-  ThreadedCommandProcessor p(4);
-
-  std::set<int> s;
-
-  for (size_t i = 0; i < 100; i++)
-  {
-    p.Post(new DynamicInteger(i * 2, s));
-  }
-
-  p.Join();
-
-  for (size_t i = 0; i < 200; i++)
-  {
-    if (i % 2)
-      ASSERT_TRUE(s.find(i) == s.end());
-    else
-      ASSERT_TRUE(s.find(i) != s.end());
-  }
-}
-
-
-TEST(MultiThreading, Mutex)
-{
-  Mutex mutex;
-  Locker locker(mutex);
-}
-
-
-TEST(MultiThreading, ReaderWriterLock)
-{
-  ReaderWriterLock lock;
-
-  {
-    Locker locker1(lock.ForReader());
-    Locker locker2(lock.ForReader());
-  }
-
-  {
-    Locker locker3(lock.ForWriter());
-  }
-}
-
-
-
-
-
-#include "../OrthancServer/DicomProtocol/ReusableDicomUserConnection.h"
-
-TEST(ReusableDicomUserConnection, DISABLED_Basic)
-{
-  ReusableDicomUserConnection c;
-  c.SetMillisecondsBeforeClose(200);
-  printf("START\n"); fflush(stdout);
-  {
-    ReusableDicomUserConnection::Locker lock(c, "STORESCP", "localhost", 2000, ModalityManufacturer_Generic);
-    lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676281");
-  }
-
-  printf("**\n"); fflush(stdout);
-  Toolbox::USleep(1000000);
-  printf("**\n"); fflush(stdout);
-
-  {
-    ReusableDicomUserConnection::Locker lock(c, "STORESCP", "localhost", 2000, ModalityManufacturer_Generic);
-    lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676277");
-  }
-
-  Toolbox::ServerBarrier();
-  printf("DONE\n"); fflush(stdout);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/MultiThreadingTests.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,372 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "PrecompiledHeadersUnitTests.h"
+#include "gtest/gtest.h"
+#include <glog/logging.h>
+
+#include "../OrthancServer/Scheduler/ServerScheduler.h"
+
+#include "../Core/OrthancException.h"
+#include "../Core/Toolbox.h"
+#include "../Core/MultiThreading/ArrayFilledByThreads.h"
+#include "../Core/MultiThreading/Locker.h"
+#include "../Core/MultiThreading/Mutex.h"
+#include "../Core/MultiThreading/ReaderWriterLock.h"
+#include "../Core/MultiThreading/ThreadedCommandProcessor.h"
+
+using namespace Orthanc;
+
+namespace
+{
+  class DynamicInteger : public ICommand
+  {
+  private:
+    int value_;
+    std::set<int>& target_;
+
+  public:
+    DynamicInteger(int value, std::set<int>& target) : 
+      value_(value), target_(target)
+    {
+    }
+
+    int GetValue() const
+    {
+      return value_;
+    }
+
+    virtual bool Execute()
+    {
+      static boost::mutex mutex;
+      boost::mutex::scoped_lock lock(mutex);
+      target_.insert(value_);
+      return true;
+    }
+  };
+
+  class MyFiller : public ArrayFilledByThreads::IFiller
+  {
+  private:
+    int size_;
+    unsigned int created_;
+    std::set<int> set_;
+
+  public:
+    MyFiller(int size) : size_(size), created_(0)
+    {
+    }
+
+    virtual size_t GetFillerSize()
+    {
+      return size_;
+    }
+
+    virtual IDynamicObject* GetFillerItem(size_t index)
+    {
+      static boost::mutex mutex;
+      boost::mutex::scoped_lock lock(mutex);
+      created_++;
+      return new DynamicInteger(index * 2, set_);
+    }
+
+    unsigned int GetCreatedCount() const
+    {
+      return created_;
+    }
+
+    std::set<int> GetSet()
+    {
+      return set_;
+    }    
+  };
+}
+
+
+
+
+TEST(MultiThreading, SharedMessageQueueBasic)
+{
+  std::set<int> s;
+
+  SharedMessageQueue q;
+  ASSERT_TRUE(q.WaitEmpty(0));
+  q.Enqueue(new DynamicInteger(10, s));
+  ASSERT_FALSE(q.WaitEmpty(1));
+  q.Enqueue(new DynamicInteger(20, s));
+  q.Enqueue(new DynamicInteger(30, s));
+  q.Enqueue(new DynamicInteger(40, s));
+
+  std::auto_ptr<DynamicInteger> i;
+  i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(10, i->GetValue());
+  i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(20, i->GetValue());
+  i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(30, i->GetValue());
+  ASSERT_FALSE(q.WaitEmpty(1));
+  i.reset(dynamic_cast<DynamicInteger*>(q.Dequeue(1))); ASSERT_EQ(40, i->GetValue());
+  ASSERT_TRUE(q.WaitEmpty(0));
+  ASSERT_EQ(NULL, q.Dequeue(1));
+}
+
+
+TEST(MultiThreading, SharedMessageQueueClean)
+{
+  std::set<int> s;
+
+  try
+  {
+    SharedMessageQueue q;
+    q.Enqueue(new DynamicInteger(10, s));
+    q.Enqueue(new DynamicInteger(20, s));  
+    throw OrthancException("Nope");
+  }
+  catch (OrthancException&)
+  {
+  }
+}
+
+
+TEST(MultiThreading, ArrayFilledByThreadEmpty)
+{
+  MyFiller f(0);
+  ArrayFilledByThreads a(f);
+  a.SetThreadCount(1);
+  ASSERT_EQ(0, a.GetSize());
+}
+
+
+TEST(MultiThreading, ArrayFilledByThread1)
+{
+  MyFiller f(100);
+  ArrayFilledByThreads a(f);
+  a.SetThreadCount(1);
+  ASSERT_EQ(100, a.GetSize());
+  for (size_t i = 0; i < a.GetSize(); i++)
+  {
+    ASSERT_EQ(2 * i, dynamic_cast<DynamicInteger&>(a.GetItem(i)).GetValue());
+  }
+}
+
+
+TEST(MultiThreading, ArrayFilledByThread4)
+{
+  MyFiller f(100);
+  ArrayFilledByThreads a(f);
+  a.SetThreadCount(4);
+  ASSERT_EQ(100, a.GetSize());
+  for (size_t i = 0; i < a.GetSize(); i++)
+  {
+    ASSERT_EQ(2 * i, dynamic_cast<DynamicInteger&>(a.GetItem(i)).GetValue());
+  }
+
+  ASSERT_EQ(100u, f.GetCreatedCount());
+
+  a.Invalidate();
+
+  ASSERT_EQ(100, a.GetSize());
+  ASSERT_EQ(200u, f.GetCreatedCount());
+  ASSERT_EQ(4u, a.GetThreadCount());
+  ASSERT_TRUE(f.GetSet().empty());
+
+  for (size_t i = 0; i < a.GetSize(); i++)
+  {
+    ASSERT_EQ(2 * i, dynamic_cast<DynamicInteger&>(a.GetItem(i)).GetValue());
+  }
+}
+
+
+TEST(MultiThreading, CommandProcessor)
+{
+  ThreadedCommandProcessor p(4);
+
+  std::set<int> s;
+
+  for (size_t i = 0; i < 100; i++)
+  {
+    p.Post(new DynamicInteger(i * 2, s));
+  }
+
+  p.Join();
+
+  for (size_t i = 0; i < 200; i++)
+  {
+    if (i % 2)
+      ASSERT_TRUE(s.find(i) == s.end());
+    else
+      ASSERT_TRUE(s.find(i) != s.end());
+  }
+}
+
+
+TEST(MultiThreading, Mutex)
+{
+  Mutex mutex;
+  Locker locker(mutex);
+}
+
+
+TEST(MultiThreading, ReaderWriterLock)
+{
+  ReaderWriterLock lock;
+
+  {
+    Locker locker1(lock.ForReader());
+    Locker locker2(lock.ForReader());
+  }
+
+  {
+    Locker locker3(lock.ForWriter());
+  }
+}
+
+
+
+#include "../OrthancServer/DicomProtocol/ReusableDicomUserConnection.h"
+
+TEST(ReusableDicomUserConnection, DISABLED_Basic)
+{
+  ReusableDicomUserConnection c;
+  c.SetMillisecondsBeforeClose(200);
+  printf("START\n"); fflush(stdout);
+
+  {
+    ReusableDicomUserConnection::Locker lock(c, "STORESCP", "localhost", 2000, ModalityManufacturer_Generic);
+    lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676281");
+  }
+
+  printf("**\n"); fflush(stdout);
+  Toolbox::USleep(1000000);
+  printf("**\n"); fflush(stdout);
+
+  {
+    ReusableDicomUserConnection::Locker lock(c, "STORESCP", "localhost", 2000, ModalityManufacturer_Generic);
+    lock.GetConnection().StoreFile("/home/jodogne/DICOM/Cardiac/MR.X.1.2.276.0.7230010.3.1.4.2831157719.2256.1336386844.676277");
+  }
+
+  Toolbox::ServerBarrier();
+  printf("DONE\n"); fflush(stdout);
+}
+
+
+
+class Tutu : public IServerCommand
+{
+private:
+  int factor_;
+
+public:
+  Tutu(int f) : factor_(f)
+  {
+  }
+
+  virtual bool Apply(ListOfStrings& outputs,
+                     const ListOfStrings& inputs)
+  {
+    for (ListOfStrings::const_iterator 
+           it = inputs.begin(); it != inputs.end(); it++)
+    {
+      int a = boost::lexical_cast<int>(*it);
+      int b = factor_ * a;
+
+      printf("%d * %d = %d\n", a, factor_, b);
+
+      //if (a == 84) { printf("BREAK\n"); return false; }
+
+      outputs.push_back(boost::lexical_cast<std::string>(b));
+    }
+
+    Toolbox::USleep(100000);
+
+    return true;
+  }
+};
+
+
+static void Tata(ServerScheduler* s, ServerJob* j, bool* done)
+{
+  typedef IServerCommand::ListOfStrings  ListOfStrings;
+
+  while (!(*done))
+  {
+    ListOfStrings l;
+    s->GetListOfJobs(l);
+    for (ListOfStrings::iterator i = l.begin(); i != l.end(); i++)
+      printf(">> %s: %0.1f\n", i->c_str(), 100.0f * s->GetProgress(*i));
+    Toolbox::USleep(10000);
+  }
+}
+
+
+TEST(MultiThreading, ServerScheduler)
+{
+  ServerScheduler scheduler(10);
+
+  ServerJob job;
+  ServerCommandInstance& f2 = job.AddCommand(new Tutu(2));
+  ServerCommandInstance& f3 = job.AddCommand(new Tutu(3));
+  ServerCommandInstance& f4 = job.AddCommand(new Tutu(4));
+  ServerCommandInstance& f5 = job.AddCommand(new Tutu(5));
+  f2.AddInput(boost::lexical_cast<std::string>(42));
+  //f3.AddInput(boost::lexical_cast<std::string>(42));
+  //f4.AddInput(boost::lexical_cast<std::string>(42));
+  f2.ConnectOutput(f3);
+  f3.ConnectOutput(f4);
+  f4.ConnectOutput(f5);
+
+  f3.SetConnectedToSink(true);
+  f5.SetConnectedToSink(true);
+
+  job.SetDescription("tutu");
+
+  bool done = false;
+  boost::thread t(Tata, &scheduler, &job, &done);
+
+
+  //scheduler.Submit(job);
+
+  IServerCommand::ListOfStrings l;
+  scheduler.SubmitAndWait(l, job);
+
+  ASSERT_EQ(2, l.size());
+  ASSERT_EQ(42 * 2 * 3, boost::lexical_cast<int>(l.front()));
+  ASSERT_EQ(42 * 2 * 3 * 4 * 5, boost::lexical_cast<int>(l.back()));
+
+  for (IServerCommand::ListOfStrings::iterator i = l.begin(); i != l.end(); i++)
+  {
+    printf("** %s\n", i->c_str());
+  }
+
+  //Toolbox::ServerBarrier();
+  //Toolbox::USleep(3000000);
+
+  done = true;
+  t.join();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/PluginsTests.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,61 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "PrecompiledHeadersUnitTests.h"
+#include "gtest/gtest.h"
+
+#include <glog/logging.h>
+
+#include "../Plugins/Engine/PluginsManager.h"
+
+using namespace Orthanc;
+
+TEST(SharedLibrary, Basic)
+{
+#if defined(_WIN32)
+  SharedLibrary l("kernel32.dll");
+  ASSERT_THROW(l.GetFunction("world"), OrthancException);
+  ASSERT_TRUE(l.GetFunction("GetVersionExW") != NULL);
+  ASSERT_TRUE(l.HasFunction("GetVersionExW"));
+  ASSERT_FALSE(l.HasFunction("world"));
+
+#elif defined(__linux)
+  SharedLibrary l("libdl.so");
+  ASSERT_THROW(l.GetFunction("world"), OrthancException);
+  ASSERT_TRUE(l.GetFunction("dlopen") != NULL);
+  ASSERT_TRUE(l.HasFunction("dlclose"));
+  ASSERT_FALSE(l.HasFunction("world"));
+
+#else
+#error Support your platform here
+#endif
+}
--- a/UnitTestsSources/Png.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,186 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
- **/
-
-
-#include "PrecompiledHeadersUnitTests.h"
-#include "gtest/gtest.h"
-
-#include <stdint.h>
-#include "../Core/ImageFormats/PngReader.h"
-#include "../Core/ImageFormats/PngWriter.h"
-#include "../Core/Toolbox.h"
-#include "../Core/Uuid.h"
-
-
-TEST(PngWriter, ColorPattern)
-{
-  Orthanc::PngWriter w;
-  int width = 17;
-  int height = 61;
-  int pitch = width * 3;
-
-  std::vector<uint8_t> image(height * pitch);
-  for (int y = 0; y < height; y++)
-  {
-    uint8_t *p = &image[0] + y * pitch;
-    for (int x = 0; x < width; x++, p += 3)
-    {
-      p[0] = (y % 3 == 0) ? 255 : 0;
-      p[1] = (y % 3 == 1) ? 255 : 0;
-      p[2] = (y % 3 == 2) ? 255 : 0;
-    }
-  }
-
-  w.WriteToFile("UnitTestsResults/ColorPattern.png", width, height, pitch, Orthanc::PixelFormat_RGB24, &image[0]);
-
-  std::string f, md5;
-  Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/ColorPattern.png");
-  Orthanc::Toolbox::ComputeMD5(md5, f);
-  ASSERT_EQ("604e785f53c99cae6ea4584870b2c41d", md5);
-}
-
-TEST(PngWriter, Gray8Pattern)
-{
-  Orthanc::PngWriter w;
-  int width = 17;
-  int height = 256;
-  int pitch = width;
-
-  std::vector<uint8_t> image(height * pitch);
-  for (int y = 0; y < height; y++)
-  {
-    uint8_t *p = &image[0] + y * pitch;
-    for (int x = 0; x < width; x++, p++)
-    {
-      *p = y;
-    }
-  }
-
-  w.WriteToFile("UnitTestsResults/Gray8Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale8, &image[0]);
-
-  std::string f, md5;
-  Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/Gray8Pattern.png");
-  Orthanc::Toolbox::ComputeMD5(md5, f);
-  ASSERT_EQ("5a9b98bea3d0a6d983980cc38bfbcdb3", md5);
-}
-
-TEST(PngWriter, Gray16Pattern)
-{
-  Orthanc::PngWriter w;
-  int width = 256;
-  int height = 256;
-  int pitch = width * 2 + 16;
-
-  std::vector<uint8_t> image(height * pitch);
-
-  int v = 0;
-  for (int y = 0; y < height; y++)
-  {
-    uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch);
-    for (int x = 0; x < width; x++, p++, v++)
-    {
-      *p = v;
-    }
-  }
-
-  w.WriteToFile("UnitTestsResults/Gray16Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]);
-
-  std::string f, md5;
-  Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/Gray16Pattern.png");
-  Orthanc::Toolbox::ComputeMD5(md5, f);
-  ASSERT_EQ("0785866a08bf0a02d2eeff87f658571c", md5);
-}
-
-TEST(PngWriter, EndToEnd)
-{
-  Orthanc::PngWriter w;
-  int width = 256;
-  int height = 256;
-  int pitch = width * 2 + 16;
-
-  std::vector<uint8_t> image(height * pitch);
-
-  int v = 0;
-  for (int y = 0; y < height; y++)
-  {
-    uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch);
-    for (int x = 0; x < width; x++, p++, v++)
-    {
-      *p = v;
-    }
-  }
-
-  std::string s;
-  w.WriteToMemory(s, width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]);
-
-  {
-    Orthanc::PngReader r;
-    r.ReadFromMemory(s);
-
-    ASSERT_EQ(r.GetFormat(), Orthanc::PixelFormat_Grayscale16);
-    ASSERT_EQ(r.GetWidth(), width);
-    ASSERT_EQ(r.GetHeight(), height);
-
-    v = 0;
-    for (int y = 0; y < height; y++)
-    {
-      const uint16_t *p = reinterpret_cast<const uint16_t*>((const uint8_t*) r.GetConstBuffer() + y * r.GetPitch());
-      ASSERT_EQ(p, r.GetConstRow(y));
-      for (int x = 0; x < width; x++, p++, v++)
-      {
-        ASSERT_EQ(*p, v);
-      }
-    }
-  }
-
-  {
-    Orthanc::Toolbox::TemporaryFile tmp;
-    Orthanc::Toolbox::WriteFile(s, tmp.GetPath());
-
-    Orthanc::PngReader r2;
-    r2.ReadFromFile(tmp.GetPath());
-
-    ASSERT_EQ(r2.GetFormat(), Orthanc::PixelFormat_Grayscale16);
-    ASSERT_EQ(r2.GetWidth(), width);
-    ASSERT_EQ(r2.GetHeight(), height);
-
-    v = 0;
-    for (int y = 0; y < height; y++)
-    {
-      const uint16_t *p = reinterpret_cast<const uint16_t*>((const uint8_t*) r2.GetConstBuffer() + y * r2.GetPitch());
-      ASSERT_EQ(p, r2.GetConstRow(y));
-      for (int x = 0; x < width; x++, p++, v++)
-      {
-        ASSERT_EQ(*p, v);
-      }
-    }
-  }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/PngTests.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,186 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "PrecompiledHeadersUnitTests.h"
+#include "gtest/gtest.h"
+
+#include <stdint.h>
+#include "../Core/ImageFormats/PngReader.h"
+#include "../Core/ImageFormats/PngWriter.h"
+#include "../Core/Toolbox.h"
+#include "../Core/Uuid.h"
+
+
+TEST(PngWriter, ColorPattern)
+{
+  Orthanc::PngWriter w;
+  int width = 17;
+  int height = 61;
+  int pitch = width * 3;
+
+  std::vector<uint8_t> image(height * pitch);
+  for (int y = 0; y < height; y++)
+  {
+    uint8_t *p = &image[0] + y * pitch;
+    for (int x = 0; x < width; x++, p += 3)
+    {
+      p[0] = (y % 3 == 0) ? 255 : 0;
+      p[1] = (y % 3 == 1) ? 255 : 0;
+      p[2] = (y % 3 == 2) ? 255 : 0;
+    }
+  }
+
+  w.WriteToFile("UnitTestsResults/ColorPattern.png", width, height, pitch, Orthanc::PixelFormat_RGB24, &image[0]);
+
+  std::string f, md5;
+  Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/ColorPattern.png");
+  Orthanc::Toolbox::ComputeMD5(md5, f);
+  ASSERT_EQ("604e785f53c99cae6ea4584870b2c41d", md5);
+}
+
+TEST(PngWriter, Gray8Pattern)
+{
+  Orthanc::PngWriter w;
+  int width = 17;
+  int height = 256;
+  int pitch = width;
+
+  std::vector<uint8_t> image(height * pitch);
+  for (int y = 0; y < height; y++)
+  {
+    uint8_t *p = &image[0] + y * pitch;
+    for (int x = 0; x < width; x++, p++)
+    {
+      *p = y;
+    }
+  }
+
+  w.WriteToFile("UnitTestsResults/Gray8Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale8, &image[0]);
+
+  std::string f, md5;
+  Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/Gray8Pattern.png");
+  Orthanc::Toolbox::ComputeMD5(md5, f);
+  ASSERT_EQ("5a9b98bea3d0a6d983980cc38bfbcdb3", md5);
+}
+
+TEST(PngWriter, Gray16Pattern)
+{
+  Orthanc::PngWriter w;
+  int width = 256;
+  int height = 256;
+  int pitch = width * 2 + 16;
+
+  std::vector<uint8_t> image(height * pitch);
+
+  int v = 0;
+  for (int y = 0; y < height; y++)
+  {
+    uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch);
+    for (int x = 0; x < width; x++, p++, v++)
+    {
+      *p = v;
+    }
+  }
+
+  w.WriteToFile("UnitTestsResults/Gray16Pattern.png", width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]);
+
+  std::string f, md5;
+  Orthanc::Toolbox::ReadFile(f, "UnitTestsResults/Gray16Pattern.png");
+  Orthanc::Toolbox::ComputeMD5(md5, f);
+  ASSERT_EQ("0785866a08bf0a02d2eeff87f658571c", md5);
+}
+
+TEST(PngWriter, EndToEnd)
+{
+  Orthanc::PngWriter w;
+  int width = 256;
+  int height = 256;
+  int pitch = width * 2 + 16;
+
+  std::vector<uint8_t> image(height * pitch);
+
+  int v = 0;
+  for (int y = 0; y < height; y++)
+  {
+    uint16_t *p = reinterpret_cast<uint16_t*>(&image[0] + y * pitch);
+    for (int x = 0; x < width; x++, p++, v++)
+    {
+      *p = v;
+    }
+  }
+
+  std::string s;
+  w.WriteToMemory(s, width, height, pitch, Orthanc::PixelFormat_Grayscale16, &image[0]);
+
+  {
+    Orthanc::PngReader r;
+    r.ReadFromMemory(s);
+
+    ASSERT_EQ(r.GetFormat(), Orthanc::PixelFormat_Grayscale16);
+    ASSERT_EQ(r.GetWidth(), width);
+    ASSERT_EQ(r.GetHeight(), height);
+
+    v = 0;
+    for (int y = 0; y < height; y++)
+    {
+      const uint16_t *p = reinterpret_cast<const uint16_t*>((const uint8_t*) r.GetConstBuffer() + y * r.GetPitch());
+      ASSERT_EQ(p, r.GetConstRow(y));
+      for (int x = 0; x < width; x++, p++, v++)
+      {
+        ASSERT_EQ(*p, v);
+      }
+    }
+  }
+
+  {
+    Orthanc::Toolbox::TemporaryFile tmp;
+    Orthanc::Toolbox::WriteFile(s, tmp.GetPath());
+
+    Orthanc::PngReader r2;
+    r2.ReadFromFile(tmp.GetPath());
+
+    ASSERT_EQ(r2.GetFormat(), Orthanc::PixelFormat_Grayscale16);
+    ASSERT_EQ(r2.GetWidth(), width);
+    ASSERT_EQ(r2.GetHeight(), height);
+
+    v = 0;
+    for (int y = 0; y < height; y++)
+    {
+      const uint16_t *p = reinterpret_cast<const uint16_t*>((const uint8_t*) r2.GetConstBuffer() + y * r2.GetPitch());
+      ASSERT_EQ(p, r2.GetConstRow(y));
+      for (int x = 0; x < width; x++, p++, v++)
+      {
+        ASSERT_EQ(*p, v);
+      }
+    }
+  }
+}
--- a/UnitTestsSources/RestApi.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,161 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
- **/
-
-
-#include "PrecompiledHeadersUnitTests.h"
-#include "gtest/gtest.h"
-
-#include <ctype.h>
-#include <glog/logging.h>
-
-#include "../Core/ChunkedBuffer.h"
-#include "../Core/HttpClient.h"
-#include "../Core/RestApi/RestApi.h"
-#include "../Core/Uuid.h"
-#include "../Core/OrthancException.h"
-#include "../Core/Compression/ZlibCompressor.h"
-
-using namespace Orthanc;
-
-#if !defined(UNIT_TESTS_WITH_HTTP_CONNEXIONS)
-#error "Please set UNIT_TESTS_WITH_HTTP_CONNEXIONS"
-#endif
-
-TEST(HttpClient, Basic)
-{
-  HttpClient c;
-  ASSERT_FALSE(c.IsVerbose());
-  c.SetVerbose(true);
-  ASSERT_TRUE(c.IsVerbose());
-  c.SetVerbose(false);
-  ASSERT_FALSE(c.IsVerbose());
-
-#if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1
-  Json::Value v;
-  c.SetUrl("http://orthanc.googlecode.com/hg/Resources/Configuration.json");
-  c.Apply(v);
-  ASSERT_TRUE(v.isMember("StorageDirectory"));
-  //ASSERT_EQ(GetLastStatusText());
-
-  v = Json::nullValue;
-
-  HttpClient cc(c);
-  cc.SetUrl("https://orthanc.googlecode.com/hg/Resources/Configuration.json");
-  cc.Apply(v);
-  ASSERT_TRUE(v.isMember("LuaScripts"));
-#endif
-}
-
-TEST(RestApi, ChunkedBuffer)
-{
-  ChunkedBuffer b;
-  ASSERT_EQ(0, b.GetNumBytes());
-
-  b.AddChunk("hello", 5);
-  ASSERT_EQ(5, b.GetNumBytes());
-
-  b.AddChunk("world", 5);
-  ASSERT_EQ(10, b.GetNumBytes());
-
-  std::string s;
-  b.Flatten(s);
-  ASSERT_EQ("helloworld", s);
-}
-
-TEST(RestApi, ParseCookies)
-{
-  HttpHandler::Arguments headers;
-  HttpHandler::Arguments cookies;
-
-  headers["cookie"] = "a=b;c=d;;;e=f;;g=h;";
-  HttpHandler::ParseCookies(cookies, headers);
-  ASSERT_EQ(4u, cookies.size());
-  ASSERT_EQ("b", cookies["a"]);
-  ASSERT_EQ("d", cookies["c"]);
-  ASSERT_EQ("f", cookies["e"]);
-  ASSERT_EQ("h", cookies["g"]);
-
-  headers["cookie"] = "  name =  value  ; name2=value2";
-  HttpHandler::ParseCookies(cookies, headers);
-  ASSERT_EQ(2u, cookies.size());
-  ASSERT_EQ("value", cookies["name"]);
-  ASSERT_EQ("value2", cookies["name2"]);
-
-  headers["cookie"] = "  ;;;    ";
-  HttpHandler::ParseCookies(cookies, headers);
-  ASSERT_EQ(0u, cookies.size());
-
-  headers["cookie"] = "  ;   n=v  ;;    ";
-  HttpHandler::ParseCookies(cookies, headers);
-  ASSERT_EQ(1u, cookies.size());
-  ASSERT_EQ("v", cookies["n"]);
-}
-
-TEST(RestApi, RestApiPath)
-{
-  RestApiPath::Components args;
-  UriComponents trail;
-
-  {
-    RestApiPath uri("/coucou/{abc}/d/*");
-    ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/e/f/g"));
-    ASSERT_EQ(1u, args.size());
-    ASSERT_EQ(3u, trail.size());
-    ASSERT_EQ("moi", args["abc"]);
-    ASSERT_EQ("e", trail[0]);
-    ASSERT_EQ("f", trail[1]);
-    ASSERT_EQ("g", trail[2]);
-
-    ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/f"));
-    ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/"));
-    ASSERT_FALSE(uri.Match(args, trail, "/a/moi/d"));
-    ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi"));
-  }
-
-  {
-    RestApiPath uri("/coucou/{abc}/d");
-    ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/d/e/f/g"));
-    ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d"));
-    ASSERT_EQ(1u, args.size());
-    ASSERT_EQ(0u, trail.size());
-    ASSERT_EQ("moi", args["abc"]);
-  }
-
-  {
-    RestApiPath uri("/*");
-    ASSERT_TRUE(uri.Match(args, trail, "/a/b/c"));
-    ASSERT_EQ(0u, args.size());
-    ASSERT_EQ(3u, trail.size());
-    ASSERT_EQ("a", trail[0]);
-    ASSERT_EQ("b", trail[1]);
-    ASSERT_EQ("c", trail[2]);
-  }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/RestApiTests.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,283 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "PrecompiledHeadersUnitTests.h"
+#include "gtest/gtest.h"
+
+#include <ctype.h>
+#include <glog/logging.h>
+
+#include "../Core/ChunkedBuffer.h"
+#include "../Core/HttpClient.h"
+#include "../Core/RestApi/RestApi.h"
+#include "../Core/Uuid.h"
+#include "../Core/OrthancException.h"
+#include "../Core/Compression/ZlibCompressor.h"
+#include "../Core/RestApi/RestApiHierarchy.h"
+
+using namespace Orthanc;
+
+#if !defined(UNIT_TESTS_WITH_HTTP_CONNEXIONS)
+#error "Please set UNIT_TESTS_WITH_HTTP_CONNEXIONS"
+#endif
+
+TEST(HttpClient, Basic)
+{
+  HttpClient c;
+  ASSERT_FALSE(c.IsVerbose());
+  c.SetVerbose(true);
+  ASSERT_TRUE(c.IsVerbose());
+  c.SetVerbose(false);
+  ASSERT_FALSE(c.IsVerbose());
+
+#if UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1
+  Json::Value v;
+  c.SetUrl("http://orthanc.googlecode.com/hg/Resources/Configuration.json");
+  c.Apply(v);
+  ASSERT_TRUE(v.isMember("StorageDirectory"));
+  //ASSERT_EQ(GetLastStatusText());
+
+  v = Json::nullValue;
+
+  HttpClient cc(c);
+  cc.SetUrl("https://orthanc.googlecode.com/hg/Resources/Configuration.json");
+  cc.Apply(v);
+  ASSERT_TRUE(v.isMember("LuaScripts"));
+#endif
+}
+
+TEST(RestApi, ChunkedBuffer)
+{
+  ChunkedBuffer b;
+  ASSERT_EQ(0, b.GetNumBytes());
+
+  b.AddChunk("hello", 5);
+  ASSERT_EQ(5, b.GetNumBytes());
+
+  b.AddChunk("world", 5);
+  ASSERT_EQ(10, b.GetNumBytes());
+
+  std::string s;
+  b.Flatten(s);
+  ASSERT_EQ("helloworld", s);
+}
+
+TEST(RestApi, ParseCookies)
+{
+  HttpHandler::Arguments headers;
+  HttpHandler::Arguments cookies;
+
+  headers["cookie"] = "a=b;c=d;;;e=f;;g=h;";
+  HttpHandler::ParseCookies(cookies, headers);
+  ASSERT_EQ(4u, cookies.size());
+  ASSERT_EQ("b", cookies["a"]);
+  ASSERT_EQ("d", cookies["c"]);
+  ASSERT_EQ("f", cookies["e"]);
+  ASSERT_EQ("h", cookies["g"]);
+
+  headers["cookie"] = "  name =  value  ; name2=value2";
+  HttpHandler::ParseCookies(cookies, headers);
+  ASSERT_EQ(2u, cookies.size());
+  ASSERT_EQ("value", cookies["name"]);
+  ASSERT_EQ("value2", cookies["name2"]);
+
+  headers["cookie"] = "  ;;;    ";
+  HttpHandler::ParseCookies(cookies, headers);
+  ASSERT_EQ(0u, cookies.size());
+
+  headers["cookie"] = "  ;   n=v  ;;    ";
+  HttpHandler::ParseCookies(cookies, headers);
+  ASSERT_EQ(1u, cookies.size());
+  ASSERT_EQ("v", cookies["n"]);
+}
+
+TEST(RestApi, RestApiPath)
+{
+  HttpHandler::Arguments args;
+  UriComponents trail;
+
+  {
+    RestApiPath uri("/coucou/{abc}/d/*");
+    ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/e/f/g"));
+    ASSERT_EQ(1u, args.size());
+    ASSERT_EQ(3u, trail.size());
+    ASSERT_EQ("moi", args["abc"]);
+    ASSERT_EQ("e", trail[0]);
+    ASSERT_EQ("f", trail[1]);
+    ASSERT_EQ("g", trail[2]);
+
+    ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/f"));
+    ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/"));
+    ASSERT_FALSE(uri.Match(args, trail, "/a/moi/d"));
+    ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi"));
+
+    ASSERT_EQ(3u, uri.GetLevelCount());
+    ASSERT_TRUE(uri.IsUniversalTrailing());
+
+    ASSERT_EQ("coucou", uri.GetLevelName(0));
+    ASSERT_THROW(uri.GetWildcardName(0), OrthancException);
+
+    ASSERT_EQ("abc", uri.GetWildcardName(1));
+    ASSERT_THROW(uri.GetLevelName(1), OrthancException);
+
+    ASSERT_EQ("d", uri.GetLevelName(2));
+    ASSERT_THROW(uri.GetWildcardName(2), OrthancException);
+  }
+
+  {
+    RestApiPath uri("/coucou/{abc}/d");
+    ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/d/e/f/g"));
+    ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d"));
+    ASSERT_EQ(1u, args.size());
+    ASSERT_EQ(0u, trail.size());
+    ASSERT_EQ("moi", args["abc"]);
+
+    ASSERT_EQ(3u, uri.GetLevelCount());
+    ASSERT_FALSE(uri.IsUniversalTrailing());
+
+    ASSERT_EQ("coucou", uri.GetLevelName(0));
+    ASSERT_THROW(uri.GetWildcardName(0), OrthancException);
+
+    ASSERT_EQ("abc", uri.GetWildcardName(1));
+    ASSERT_THROW(uri.GetLevelName(1), OrthancException);
+
+    ASSERT_EQ("d", uri.GetLevelName(2));
+    ASSERT_THROW(uri.GetWildcardName(2), OrthancException);
+  }
+
+  {
+    RestApiPath uri("/*");
+    ASSERT_TRUE(uri.Match(args, trail, "/a/b/c"));
+    ASSERT_EQ(0u, args.size());
+    ASSERT_EQ(3u, trail.size());
+    ASSERT_EQ("a", trail[0]);
+    ASSERT_EQ("b", trail[1]);
+    ASSERT_EQ("c", trail[2]);
+
+    ASSERT_EQ(0u, uri.GetLevelCount());
+    ASSERT_TRUE(uri.IsUniversalTrailing());
+  }
+}
+
+
+
+
+
+
+static int testValue;
+
+template <int value>
+static void SetValue(RestApiGetCall& get)
+{
+  testValue = value;
+}
+
+
+static bool GetDirectory(Json::Value& target,
+                         RestApiHierarchy& hierarchy, 
+                         const std::string& uri)
+{
+  UriComponents p;
+  Toolbox::SplitUriComponents(p, uri);
+  return hierarchy.GetDirectory(target, p);
+}
+
+
+
+namespace
+{
+  class MyVisitor : public RestApiHierarchy::IVisitor
+  {
+  public:
+    virtual bool Visit(const RestApiHierarchy::Resource& resource,
+                       const UriComponents& uri,
+                       const HttpHandler::Arguments& components,
+                       const UriComponents& trailing)
+    {
+      return resource.Handle(*reinterpret_cast<RestApiGetCall*>(NULL));
+    }
+  };
+}
+
+
+static bool HandleGet(RestApiHierarchy& hierarchy, 
+                      const std::string& uri)
+{
+  UriComponents p;
+  Toolbox::SplitUriComponents(p, uri);
+  MyVisitor visitor;
+  return hierarchy.LookupResource(p, visitor);
+}
+
+
+TEST(RestApi, RestApiHierarchy)
+{
+  RestApiHierarchy root;
+  root.Register("/hello/world/test", SetValue<1>);
+  root.Register("/hello/world/test2", SetValue<2>);
+  root.Register("/hello/{world}/test3/test4", SetValue<3>);
+  root.Register("/hello2/*", SetValue<4>);
+
+  Json::Value m;
+  root.CreateSiteMap(m);
+  std::cout << m;
+
+  Json::Value d;
+  ASSERT_FALSE(GetDirectory(d, root, "/hello"));
+
+  ASSERT_TRUE(GetDirectory(d, root, "/hello/a")); 
+  ASSERT_EQ(1u, d.size());
+  ASSERT_EQ("test3", d[0].asString());
+
+  ASSERT_TRUE(GetDirectory(d, root, "/hello/world"));
+  ASSERT_EQ(2u, d.size());
+
+  ASSERT_TRUE(GetDirectory(d, root, "/hello/a/test3"));
+  ASSERT_EQ(1u, d.size());
+  ASSERT_EQ("test4", d[0].asString());
+
+  ASSERT_FALSE(GetDirectory(d, root, "/hello/world/test"));
+  ASSERT_FALSE(GetDirectory(d, root, "/hello/world/test2"));
+  ASSERT_FALSE(GetDirectory(d, root, "/hello2"));
+
+  testValue = 0;
+  ASSERT_TRUE(HandleGet(root, "/hello/world/test"));
+  ASSERT_EQ(testValue, 1);
+  ASSERT_TRUE(HandleGet(root, "/hello/world/test2"));
+  ASSERT_EQ(testValue, 2);
+  ASSERT_TRUE(HandleGet(root, "/hello/b/test3/test4"));
+  ASSERT_EQ(testValue, 3);
+  ASSERT_FALSE(HandleGet(root, "/hello/b/test3/test"));
+  ASSERT_EQ(testValue, 3);
+  ASSERT_TRUE(HandleGet(root, "/hello2/a/b"));
+  ASSERT_EQ(testValue, 4);
+}
--- a/UnitTestsSources/SQLite.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,334 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
- **/
-
-
-#include "PrecompiledHeadersUnitTests.h"
-#include "gtest/gtest.h"
-
-#include "../Core/Toolbox.h"
-#include "../Core/SQLite/Connection.h"
-#include "../Core/SQLite/Statement.h"
-#include "../Core/SQLite/Transaction.h"
-
-#include <sqlite3.h>
-
-using namespace Orthanc;
-
-
-TEST(SQLite, Configuration)
-{
-  ASSERT_EQ(1, sqlite3_threadsafe());
-}
-
-
-TEST(SQLite, Connection)
-{
-  Toolbox::RemoveFile("UnitTestsResults/coucou");
-  SQLite::Connection c;
-  c.Open("UnitTestsResults/coucou");
-  c.Execute("CREATE TABLE c(k INTEGER PRIMARY KEY AUTOINCREMENT, v INTEGER)");
-  c.Execute("INSERT INTO c VALUES(NULL, 42);");
-}
-
-
-TEST(SQLite, StatementReferenceBasic)
-{
-  sqlite3* db;
-  sqlite3_open(":memory:", &db);
-
-  {
-    SQLite::StatementReference r(db, "SELECT * FROM sqlite_master");
-    ASSERT_EQ(0u, r.GetReferenceCount());
-
-    {
-      SQLite::StatementReference r1(r);
-      ASSERT_EQ(1u, r.GetReferenceCount());
-      ASSERT_EQ(0u, r1.GetReferenceCount());
-
-      {
-        SQLite::StatementReference r2(r);
-        ASSERT_EQ(2u, r.GetReferenceCount());
-        ASSERT_EQ(0u, r1.GetReferenceCount());
-        ASSERT_EQ(0u, r2.GetReferenceCount());
-
-        SQLite::StatementReference r3(r2);
-        ASSERT_EQ(3u, r.GetReferenceCount());
-        ASSERT_EQ(0u, r1.GetReferenceCount());
-        ASSERT_EQ(0u, r2.GetReferenceCount());
-        ASSERT_EQ(0u, r3.GetReferenceCount());
-      }
-
-      ASSERT_EQ(1u, r.GetReferenceCount());
-      ASSERT_EQ(0u, r1.GetReferenceCount());
-
-      {
-        SQLite::StatementReference r2(r);
-        ASSERT_EQ(2u, r.GetReferenceCount());
-        ASSERT_EQ(0u, r1.GetReferenceCount());
-        ASSERT_EQ(0u, r2.GetReferenceCount());
-      }
-
-      ASSERT_EQ(1u, r.GetReferenceCount());
-      ASSERT_EQ(0u, r1.GetReferenceCount());
-    }
-
-    ASSERT_EQ(0u, r.GetReferenceCount());
-  }
-
-  sqlite3_close(db);
-}
-
-TEST(SQLite, StatementBasic)
-{
-  SQLite::Connection c;
-  c.OpenInMemory();
-  
-  SQLite::Statement s(c, "SELECT * from sqlite_master");
-  s.Run();
-
-  for (unsigned int i = 0; i < 5; i++)
-  {
-    SQLite::Statement cs(c, SQLITE_FROM_HERE, "SELECT * from sqlite_master");
-    cs.Step();
-  }
-}
-
-
-namespace
-{
-  static bool destroyed;
-
-  class MyFunc : public SQLite::IScalarFunction
-  {
-  public:
-    MyFunc()
-    {
-      destroyed = false;
-    }
-
-    virtual ~MyFunc()
-    {
-      destroyed = true;
-    }
-
-    virtual const char* GetName() const
-    {
-      return "MYFUNC";
-    }
-
-    virtual unsigned int GetCardinality() const
-    {
-      return 2;
-    }
-
-    virtual void Compute(SQLite::FunctionContext& context)
-    {
-      context.SetIntResult(1000 + context.GetIntValue(0) * context.GetIntValue(1));
-    }
-  };
-
-  class MyDelete : public SQLite::IScalarFunction
-  {
-  public:
-    std::set<int> deleted_;
-
-    virtual const char* GetName() const
-    {
-      return "MYDELETE";
-    }
-
-    virtual unsigned int GetCardinality() const
-    {
-      return 1;
-    }
-
-    virtual void Compute(SQLite::FunctionContext& context)
-    {
-      deleted_.insert(context.GetIntValue(0));
-      context.SetNullResult();
-    }
-  };
-}
-
-TEST(SQLite, ScalarFunction)
-{
-  {
-    SQLite::Connection c;
-    c.OpenInMemory();
-    c.Register(new MyFunc());
-    c.Execute("CREATE TABLE t(id INTEGER PRIMARY KEY, v1 INTEGER, v2 INTEGER);");
-    c.Execute("INSERT INTO t VALUES(NULL, 2, 3);");
-    c.Execute("INSERT INTO t VALUES(NULL, 4, 4);");
-    c.Execute("INSERT INTO t VALUES(NULL, 6, 5);");
-    SQLite::Statement t(c, "SELECT MYFUNC(v1, v2), v1, v2 FROM t");
-    int i = 0;
-    while (t.Step())
-    {
-      ASSERT_EQ(t.ColumnInt(0), 1000 + t.ColumnInt(1) * t.ColumnInt(2));
-      i++;
-    }
-    ASSERT_EQ(3, i);
-    ASSERT_FALSE(destroyed);
-  }
-  ASSERT_TRUE(destroyed);
-}
-
-TEST(SQLite, CascadedDeleteCallback)
-{
-  SQLite::Connection c;
-  c.OpenInMemory();
-  MyDelete *func = new MyDelete();
-  c.Register(func);
-  c.Execute("CREATE TABLE parent(id INTEGER PRIMARY KEY, dummy INTEGER);");
-  c.Execute("CREATE TABLE child("
-            "  id INTEGER PRIMARY KEY, "
-            "  parent INTEGER REFERENCES parent(id) ON DELETE CASCADE, "
-            "  value INTEGER);");
-  c.Execute("CREATE TRIGGER childRemoved "
-            "AFTER DELETE ON child "
-            "FOR EACH ROW BEGIN "
-            "  SELECT MYDELETE(old.value); "
-            "END;");
-
-  c.Execute("INSERT INTO parent VALUES(42, 100);");
-  c.Execute("INSERT INTO parent VALUES(43, 101);");
-
-  c.Execute("INSERT INTO child VALUES(NULL, 42, 4200);");
-  c.Execute("INSERT INTO child VALUES(NULL, 42, 4201);");
-
-  c.Execute("INSERT INTO child VALUES(NULL, 43, 4300);");
-  c.Execute("INSERT INTO child VALUES(NULL, 43, 4301);");
-
-  // The following command deletes "parent(43, 101)", then in turns
-  // "child(NULL, 43, 4300/4301)", then calls the MyDelete on 4300 and
-  // 4301
-  c.Execute("DELETE FROM parent WHERE dummy=101");
-
-  ASSERT_EQ(2u, func->deleted_.size());
-  ASSERT_TRUE(func->deleted_.find(4300) != func->deleted_.end());
-  ASSERT_TRUE(func->deleted_.find(4301) != func->deleted_.end());
-}
-
-
-TEST(SQLite, EmptyTransactions)
-{
-  try
-  {
-    SQLite::Connection c;
-    c.OpenInMemory();
-
-    c.Execute("CREATE TABLE a(id INTEGER PRIMARY KEY);");
-    c.Execute("INSERT INTO a VALUES(NULL)");
-      
-    {
-      SQLite::Transaction t(c);
-      t.Begin();
-      {
-        SQLite::Statement s(c, SQLITE_FROM_HERE, "SELECT * FROM a");
-        s.Step();
-      }
-      //t.Commit();
-    }
-
-    {
-      SQLite::Statement s(c, SQLITE_FROM_HERE, "SELECT * FROM a");
-      s.Step();
-    }
-  }
-  catch (OrthancException& e)
-  {
-    fprintf(stderr, "Exception: [%s]\n", e.What());
-    throw e;
-  }
-}
-
-
-TEST(SQLite, Types)
-{
-  SQLite::Connection c;
-  c.OpenInMemory();
-  c.Execute("CREATE TABLE a(id INTEGER PRIMARY KEY, value)");
-
-  {
-    SQLite::Statement s(c, std::string("SELECT * FROM a"));
-    ASSERT_EQ(2, s.ColumnCount());
-    ASSERT_FALSE(s.Step());
-  }
-
-  {
-    SQLite::Statement s(c, SQLITE_FROM_HERE, std::string("SELECT * FROM a"));
-    ASSERT_FALSE(s.Step());
-    ASSERT_EQ("SELECT * FROM a", s.GetOriginalSQLStatement());
-  }
-
-  {
-    SQLite::Statement s(c, SQLITE_FROM_HERE, "INSERT INTO a VALUES(NULL, ?);");
-    s.BindNull(0);             ASSERT_TRUE(s.Run()); s.Reset();
-    s.BindBool(0, true);       ASSERT_TRUE(s.Run()); s.Reset();
-    s.BindInt(0, 42);          ASSERT_TRUE(s.Run()); s.Reset();
-    s.BindInt64(0, 42ll);      ASSERT_TRUE(s.Run()); s.Reset();
-    s.BindDouble(0, 42.5);     ASSERT_TRUE(s.Run()); s.Reset();
-    s.BindCString(0, "Hello"); ASSERT_TRUE(s.Run()); s.Reset();
-    s.BindBlob(0, "Hello", 5); ASSERT_TRUE(s.Run()); s.Reset();
-  }
-
-  {
-    SQLite::Statement s(c, SQLITE_FROM_HERE, std::string("SELECT * FROM a"));
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ(SQLite::COLUMN_TYPE_NULL, s.GetColumnType(1));
-    ASSERT_TRUE(s.ColumnIsNull(1));
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ(SQLite::COLUMN_TYPE_INTEGER, s.GetColumnType(1));
-    ASSERT_TRUE(s.ColumnBool(1));
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ(SQLite::COLUMN_TYPE_INTEGER, s.GetColumnType(1));
-    ASSERT_EQ(42, s.ColumnInt(1));
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ(SQLite::COLUMN_TYPE_INTEGER, s.GetColumnType(1));
-    ASSERT_EQ(42ll, s.ColumnInt64(1));
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ(SQLite::COLUMN_TYPE_FLOAT, s.GetColumnType(1));
-    ASSERT_DOUBLE_EQ(42.5, s.ColumnDouble(1));
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ(SQLite::COLUMN_TYPE_TEXT, s.GetColumnType(1));
-    ASSERT_EQ("Hello", s.ColumnString(1));
-    ASSERT_TRUE(s.Step());
-    ASSERT_EQ(SQLite::COLUMN_TYPE_BLOB, s.GetColumnType(1));
-    ASSERT_EQ(5, s.ColumnByteLength(1));
-    ASSERT_TRUE(!memcmp("Hello", s.ColumnBlob(1), 5));
-
-    std::string t;
-    ASSERT_TRUE(s.ColumnBlobAsString(1, &t));
-    ASSERT_EQ("Hello", t);
-
-    ASSERT_FALSE(s.Step());
-  }
-}
--- a/UnitTestsSources/SQLiteChromium.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,377 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
- **/
-
-
-#include "PrecompiledHeadersUnitTests.h"
-#include "gtest/gtest.h"
-
-#include "../Core/Toolbox.h"
-#include "../Core/SQLite/Connection.h"
-#include "../Core/SQLite/Statement.h"
-#include "../Core/SQLite/Transaction.h"
-
-#include <sqlite3.h>
-
-
-using namespace Orthanc;
-using namespace Orthanc::SQLite;
-
-
-/********************************************************************
- ** Tests from
- ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/connection_unittest.cc
- ********************************************************************/
-
-class SQLConnectionTest : public testing::Test 
-{
-public:
-  SQLConnectionTest()
-  {
-  }
-
-  virtual ~SQLConnectionTest()
-  {
-  }
-
-  virtual void SetUp() 
-  {
-    db_.OpenInMemory();
-  }
-
-  virtual void TearDown() 
-  {
-    db_.Close();
-  }
-
-  Connection& db() 
-  { 
-    return db_; 
-  }
-
-private:
-  Connection db_;
-};
-
-
-
-TEST_F(SQLConnectionTest, Execute) 
-{
-  // Valid statement should return true.
-  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
-  EXPECT_EQ(SQLITE_OK, db().GetErrorCode());
-
-  // Invalid statement should fail.
-  ASSERT_EQ(SQLITE_ERROR,
-            db().ExecuteAndReturnErrorCode("CREATE TAB foo (a, b"));
-  EXPECT_EQ(SQLITE_ERROR, db().GetErrorCode());
-}
-
-TEST_F(SQLConnectionTest, ExecuteWithErrorCode) {
-  ASSERT_EQ(SQLITE_OK,
-            db().ExecuteAndReturnErrorCode("CREATE TABLE foo (a, b)"));
-  ASSERT_EQ(SQLITE_ERROR,
-            db().ExecuteAndReturnErrorCode("CREATE TABLE TABLE"));
-  ASSERT_EQ(SQLITE_ERROR,
-            db().ExecuteAndReturnErrorCode(
-              "INSERT INTO foo(a, b) VALUES (1, 2, 3, 4)"));
-}
-
-TEST_F(SQLConnectionTest, CachedStatement) {
-  StatementId id1("foo", 12);
-  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
-  ASSERT_TRUE(db().Execute("INSERT INTO foo(a, b) VALUES (12, 13)"));
-
-  // Create a new cached statement.
-  {
-    Statement s(db(), id1, "SELECT a FROM foo");
-    ASSERT_TRUE(s.Step());
-    EXPECT_EQ(12, s.ColumnInt(0));
-  }
-
-  // The statement should be cached still.
-  EXPECT_TRUE(db().HasCachedStatement(id1));
-
-  {
-    // Get the same statement using different SQL. This should ignore our
-    // SQL and use the cached one (so it will be valid).
-    Statement s(db(), id1, "something invalid(");
-    ASSERT_TRUE(s.Step());
-    EXPECT_EQ(12, s.ColumnInt(0));
-  }
-
-  // Make sure other statements aren't marked as cached.
-  EXPECT_FALSE(db().HasCachedStatement(SQLITE_FROM_HERE));
-}
-
-TEST_F(SQLConnectionTest, IsSQLValidTest) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
-  ASSERT_TRUE(db().IsSQLValid("SELECT a FROM foo"));
-  ASSERT_FALSE(db().IsSQLValid("SELECT no_exist FROM foo"));
-}
-
-
-
-TEST_F(SQLConnectionTest, DoesStuffExist) {
-  // Test DoesTableExist.
-  EXPECT_FALSE(db().DoesTableExist("foo"));
-  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
-  EXPECT_TRUE(db().DoesTableExist("foo"));
-
-  // Should be case sensitive.
-  EXPECT_FALSE(db().DoesTableExist("FOO"));
-
-  // Test DoesColumnExist.
-  EXPECT_FALSE(db().DoesColumnExist("foo", "bar"));
-  EXPECT_TRUE(db().DoesColumnExist("foo", "a"));
-
-  // Testing for a column on a nonexistent table.
-  EXPECT_FALSE(db().DoesColumnExist("bar", "b"));
-}
-
-TEST_F(SQLConnectionTest, GetLastInsertRowId) {
-  ASSERT_TRUE(db().Execute("CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"));
-
-  ASSERT_TRUE(db().Execute("INSERT INTO foo (value) VALUES (12)"));
-
-  // Last insert row ID should be valid.
-  int64_t row = db().GetLastInsertRowId();
-  EXPECT_LT(0, row);
-
-  // It should be the primary key of the row we just inserted.
-  Statement s(db(), "SELECT value FROM foo WHERE id=?");
-  s.BindInt64(0, row);
-  ASSERT_TRUE(s.Step());
-  EXPECT_EQ(12, s.ColumnInt(0));
-}
-
-TEST_F(SQLConnectionTest, Rollback) {
-  ASSERT_TRUE(db().BeginTransaction());
-  ASSERT_TRUE(db().BeginTransaction());
-  EXPECT_EQ(2, db().GetTransactionNesting());
-  db().RollbackTransaction();
-  EXPECT_FALSE(db().CommitTransaction());
-  EXPECT_TRUE(db().BeginTransaction());
-}
-
-
-
-
-/********************************************************************
- ** Tests from
- ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/statement_unittest.cc
- ********************************************************************/
-
-namespace Orthanc
-{
-  namespace SQLite
-  {
-    class SQLStatementTest : public SQLConnectionTest
-    {
-    };
-
-    TEST_F(SQLStatementTest, Run) {
-      ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
-      ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)"));
-
-      Statement s(db(), "SELECT b FROM foo WHERE a=?");
-      // Stepping it won't work since we haven't bound the value.
-      EXPECT_FALSE(s.Step());
-
-      // Run should fail since this produces output, and we should use Step(). This
-      // gets a bit wonky since sqlite says this is OK so succeeded is set.
-      s.Reset(true);
-      s.BindInt(0, 3);
-      EXPECT_FALSE(s.Run());
-      EXPECT_EQ(SQLITE_ROW, db().GetErrorCode());
-
-      // Resetting it should put it back to the previous state (not runnable).
-      s.Reset(true);
-
-      // Binding and stepping should produce one row.
-      s.BindInt(0, 3);
-      EXPECT_TRUE(s.Step());
-      EXPECT_EQ(12, s.ColumnInt(0));
-      EXPECT_FALSE(s.Step());
-    }
-
-    TEST_F(SQLStatementTest, BasicErrorCallback) {
-      ASSERT_TRUE(db().Execute("CREATE TABLE foo (a INTEGER PRIMARY KEY, b)"));
-      // Insert in the foo table the primary key. It is an error to insert
-      // something other than an number. This error causes the error callback
-      // handler to be called with SQLITE_MISMATCH as error code.
-      Statement s(db(), "INSERT INTO foo (a) VALUES (?)");
-      s.BindCString(0, "bad bad");
-      EXPECT_THROW(s.Run(), OrthancException);
-    }
-
-    TEST_F(SQLStatementTest, Reset) {
-      ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
-      ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)"));
-      ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (4, 13)"));
-
-      Statement s(db(), "SELECT b FROM foo WHERE a = ? ");
-      s.BindInt(0, 3);
-      ASSERT_TRUE(s.Step());
-      EXPECT_EQ(12, s.ColumnInt(0));
-      ASSERT_FALSE(s.Step());
-
-      s.Reset(false);
-      // Verify that we can get all rows again.
-      ASSERT_TRUE(s.Step());
-      EXPECT_EQ(12, s.ColumnInt(0));
-      EXPECT_FALSE(s.Step());
-
-      s.Reset(true);
-      ASSERT_FALSE(s.Step());
-    }
-  }
-}
-
-
-
-
-
-
-/********************************************************************
- ** Tests from
- ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/transaction_unittest.cc
- ********************************************************************/
-
-class SQLTransactionTest : public SQLConnectionTest
-{
-public:
-  virtual void SetUp()
-  {
-    SQLConnectionTest::SetUp();
-    ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
-  }
-
-  // Returns the number of rows in table "foo".
-  int CountFoo() 
-  {
-    Statement count(db(), "SELECT count(*) FROM foo");
-    count.Step();
-    return count.ColumnInt(0);
-  }
-};
-
-
-TEST_F(SQLTransactionTest, Commit) {
-  {
-    Transaction t(db());
-    EXPECT_FALSE(t.IsOpen());
-    t.Begin();
-    EXPECT_TRUE(t.IsOpen());
-
-    EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
-
-    t.Commit();
-    EXPECT_FALSE(t.IsOpen());
-  }
-
-  EXPECT_EQ(1, CountFoo());
-}
-
-TEST_F(SQLTransactionTest, Rollback) {
-  // Test some basic initialization, and that rollback runs when you exit the
-  // scope.
-  {
-    Transaction t(db());
-    EXPECT_FALSE(t.IsOpen());
-    t.Begin();
-    EXPECT_TRUE(t.IsOpen());
-
-    EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
-  }
-
-  // Nothing should have been committed since it was implicitly rolled back.
-  EXPECT_EQ(0, CountFoo());
-
-  // Test explicit rollback.
-  Transaction t2(db());
-  EXPECT_FALSE(t2.IsOpen());
-  t2.Begin();
-
-  EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
-  t2.Rollback();
-  EXPECT_FALSE(t2.IsOpen());
-
-  // Nothing should have been committed since it was explicitly rolled back.
-  EXPECT_EQ(0, CountFoo());
-}
-
-// Rolling back any part of a transaction should roll back all of them.
-TEST_F(SQLTransactionTest, NestedRollback) {
-  EXPECT_EQ(0, db().GetTransactionNesting());
-
-  // Outermost transaction.
-  {
-    Transaction outer(db());
-    outer.Begin();
-    EXPECT_EQ(1, db().GetTransactionNesting());
-
-    // The first inner one gets committed.
-    {
-      Transaction inner1(db());
-      inner1.Begin();
-      EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
-      EXPECT_EQ(2, db().GetTransactionNesting());
-
-      inner1.Commit();
-      EXPECT_EQ(1, db().GetTransactionNesting());
-    }
-
-    // One row should have gotten inserted.
-    EXPECT_EQ(1, CountFoo());
-
-    // The second inner one gets rolled back.
-    {
-      Transaction inner2(db());
-      inner2.Begin();
-      EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
-      EXPECT_EQ(2, db().GetTransactionNesting());
-
-      inner2.Rollback();
-      EXPECT_EQ(1, db().GetTransactionNesting());
-    }
-
-    // A third inner one will fail in Begin since one has already been rolled
-    // back.
-    EXPECT_EQ(1, db().GetTransactionNesting());
-    {
-      Transaction inner3(db());
-      EXPECT_THROW(inner3.Begin(), OrthancException);
-      EXPECT_EQ(1, db().GetTransactionNesting());
-    }
-  }
-  EXPECT_EQ(0, db().GetTransactionNesting());
-  EXPECT_EQ(0, CountFoo());
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/SQLiteChromiumTests.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,377 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "PrecompiledHeadersUnitTests.h"
+#include "gtest/gtest.h"
+
+#include "../Core/Toolbox.h"
+#include "../Core/SQLite/Connection.h"
+#include "../Core/SQLite/Statement.h"
+#include "../Core/SQLite/Transaction.h"
+
+#include <sqlite3.h>
+
+
+using namespace Orthanc;
+using namespace Orthanc::SQLite;
+
+
+/********************************************************************
+ ** Tests from
+ ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/connection_unittest.cc
+ ********************************************************************/
+
+class SQLConnectionTest : public testing::Test 
+{
+public:
+  SQLConnectionTest()
+  {
+  }
+
+  virtual ~SQLConnectionTest()
+  {
+  }
+
+  virtual void SetUp() 
+  {
+    db_.OpenInMemory();
+  }
+
+  virtual void TearDown() 
+  {
+    db_.Close();
+  }
+
+  Connection& db() 
+  { 
+    return db_; 
+  }
+
+private:
+  Connection db_;
+};
+
+
+
+TEST_F(SQLConnectionTest, Execute) 
+{
+  // Valid statement should return true.
+  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
+  EXPECT_EQ(SQLITE_OK, db().GetErrorCode());
+
+  // Invalid statement should fail.
+  ASSERT_EQ(SQLITE_ERROR,
+            db().ExecuteAndReturnErrorCode("CREATE TAB foo (a, b"));
+  EXPECT_EQ(SQLITE_ERROR, db().GetErrorCode());
+}
+
+TEST_F(SQLConnectionTest, ExecuteWithErrorCode) {
+  ASSERT_EQ(SQLITE_OK,
+            db().ExecuteAndReturnErrorCode("CREATE TABLE foo (a, b)"));
+  ASSERT_EQ(SQLITE_ERROR,
+            db().ExecuteAndReturnErrorCode("CREATE TABLE TABLE"));
+  ASSERT_EQ(SQLITE_ERROR,
+            db().ExecuteAndReturnErrorCode(
+              "INSERT INTO foo(a, b) VALUES (1, 2, 3, 4)"));
+}
+
+TEST_F(SQLConnectionTest, CachedStatement) {
+  StatementId id1("foo", 12);
+  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
+  ASSERT_TRUE(db().Execute("INSERT INTO foo(a, b) VALUES (12, 13)"));
+
+  // Create a new cached statement.
+  {
+    Statement s(db(), id1, "SELECT a FROM foo");
+    ASSERT_TRUE(s.Step());
+    EXPECT_EQ(12, s.ColumnInt(0));
+  }
+
+  // The statement should be cached still.
+  EXPECT_TRUE(db().HasCachedStatement(id1));
+
+  {
+    // Get the same statement using different SQL. This should ignore our
+    // SQL and use the cached one (so it will be valid).
+    Statement s(db(), id1, "something invalid(");
+    ASSERT_TRUE(s.Step());
+    EXPECT_EQ(12, s.ColumnInt(0));
+  }
+
+  // Make sure other statements aren't marked as cached.
+  EXPECT_FALSE(db().HasCachedStatement(SQLITE_FROM_HERE));
+}
+
+TEST_F(SQLConnectionTest, IsSQLValidTest) {
+  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
+  ASSERT_TRUE(db().IsSQLValid("SELECT a FROM foo"));
+  ASSERT_FALSE(db().IsSQLValid("SELECT no_exist FROM foo"));
+}
+
+
+
+TEST_F(SQLConnectionTest, DoesStuffExist) {
+  // Test DoesTableExist.
+  EXPECT_FALSE(db().DoesTableExist("foo"));
+  ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
+  EXPECT_TRUE(db().DoesTableExist("foo"));
+
+  // Should be case sensitive.
+  EXPECT_FALSE(db().DoesTableExist("FOO"));
+
+  // Test DoesColumnExist.
+  EXPECT_FALSE(db().DoesColumnExist("foo", "bar"));
+  EXPECT_TRUE(db().DoesColumnExist("foo", "a"));
+
+  // Testing for a column on a nonexistent table.
+  EXPECT_FALSE(db().DoesColumnExist("bar", "b"));
+}
+
+TEST_F(SQLConnectionTest, GetLastInsertRowId) {
+  ASSERT_TRUE(db().Execute("CREATE TABLE foo (id INTEGER PRIMARY KEY, value)"));
+
+  ASSERT_TRUE(db().Execute("INSERT INTO foo (value) VALUES (12)"));
+
+  // Last insert row ID should be valid.
+  int64_t row = db().GetLastInsertRowId();
+  EXPECT_LT(0, row);
+
+  // It should be the primary key of the row we just inserted.
+  Statement s(db(), "SELECT value FROM foo WHERE id=?");
+  s.BindInt64(0, row);
+  ASSERT_TRUE(s.Step());
+  EXPECT_EQ(12, s.ColumnInt(0));
+}
+
+TEST_F(SQLConnectionTest, Rollback) {
+  ASSERT_TRUE(db().BeginTransaction());
+  ASSERT_TRUE(db().BeginTransaction());
+  EXPECT_EQ(2, db().GetTransactionNesting());
+  db().RollbackTransaction();
+  EXPECT_FALSE(db().CommitTransaction());
+  EXPECT_TRUE(db().BeginTransaction());
+}
+
+
+
+
+/********************************************************************
+ ** Tests from
+ ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/statement_unittest.cc
+ ********************************************************************/
+
+namespace Orthanc
+{
+  namespace SQLite
+  {
+    class SQLStatementTest : public SQLConnectionTest
+    {
+    };
+
+    TEST_F(SQLStatementTest, Run) {
+      ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
+      ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)"));
+
+      Statement s(db(), "SELECT b FROM foo WHERE a=?");
+      // Stepping it won't work since we haven't bound the value.
+      EXPECT_FALSE(s.Step());
+
+      // Run should fail since this produces output, and we should use Step(). This
+      // gets a bit wonky since sqlite says this is OK so succeeded is set.
+      s.Reset(true);
+      s.BindInt(0, 3);
+      EXPECT_FALSE(s.Run());
+      EXPECT_EQ(SQLITE_ROW, db().GetErrorCode());
+
+      // Resetting it should put it back to the previous state (not runnable).
+      s.Reset(true);
+
+      // Binding and stepping should produce one row.
+      s.BindInt(0, 3);
+      EXPECT_TRUE(s.Step());
+      EXPECT_EQ(12, s.ColumnInt(0));
+      EXPECT_FALSE(s.Step());
+    }
+
+    TEST_F(SQLStatementTest, BasicErrorCallback) {
+      ASSERT_TRUE(db().Execute("CREATE TABLE foo (a INTEGER PRIMARY KEY, b)"));
+      // Insert in the foo table the primary key. It is an error to insert
+      // something other than an number. This error causes the error callback
+      // handler to be called with SQLITE_MISMATCH as error code.
+      Statement s(db(), "INSERT INTO foo (a) VALUES (?)");
+      s.BindCString(0, "bad bad");
+      EXPECT_THROW(s.Run(), OrthancException);
+    }
+
+    TEST_F(SQLStatementTest, Reset) {
+      ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
+      ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (3, 12)"));
+      ASSERT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (4, 13)"));
+
+      Statement s(db(), "SELECT b FROM foo WHERE a = ? ");
+      s.BindInt(0, 3);
+      ASSERT_TRUE(s.Step());
+      EXPECT_EQ(12, s.ColumnInt(0));
+      ASSERT_FALSE(s.Step());
+
+      s.Reset(false);
+      // Verify that we can get all rows again.
+      ASSERT_TRUE(s.Step());
+      EXPECT_EQ(12, s.ColumnInt(0));
+      EXPECT_FALSE(s.Step());
+
+      s.Reset(true);
+      ASSERT_FALSE(s.Step());
+    }
+  }
+}
+
+
+
+
+
+
+/********************************************************************
+ ** Tests from
+ ** http://src.chromium.org/viewvc/chrome/trunk/src/sql/transaction_unittest.cc
+ ********************************************************************/
+
+class SQLTransactionTest : public SQLConnectionTest
+{
+public:
+  virtual void SetUp()
+  {
+    SQLConnectionTest::SetUp();
+    ASSERT_TRUE(db().Execute("CREATE TABLE foo (a, b)"));
+  }
+
+  // Returns the number of rows in table "foo".
+  int CountFoo() 
+  {
+    Statement count(db(), "SELECT count(*) FROM foo");
+    count.Step();
+    return count.ColumnInt(0);
+  }
+};
+
+
+TEST_F(SQLTransactionTest, Commit) {
+  {
+    Transaction t(db());
+    EXPECT_FALSE(t.IsOpen());
+    t.Begin();
+    EXPECT_TRUE(t.IsOpen());
+
+    EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
+
+    t.Commit();
+    EXPECT_FALSE(t.IsOpen());
+  }
+
+  EXPECT_EQ(1, CountFoo());
+}
+
+TEST_F(SQLTransactionTest, Rollback) {
+  // Test some basic initialization, and that rollback runs when you exit the
+  // scope.
+  {
+    Transaction t(db());
+    EXPECT_FALSE(t.IsOpen());
+    t.Begin();
+    EXPECT_TRUE(t.IsOpen());
+
+    EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
+  }
+
+  // Nothing should have been committed since it was implicitly rolled back.
+  EXPECT_EQ(0, CountFoo());
+
+  // Test explicit rollback.
+  Transaction t2(db());
+  EXPECT_FALSE(t2.IsOpen());
+  t2.Begin();
+
+  EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
+  t2.Rollback();
+  EXPECT_FALSE(t2.IsOpen());
+
+  // Nothing should have been committed since it was explicitly rolled back.
+  EXPECT_EQ(0, CountFoo());
+}
+
+// Rolling back any part of a transaction should roll back all of them.
+TEST_F(SQLTransactionTest, NestedRollback) {
+  EXPECT_EQ(0, db().GetTransactionNesting());
+
+  // Outermost transaction.
+  {
+    Transaction outer(db());
+    outer.Begin();
+    EXPECT_EQ(1, db().GetTransactionNesting());
+
+    // The first inner one gets committed.
+    {
+      Transaction inner1(db());
+      inner1.Begin();
+      EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
+      EXPECT_EQ(2, db().GetTransactionNesting());
+
+      inner1.Commit();
+      EXPECT_EQ(1, db().GetTransactionNesting());
+    }
+
+    // One row should have gotten inserted.
+    EXPECT_EQ(1, CountFoo());
+
+    // The second inner one gets rolled back.
+    {
+      Transaction inner2(db());
+      inner2.Begin();
+      EXPECT_TRUE(db().Execute("INSERT INTO foo (a, b) VALUES (1, 2)"));
+      EXPECT_EQ(2, db().GetTransactionNesting());
+
+      inner2.Rollback();
+      EXPECT_EQ(1, db().GetTransactionNesting());
+    }
+
+    // A third inner one will fail in Begin since one has already been rolled
+    // back.
+    EXPECT_EQ(1, db().GetTransactionNesting());
+    {
+      Transaction inner3(db());
+      EXPECT_THROW(inner3.Begin(), OrthancException);
+      EXPECT_EQ(1, db().GetTransactionNesting());
+    }
+  }
+  EXPECT_EQ(0, db().GetTransactionNesting());
+  EXPECT_EQ(0, CountFoo());
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/SQLiteTests.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,334 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "PrecompiledHeadersUnitTests.h"
+#include "gtest/gtest.h"
+
+#include "../Core/Toolbox.h"
+#include "../Core/SQLite/Connection.h"
+#include "../Core/SQLite/Statement.h"
+#include "../Core/SQLite/Transaction.h"
+
+#include <sqlite3.h>
+
+using namespace Orthanc;
+
+
+TEST(SQLite, Configuration)
+{
+  ASSERT_EQ(1, sqlite3_threadsafe());
+}
+
+
+TEST(SQLite, Connection)
+{
+  Toolbox::RemoveFile("UnitTestsResults/coucou");
+  SQLite::Connection c;
+  c.Open("UnitTestsResults/coucou");
+  c.Execute("CREATE TABLE c(k INTEGER PRIMARY KEY AUTOINCREMENT, v INTEGER)");
+  c.Execute("INSERT INTO c VALUES(NULL, 42);");
+}
+
+
+TEST(SQLite, StatementReferenceBasic)
+{
+  sqlite3* db;
+  sqlite3_open(":memory:", &db);
+
+  {
+    SQLite::StatementReference r(db, "SELECT * FROM sqlite_master");
+    ASSERT_EQ(0u, r.GetReferenceCount());
+
+    {
+      SQLite::StatementReference r1(r);
+      ASSERT_EQ(1u, r.GetReferenceCount());
+      ASSERT_EQ(0u, r1.GetReferenceCount());
+
+      {
+        SQLite::StatementReference r2(r);
+        ASSERT_EQ(2u, r.GetReferenceCount());
+        ASSERT_EQ(0u, r1.GetReferenceCount());
+        ASSERT_EQ(0u, r2.GetReferenceCount());
+
+        SQLite::StatementReference r3(r2);
+        ASSERT_EQ(3u, r.GetReferenceCount());
+        ASSERT_EQ(0u, r1.GetReferenceCount());
+        ASSERT_EQ(0u, r2.GetReferenceCount());
+        ASSERT_EQ(0u, r3.GetReferenceCount());
+      }
+
+      ASSERT_EQ(1u, r.GetReferenceCount());
+      ASSERT_EQ(0u, r1.GetReferenceCount());
+
+      {
+        SQLite::StatementReference r2(r);
+        ASSERT_EQ(2u, r.GetReferenceCount());
+        ASSERT_EQ(0u, r1.GetReferenceCount());
+        ASSERT_EQ(0u, r2.GetReferenceCount());
+      }
+
+      ASSERT_EQ(1u, r.GetReferenceCount());
+      ASSERT_EQ(0u, r1.GetReferenceCount());
+    }
+
+    ASSERT_EQ(0u, r.GetReferenceCount());
+  }
+
+  sqlite3_close(db);
+}
+
+TEST(SQLite, StatementBasic)
+{
+  SQLite::Connection c;
+  c.OpenInMemory();
+  
+  SQLite::Statement s(c, "SELECT * from sqlite_master");
+  s.Run();
+
+  for (unsigned int i = 0; i < 5; i++)
+  {
+    SQLite::Statement cs(c, SQLITE_FROM_HERE, "SELECT * from sqlite_master");
+    cs.Step();
+  }
+}
+
+
+namespace
+{
+  static bool destroyed;
+
+  class MyFunc : public SQLite::IScalarFunction
+  {
+  public:
+    MyFunc()
+    {
+      destroyed = false;
+    }
+
+    virtual ~MyFunc()
+    {
+      destroyed = true;
+    }
+
+    virtual const char* GetName() const
+    {
+      return "MYFUNC";
+    }
+
+    virtual unsigned int GetCardinality() const
+    {
+      return 2;
+    }
+
+    virtual void Compute(SQLite::FunctionContext& context)
+    {
+      context.SetIntResult(1000 + context.GetIntValue(0) * context.GetIntValue(1));
+    }
+  };
+
+  class MyDelete : public SQLite::IScalarFunction
+  {
+  public:
+    std::set<int> deleted_;
+
+    virtual const char* GetName() const
+    {
+      return "MYDELETE";
+    }
+
+    virtual unsigned int GetCardinality() const
+    {
+      return 1;
+    }
+
+    virtual void Compute(SQLite::FunctionContext& context)
+    {
+      deleted_.insert(context.GetIntValue(0));
+      context.SetNullResult();
+    }
+  };
+}
+
+TEST(SQLite, ScalarFunction)
+{
+  {
+    SQLite::Connection c;
+    c.OpenInMemory();
+    c.Register(new MyFunc());
+    c.Execute("CREATE TABLE t(id INTEGER PRIMARY KEY, v1 INTEGER, v2 INTEGER);");
+    c.Execute("INSERT INTO t VALUES(NULL, 2, 3);");
+    c.Execute("INSERT INTO t VALUES(NULL, 4, 4);");
+    c.Execute("INSERT INTO t VALUES(NULL, 6, 5);");
+    SQLite::Statement t(c, "SELECT MYFUNC(v1, v2), v1, v2 FROM t");
+    int i = 0;
+    while (t.Step())
+    {
+      ASSERT_EQ(t.ColumnInt(0), 1000 + t.ColumnInt(1) * t.ColumnInt(2));
+      i++;
+    }
+    ASSERT_EQ(3, i);
+    ASSERT_FALSE(destroyed);
+  }
+  ASSERT_TRUE(destroyed);
+}
+
+TEST(SQLite, CascadedDeleteCallback)
+{
+  SQLite::Connection c;
+  c.OpenInMemory();
+  MyDelete *func = new MyDelete();
+  c.Register(func);
+  c.Execute("CREATE TABLE parent(id INTEGER PRIMARY KEY, dummy INTEGER);");
+  c.Execute("CREATE TABLE child("
+            "  id INTEGER PRIMARY KEY, "
+            "  parent INTEGER REFERENCES parent(id) ON DELETE CASCADE, "
+            "  value INTEGER);");
+  c.Execute("CREATE TRIGGER childRemoved "
+            "AFTER DELETE ON child "
+            "FOR EACH ROW BEGIN "
+            "  SELECT MYDELETE(old.value); "
+            "END;");
+
+  c.Execute("INSERT INTO parent VALUES(42, 100);");
+  c.Execute("INSERT INTO parent VALUES(43, 101);");
+
+  c.Execute("INSERT INTO child VALUES(NULL, 42, 4200);");
+  c.Execute("INSERT INTO child VALUES(NULL, 42, 4201);");
+
+  c.Execute("INSERT INTO child VALUES(NULL, 43, 4300);");
+  c.Execute("INSERT INTO child VALUES(NULL, 43, 4301);");
+
+  // The following command deletes "parent(43, 101)", then in turns
+  // "child(NULL, 43, 4300/4301)", then calls the MyDelete on 4300 and
+  // 4301
+  c.Execute("DELETE FROM parent WHERE dummy=101");
+
+  ASSERT_EQ(2u, func->deleted_.size());
+  ASSERT_TRUE(func->deleted_.find(4300) != func->deleted_.end());
+  ASSERT_TRUE(func->deleted_.find(4301) != func->deleted_.end());
+}
+
+
+TEST(SQLite, EmptyTransactions)
+{
+  try
+  {
+    SQLite::Connection c;
+    c.OpenInMemory();
+
+    c.Execute("CREATE TABLE a(id INTEGER PRIMARY KEY);");
+    c.Execute("INSERT INTO a VALUES(NULL)");
+      
+    {
+      SQLite::Transaction t(c);
+      t.Begin();
+      {
+        SQLite::Statement s(c, SQLITE_FROM_HERE, "SELECT * FROM a");
+        s.Step();
+      }
+      //t.Commit();
+    }
+
+    {
+      SQLite::Statement s(c, SQLITE_FROM_HERE, "SELECT * FROM a");
+      s.Step();
+    }
+  }
+  catch (OrthancException& e)
+  {
+    fprintf(stderr, "Exception: [%s]\n", e.What());
+    throw e;
+  }
+}
+
+
+TEST(SQLite, Types)
+{
+  SQLite::Connection c;
+  c.OpenInMemory();
+  c.Execute("CREATE TABLE a(id INTEGER PRIMARY KEY, value)");
+
+  {
+    SQLite::Statement s(c, std::string("SELECT * FROM a"));
+    ASSERT_EQ(2, s.ColumnCount());
+    ASSERT_FALSE(s.Step());
+  }
+
+  {
+    SQLite::Statement s(c, SQLITE_FROM_HERE, std::string("SELECT * FROM a"));
+    ASSERT_FALSE(s.Step());
+    ASSERT_EQ("SELECT * FROM a", s.GetOriginalSQLStatement());
+  }
+
+  {
+    SQLite::Statement s(c, SQLITE_FROM_HERE, "INSERT INTO a VALUES(NULL, ?);");
+    s.BindNull(0);             ASSERT_TRUE(s.Run()); s.Reset();
+    s.BindBool(0, true);       ASSERT_TRUE(s.Run()); s.Reset();
+    s.BindInt(0, 42);          ASSERT_TRUE(s.Run()); s.Reset();
+    s.BindInt64(0, 42ll);      ASSERT_TRUE(s.Run()); s.Reset();
+    s.BindDouble(0, 42.5);     ASSERT_TRUE(s.Run()); s.Reset();
+    s.BindCString(0, "Hello"); ASSERT_TRUE(s.Run()); s.Reset();
+    s.BindBlob(0, "Hello", 5); ASSERT_TRUE(s.Run()); s.Reset();
+  }
+
+  {
+    SQLite::Statement s(c, SQLITE_FROM_HERE, std::string("SELECT * FROM a"));
+    ASSERT_TRUE(s.Step());
+    ASSERT_EQ(SQLite::COLUMN_TYPE_NULL, s.GetColumnType(1));
+    ASSERT_TRUE(s.ColumnIsNull(1));
+    ASSERT_TRUE(s.Step());
+    ASSERT_EQ(SQLite::COLUMN_TYPE_INTEGER, s.GetColumnType(1));
+    ASSERT_TRUE(s.ColumnBool(1));
+    ASSERT_TRUE(s.Step());
+    ASSERT_EQ(SQLite::COLUMN_TYPE_INTEGER, s.GetColumnType(1));
+    ASSERT_EQ(42, s.ColumnInt(1));
+    ASSERT_TRUE(s.Step());
+    ASSERT_EQ(SQLite::COLUMN_TYPE_INTEGER, s.GetColumnType(1));
+    ASSERT_EQ(42ll, s.ColumnInt64(1));
+    ASSERT_TRUE(s.Step());
+    ASSERT_EQ(SQLite::COLUMN_TYPE_FLOAT, s.GetColumnType(1));
+    ASSERT_DOUBLE_EQ(42.5, s.ColumnDouble(1));
+    ASSERT_TRUE(s.Step());
+    ASSERT_EQ(SQLite::COLUMN_TYPE_TEXT, s.GetColumnType(1));
+    ASSERT_EQ("Hello", s.ColumnString(1));
+    ASSERT_TRUE(s.Step());
+    ASSERT_EQ(SQLite::COLUMN_TYPE_BLOB, s.GetColumnType(1));
+    ASSERT_EQ(5, s.ColumnByteLength(1));
+    ASSERT_TRUE(!memcmp("Hello", s.ColumnBlob(1), 5));
+
+    std::string t;
+    ASSERT_TRUE(s.ColumnBlobAsString(1, &t));
+    ASSERT_EQ("Hello", t);
+
+    ASSERT_FALSE(s.Step());
+  }
+}
--- a/UnitTestsSources/ServerIndexTests.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/UnitTestsSources/ServerIndexTests.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -579,7 +579,13 @@
     instance.SetValue(DICOM_TAG_STUDY_INSTANCE_UID, "study-" + id);
     instance.SetValue(DICOM_TAG_SERIES_INSTANCE_UID, "series-" + id);
     instance.SetValue(DICOM_TAG_SOP_INSTANCE_UID, "instance-" + id);
-    ASSERT_EQ(StoreStatus_Success, index.Store(instance, attachments, ""));
+
+    std::map<MetadataType, std::string> instanceMetadata;
+    ServerIndex::MetadataMap metadata;
+    ASSERT_EQ(StoreStatus_Success, index.Store(instanceMetadata, instance, attachments, "", metadata));
+    ASSERT_EQ(2, instanceMetadata.size());
+    ASSERT_NE(instanceMetadata.end(), instanceMetadata.find(MetadataType_Instance_RemoteAet));
+    ASSERT_NE(instanceMetadata.end(), instanceMetadata.find(MetadataType_Instance_ReceptionDate));
 
     DicomInstanceHasher hasher(instance);
     ids.push_back(hasher.HashPatient());
--- a/UnitTestsSources/UnitTestsMain.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ b/UnitTestsSources/UnitTestsMain.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -174,45 +174,72 @@
 }
 
 
-TEST(ParseGetQuery, Basic)
+TEST(ParseGetArguments, Basic)
 {
   HttpHandler::Arguments a;
-  HttpHandler::ParseGetQuery(a, "aaa=baaa&bb=a&aa=c");
+  HttpHandler::ParseGetArguments(a, "aaa=baaa&bb=a&aa=c");
   ASSERT_EQ(3u, a.size());
   ASSERT_EQ(a["aaa"], "baaa");
   ASSERT_EQ(a["bb"], "a");
   ASSERT_EQ(a["aa"], "c");
 }
 
-TEST(ParseGetQuery, BasicEmpty)
+TEST(ParseGetArguments, BasicEmpty)
 {
   HttpHandler::Arguments a;
-  HttpHandler::ParseGetQuery(a, "aaa&bb=aa&aa");
+  HttpHandler::ParseGetArguments(a, "aaa&bb=aa&aa");
   ASSERT_EQ(3u, a.size());
   ASSERT_EQ(a["aaa"], "");
   ASSERT_EQ(a["bb"], "aa");
   ASSERT_EQ(a["aa"], "");
 }
 
-TEST(ParseGetQuery, Single)
+TEST(ParseGetArguments, Single)
 {
   HttpHandler::Arguments a;
-  HttpHandler::ParseGetQuery(a, "aaa=baaa");
+  HttpHandler::ParseGetArguments(a, "aaa=baaa");
   ASSERT_EQ(1u, a.size());
   ASSERT_EQ(a["aaa"], "baaa");
 }
 
-TEST(ParseGetQuery, SingleEmpty)
+TEST(ParseGetArguments, SingleEmpty)
 {
   HttpHandler::Arguments a;
-  HttpHandler::ParseGetQuery(a, "aaa");
+  HttpHandler::ParseGetArguments(a, "aaa");
   ASSERT_EQ(1u, a.size());
   ASSERT_EQ(a["aaa"], "");
 }
 
+TEST(ParseGetQuery, Test1)
+{
+  UriComponents uri;
+  HttpHandler::Arguments a;
+  HttpHandler::ParseGetQuery(uri, a, "/instances/test/world?aaa=baaa&bb=a&aa=c");
+  ASSERT_EQ(3u, uri.size());
+  ASSERT_EQ("instances", uri[0]);
+  ASSERT_EQ("test", uri[1]);
+  ASSERT_EQ("world", uri[2]);
+  ASSERT_EQ(3u, a.size());
+  ASSERT_EQ(a["aaa"], "baaa");
+  ASSERT_EQ(a["bb"], "a");
+  ASSERT_EQ(a["aa"], "c");
+}
+
+TEST(ParseGetQuery, Test2)
+{
+  UriComponents uri;
+  HttpHandler::Arguments a;
+  HttpHandler::ParseGetQuery(uri, a, "/instances/test/world");
+  ASSERT_EQ(3u, uri.size());
+  ASSERT_EQ("instances", uri[0]);
+  ASSERT_EQ("test", uri[1]);
+  ASSERT_EQ("world", uri[2]);
+  ASSERT_EQ(0u, a.size());
+}
+
 TEST(Uri, SplitUriComponents)
 {
-  UriComponents c;
+  UriComponents c, d;
   Toolbox::SplitUriComponents(c, "/cou/hello/world");
   ASSERT_EQ(3u, c.size());
   ASSERT_EQ("cou", c[0]);
@@ -253,6 +280,37 @@
 }
 
 
+TEST(Uri, Truncate)
+{
+  UriComponents c, d;
+  Toolbox::SplitUriComponents(c, "/cou/hello/world");
+
+  Toolbox::TruncateUri(d, c, 0);
+  ASSERT_EQ(3u, d.size());
+  ASSERT_EQ("cou", d[0]);
+  ASSERT_EQ("hello", d[1]);
+  ASSERT_EQ("world", d[2]);
+
+  Toolbox::TruncateUri(d, c, 1);
+  ASSERT_EQ(2u, d.size());
+  ASSERT_EQ("hello", d[0]);
+  ASSERT_EQ("world", d[1]);
+
+  Toolbox::TruncateUri(d, c, 2);
+  ASSERT_EQ(1u, d.size());
+  ASSERT_EQ("world", d[0]);
+
+  Toolbox::TruncateUri(d, c, 3);
+  ASSERT_EQ(0u, d.size());
+
+  Toolbox::TruncateUri(d, c, 4);
+  ASSERT_EQ(0u, d.size());
+
+  Toolbox::TruncateUri(d, c, 5);
+  ASSERT_EQ(0u, d.size());
+}
+
+
 TEST(Uri, Child)
 {
   UriComponents c1;  Toolbox::SplitUriComponents(c1, "/hello/world");  
@@ -414,7 +472,7 @@
   ASSERT_EQ("&abc", Toolbox::ConvertToAscii(s));
 
   // Open in Emacs, then save with UTF-8 encoding, then "hexdump -C"
-  std::string utf8 = Toolbox::ConvertToUtf8(s, "ISO-8859-1");
+  std::string utf8 = Toolbox::ConvertToUtf8(s, Encoding_Latin1);
   ASSERT_EQ(15u, utf8.size());
   ASSERT_EQ(0xc3, static_cast<unsigned char>(utf8[0]));
   ASSERT_EQ(0xa0, static_cast<unsigned char>(utf8[1]));
--- a/UnitTestsSources/Versions.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,133 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
- **/
-
-
-#include "PrecompiledHeadersUnitTests.h"
-#include "gtest/gtest.h"
-
-#include <stdint.h>
-#include <math.h>
-#include <png.h>
-#include <ctype.h>
-#include <zlib.h>
-#include <curl/curl.h>
-#include <boost/version.hpp>
-#include <sqlite3.h>
-#include <lua.h>
-#include <openssl/opensslv.h>
-
-
-TEST(Versions, Zlib)
-{
-  ASSERT_STREQ(zlibVersion(), ZLIB_VERSION);
-}
-
-TEST(Versions, Curl)
-{
-  curl_version_info_data* v = curl_version_info(CURLVERSION_NOW);
-  ASSERT_STREQ(LIBCURL_VERSION, v->version);
-}
-
-TEST(Versions, Png)
-{
-  ASSERT_EQ(PNG_LIBPNG_VER_MAJOR * 10000 + PNG_LIBPNG_VER_MINOR * 100 + PNG_LIBPNG_VER_RELEASE,
-            png_access_version_number());
-}
-
-TEST(Versions, SQLite)
-{
-  // http://www.sqlite.org/capi3ref.html#sqlite3_libversion
-  assert(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER );
-  assert(strcmp(sqlite3_sourceid(), SQLITE_SOURCE_ID) == 0);
-  assert(strcmp(sqlite3_libversion(), SQLITE_VERSION) == 0);
-
-  // Ensure that the SQLite version is above 3.7.0.
-  // "sqlite3_create_function_v2" is not defined in previous versions.
-  ASSERT_GE(SQLITE_VERSION_NUMBER, 3007000);
-}
-
-
-TEST(Versions, Lua)
-{
-  // Ensure that the Lua version is above 5.1.0. This version has
-  // introduced some API changes.
-  ASSERT_GE(LUA_VERSION_NUM, 501);
-}
-
-
-#if ORTHANC_STATIC == 1
-TEST(Versions, ZlibStatic)
-{
-  ASSERT_STREQ("1.2.7", zlibVersion());
-}
-
-TEST(Versions, BoostStatic)
-{
-  ASSERT_STREQ("1_55", BOOST_LIB_VERSION);
-}
-
-TEST(Versions, CurlStatic)
-{
-  curl_version_info_data* v = curl_version_info(CURLVERSION_NOW);
-  ASSERT_STREQ("7.26.0", v->version);
-}
-
-TEST(Versions, PngStatic)
-{
-  ASSERT_EQ(10512, png_access_version_number());
-  ASSERT_STREQ("1.5.12", PNG_LIBPNG_VER_STRING);
-}
-
-TEST(Versions, CurlSslStatic)
-{
-  curl_version_info_data * vinfo = curl_version_info(CURLVERSION_NOW);
-
-  // Check that SSL support is enabled when required
-  bool curlSupportsSsl = vinfo->features & CURL_VERSION_SSL;
-
-#if ORTHANC_SSL_ENABLED == 0
-  ASSERT_FALSE(curlSupportsSsl);
-#else
-  ASSERT_TRUE(curlSupportsSsl);
-#endif
-}
-
-TEST(Version, LuaStatic)
-{
-  ASSERT_STREQ("Lua 5.1.5", LUA_RELEASE);
-}
-
-TEST(Version, OpenSslStatic)
-{
-  ASSERT_EQ(0x1000107fL /* openssl-1.0.1g */, OPENSSL_VERSION_NUMBER);
-}
-
-#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/VersionsTests.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,133 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "PrecompiledHeadersUnitTests.h"
+#include "gtest/gtest.h"
+
+#include <stdint.h>
+#include <math.h>
+#include <png.h>
+#include <ctype.h>
+#include <zlib.h>
+#include <curl/curl.h>
+#include <boost/version.hpp>
+#include <sqlite3.h>
+#include <lua.h>
+#include <openssl/opensslv.h>
+
+
+TEST(Versions, Zlib)
+{
+  ASSERT_STREQ(zlibVersion(), ZLIB_VERSION);
+}
+
+TEST(Versions, Curl)
+{
+  curl_version_info_data* v = curl_version_info(CURLVERSION_NOW);
+  ASSERT_STREQ(LIBCURL_VERSION, v->version);
+}
+
+TEST(Versions, Png)
+{
+  ASSERT_EQ(PNG_LIBPNG_VER_MAJOR * 10000 + PNG_LIBPNG_VER_MINOR * 100 + PNG_LIBPNG_VER_RELEASE,
+            png_access_version_number());
+}
+
+TEST(Versions, SQLite)
+{
+  // http://www.sqlite.org/capi3ref.html#sqlite3_libversion
+  assert(sqlite3_libversion_number() == SQLITE_VERSION_NUMBER );
+  assert(strcmp(sqlite3_sourceid(), SQLITE_SOURCE_ID) == 0);
+  assert(strcmp(sqlite3_libversion(), SQLITE_VERSION) == 0);
+
+  // Ensure that the SQLite version is above 3.7.0.
+  // "sqlite3_create_function_v2" is not defined in previous versions.
+  ASSERT_GE(SQLITE_VERSION_NUMBER, 3007000);
+}
+
+
+TEST(Versions, Lua)
+{
+  // Ensure that the Lua version is above 5.1.0. This version has
+  // introduced some API changes.
+  ASSERT_GE(LUA_VERSION_NUM, 501);
+}
+
+
+#if ORTHANC_STATIC == 1
+TEST(Versions, ZlibStatic)
+{
+  ASSERT_STREQ("1.2.7", zlibVersion());
+}
+
+TEST(Versions, BoostStatic)
+{
+  ASSERT_STREQ("1_55", BOOST_LIB_VERSION);
+}
+
+TEST(Versions, CurlStatic)
+{
+  curl_version_info_data* v = curl_version_info(CURLVERSION_NOW);
+  ASSERT_STREQ("7.26.0", v->version);
+}
+
+TEST(Versions, PngStatic)
+{
+  ASSERT_EQ(10512, png_access_version_number());
+  ASSERT_STREQ("1.5.12", PNG_LIBPNG_VER_STRING);
+}
+
+TEST(Versions, CurlSslStatic)
+{
+  curl_version_info_data * vinfo = curl_version_info(CURLVERSION_NOW);
+
+  // Check that SSL support is enabled when required
+  bool curlSupportsSsl = vinfo->features & CURL_VERSION_SSL;
+
+#if ORTHANC_SSL_ENABLED == 0
+  ASSERT_FALSE(curlSupportsSsl);
+#else
+  ASSERT_TRUE(curlSupportsSsl);
+#endif
+}
+
+TEST(Version, LuaStatic)
+{
+  ASSERT_STREQ("Lua 5.1.5", LUA_RELEASE);
+}
+
+TEST(Version, OpenSslStatic)
+{
+  ASSERT_EQ(0x1000107fL /* openssl-1.0.1g */, OPENSSL_VERSION_NUMBER);
+}
+
+#endif
--- a/UnitTestsSources/Zip.cpp	Wed Jun 25 11:56:48 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,166 +0,0 @@
-/**
- * Orthanc - A Lightweight, RESTful DICOM Store
- * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
- **/
-
-
-#include "PrecompiledHeadersUnitTests.h"
-#include "gtest/gtest.h"
-
-#include "../Core/OrthancException.h"
-#include "../Core/Compression/ZipWriter.h"
-#include "../Core/Compression/HierarchicalZipWriter.h"
-#include "../Core/Toolbox.h"
-
-
-using namespace Orthanc;
-
-TEST(ZipWriter, Basic)
-{
-  Orthanc::ZipWriter w;
-  w.SetOutputPath("UnitTestsResults/hello.zip");
-  w.Open();
-  w.OpenFile("world/hello");
-  w.Write("Hello world");
-}
-
-
-TEST(ZipWriter, Basic64)
-{
-  Orthanc::ZipWriter w;
-  w.SetOutputPath("UnitTestsResults/hello64.zip");
-  w.SetZip64(true);
-  w.Open();
-  w.OpenFile("world/hello");
-  w.Write("Hello world");
-}
-
-
-TEST(ZipWriter, Exceptions)
-{
-  Orthanc::ZipWriter w;
-  ASSERT_THROW(w.Open(), Orthanc::OrthancException);
-  w.SetOutputPath("UnitTestsResults/hello3.zip");
-  w.Open();
-  ASSERT_THROW(w.Write("hello world"), Orthanc::OrthancException);
-}
-
-
-
-
-
-namespace Orthanc
-{
-  // The namespace is necessary
-  // http://code.google.com/p/googletest/wiki/AdvancedGuide#Private_Class_Members
-
-  TEST(HierarchicalZipWriter, Index)
-  {
-    HierarchicalZipWriter::Index i;
-    ASSERT_EQ("hello", i.OpenFile("hello"));
-    ASSERT_EQ("hello-2", i.OpenFile("hello"));
-    ASSERT_EQ("coucou", i.OpenFile("coucou"));
-    ASSERT_EQ("hello-3", i.OpenFile("hello"));
-
-    i.OpenDirectory("coucou");
-
-    ASSERT_EQ("coucou-2/world", i.OpenFile("world"));
-    ASSERT_EQ("coucou-2/world-2", i.OpenFile("world"));
-
-    i.OpenDirectory("world");
-  
-    ASSERT_EQ("coucou-2/world-3/hello", i.OpenFile("hello"));
-    ASSERT_EQ("coucou-2/world-3/hello-2", i.OpenFile("hello"));
-
-    i.CloseDirectory();
-
-    ASSERT_EQ("coucou-2/world-4", i.OpenFile("world"));
-
-    i.CloseDirectory();
-
-    ASSERT_EQ("coucou-3", i.OpenFile("coucou"));
-
-    ASSERT_THROW(i.CloseDirectory(), OrthancException);
-  }
-
-
-  TEST(HierarchicalZipWriter, Filenames)
-  {
-    ASSERT_EQ("trE hell", HierarchicalZipWriter::Index::KeepAlphanumeric("    ÊtrE hellô  "));
-
-    // The "^" character is considered as a space in DICOM
-    ASSERT_EQ("Hel lo world", HierarchicalZipWriter::Index::KeepAlphanumeric("    Hel^^  ^\r\n\t^^lo  \t  <world>  "));
-  }
-}
-
-
-TEST(HierarchicalZipWriter, Basic)
-{
-  static const std::string SPACES = "                             ";
-
-  HierarchicalZipWriter w("UnitTestsResults/hello2.zip");
-
-  w.SetCompressionLevel(0);
-
-  // Inside "/"
-  w.OpenFile("hello");
-  w.Write(SPACES + "hello\n");
-  w.OpenFile("hello");
-  w.Write(SPACES + "hello-2\n");
-  w.OpenDirectory("hello");
-
-  // Inside "/hello-3"
-  w.OpenFile("hello");
-  w.Write(SPACES + "hello\n");
-  w.OpenDirectory("hello");
-
-  w.SetCompressionLevel(9);
-
-  // Inside "/hello-3/hello-2"
-  w.OpenFile("hello");
-  w.Write(SPACES + "hello\n");
-  w.OpenFile("hello");
-  w.Write(SPACES + "hello-2\n");
-  w.CloseDirectory();
-
-  // Inside "/hello-3"
-  w.OpenFile("hello");
-  w.Write(SPACES + "hello-3\n");
-
-  /**
-
-     TO CHECK THE CONTENT OF THE "hello2.zip" FILE:
-
-     # unzip -v hello2.zip 
-
-     => There must be 6 files. The first 3 files must have a negative
-     compression ratio.
-
-  **/
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTestsSources/ZipTests.cpp	Thu Jul 10 11:42:32 2014 +0200
@@ -0,0 +1,166 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2014 Medical Physics Department, CHU 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/>.
+ **/
+
+
+#include "PrecompiledHeadersUnitTests.h"
+#include "gtest/gtest.h"
+
+#include "../Core/OrthancException.h"
+#include "../Core/Compression/ZipWriter.h"
+#include "../Core/Compression/HierarchicalZipWriter.h"
+#include "../Core/Toolbox.h"
+
+
+using namespace Orthanc;
+
+TEST(ZipWriter, Basic)
+{
+  Orthanc::ZipWriter w;
+  w.SetOutputPath("UnitTestsResults/hello.zip");
+  w.Open();
+  w.OpenFile("world/hello");
+  w.Write("Hello world");
+}
+
+
+TEST(ZipWriter, Basic64)
+{
+  Orthanc::ZipWriter w;
+  w.SetOutputPath("UnitTestsResults/hello64.zip");
+  w.SetZip64(true);
+  w.Open();
+  w.OpenFile("world/hello");
+  w.Write("Hello world");
+}
+
+
+TEST(ZipWriter, Exceptions)
+{
+  Orthanc::ZipWriter w;
+  ASSERT_THROW(w.Open(), Orthanc::OrthancException);
+  w.SetOutputPath("UnitTestsResults/hello3.zip");
+  w.Open();
+  ASSERT_THROW(w.Write("hello world"), Orthanc::OrthancException);
+}
+
+
+
+
+
+namespace Orthanc
+{
+  // The namespace is necessary
+  // http://code.google.com/p/googletest/wiki/AdvancedGuide#Private_Class_Members
+
+  TEST(HierarchicalZipWriter, Index)
+  {
+    HierarchicalZipWriter::Index i;
+    ASSERT_EQ("hello", i.OpenFile("hello"));
+    ASSERT_EQ("hello-2", i.OpenFile("hello"));
+    ASSERT_EQ("coucou", i.OpenFile("coucou"));
+    ASSERT_EQ("hello-3", i.OpenFile("hello"));
+
+    i.OpenDirectory("coucou");
+
+    ASSERT_EQ("coucou-2/world", i.OpenFile("world"));
+    ASSERT_EQ("coucou-2/world-2", i.OpenFile("world"));
+
+    i.OpenDirectory("world");
+  
+    ASSERT_EQ("coucou-2/world-3/hello", i.OpenFile("hello"));
+    ASSERT_EQ("coucou-2/world-3/hello-2", i.OpenFile("hello"));
+
+    i.CloseDirectory();
+
+    ASSERT_EQ("coucou-2/world-4", i.OpenFile("world"));
+
+    i.CloseDirectory();
+
+    ASSERT_EQ("coucou-3", i.OpenFile("coucou"));
+
+    ASSERT_THROW(i.CloseDirectory(), OrthancException);
+  }
+
+
+  TEST(HierarchicalZipWriter, Filenames)
+  {
+    ASSERT_EQ("trE hell", HierarchicalZipWriter::Index::KeepAlphanumeric("    ÊtrE hellô  "));
+
+    // The "^" character is considered as a space in DICOM
+    ASSERT_EQ("Hel lo world", HierarchicalZipWriter::Index::KeepAlphanumeric("    Hel^^  ^\r\n\t^^lo  \t  <world>  "));
+  }
+}
+
+
+TEST(HierarchicalZipWriter, Basic)
+{
+  static const std::string SPACES = "                             ";
+
+  HierarchicalZipWriter w("UnitTestsResults/hello2.zip");
+
+  w.SetCompressionLevel(0);
+
+  // Inside "/"
+  w.OpenFile("hello");
+  w.Write(SPACES + "hello\n");
+  w.OpenFile("hello");
+  w.Write(SPACES + "hello-2\n");
+  w.OpenDirectory("hello");
+
+  // Inside "/hello-3"
+  w.OpenFile("hello");
+  w.Write(SPACES + "hello\n");
+  w.OpenDirectory("hello");
+
+  w.SetCompressionLevel(9);
+
+  // Inside "/hello-3/hello-2"
+  w.OpenFile("hello");
+  w.Write(SPACES + "hello\n");
+  w.OpenFile("hello");
+  w.Write(SPACES + "hello-2\n");
+  w.CloseDirectory();
+
+  // Inside "/hello-3"
+  w.OpenFile("hello");
+  w.Write(SPACES + "hello-3\n");
+
+  /**
+
+     TO CHECK THE CONTENT OF THE "hello2.zip" FILE:
+
+     # unzip -v hello2.zip 
+
+     => There must be 6 files. The first 3 files must have a negative
+     compression ratio.
+
+  **/
+}