changeset 23:7a0af291cc90

Synchronization with Orthanc mainline
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 01 Jun 2015 11:52:28 +0200
parents b42eeb4bd1e3
children ed9acb0f938e
files CMakeLists.txt Orthanc/Enumerations.cpp Orthanc/Enumerations.h Orthanc/FileStorage/FilesystemStorage.cpp Orthanc/FileStorage/FilesystemStorage.h Orthanc/FileStorage/IStorageArea.h Orthanc/ImageFormats/ImageAccessor.cpp Orthanc/ImageFormats/ImageAccessor.h Orthanc/ImageFormats/ImageProcessing.cpp Orthanc/MultiThreading/SharedMessageQueue.cpp Orthanc/PrecompiledHeaders.h Orthanc/README.txt Orthanc/SQLite/FunctionContext.cpp Orthanc/SQLite/StatementReference.cpp Orthanc/Toolbox.cpp Orthanc/Toolbox.h Plugin/Cache/CacheManager.cpp Resources/CMake/BoostConfiguration.cmake Resources/SyncOrthancFolder.py Resources/ThirdParty/md5/md5.c Resources/ThirdParty/md5/md5.h
diffstat 21 files changed, 2603 insertions(+), 86 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Fri May 29 11:07:38 2015 +0200
+++ b/CMakeLists.txt	Mon Jun 01 11:52:28 2015 +0200
@@ -132,6 +132,7 @@
   ${CMAKE_SOURCE_DIR}/Orthanc/Toolbox.cpp
   ${CMAKE_SOURCE_DIR}/Orthanc/Uuid.cpp
   ${CMAKE_SOURCE_DIR}/Resources/ThirdParty/base64/base64.cpp
+  ${CMAKE_SOURCE_DIR}/Resources/ThirdParty/md5/md5.c
 
   ${CMAKE_SOURCE_DIR}/Plugin/Cache/CacheManager.cpp
   ${CMAKE_SOURCE_DIR}/Plugin/Cache/CacheScheduler.cpp
--- a/Orthanc/Enumerations.cpp	Fri May 29 11:07:38 2015 +0200
+++ b/Orthanc/Enumerations.cpp	Mon Jun 01 11:52:28 2015 +0200
@@ -34,9 +34,465 @@
 #include "Enumerations.h"
 
 #include "OrthancException.h"
+#include "Toolbox.h"
 
 namespace Orthanc
 {
+  const char* EnumerationToString(HttpMethod method)
+  {
+    switch (method)
+    {
+      case HttpMethod_Get:
+        return "GET";
+
+      case HttpMethod_Post:
+        return "POST";
+
+      case HttpMethod_Delete:
+        return "DELETE";
+
+      case HttpMethod_Put:
+        return "PUT";
+
+      default:
+        return "?";
+    }
+  }
+
+
+  const char* EnumerationToString(HttpStatus status)
+  {
+    switch (status)
+    {
+    case HttpStatus_100_Continue:
+      return "Continue";
+
+    case HttpStatus_101_SwitchingProtocols:
+      return "Switching Protocols";
+
+    case HttpStatus_102_Processing:
+      return "Processing";
+
+    case HttpStatus_200_Ok:
+      return "OK";
+
+    case HttpStatus_201_Created:
+      return "Created";
+
+    case HttpStatus_202_Accepted:
+      return "Accepted";
+
+    case HttpStatus_203_NonAuthoritativeInformation:
+      return "Non-Authoritative Information";
+
+    case HttpStatus_204_NoContent:
+      return "No Content";
+
+    case HttpStatus_205_ResetContent:
+      return "Reset Content";
+
+    case HttpStatus_206_PartialContent:
+      return "Partial Content";
+
+    case HttpStatus_207_MultiStatus:
+      return "Multi-Status";
+
+    case HttpStatus_208_AlreadyReported:
+      return "Already Reported";
+
+    case HttpStatus_226_IMUsed:
+      return "IM Used";
+
+    case HttpStatus_300_MultipleChoices:
+      return "Multiple Choices";
+
+    case HttpStatus_301_MovedPermanently:
+      return "Moved Permanently";
+
+    case HttpStatus_302_Found:
+      return "Found";
+
+    case HttpStatus_303_SeeOther:
+      return "See Other";
+
+    case HttpStatus_304_NotModified:
+      return "Not Modified";
+
+    case HttpStatus_305_UseProxy:
+      return "Use Proxy";
+
+    case HttpStatus_307_TemporaryRedirect:
+      return "Temporary Redirect";
+
+    case HttpStatus_400_BadRequest:
+      return "Bad Request";
+
+    case HttpStatus_401_Unauthorized:
+      return "Unauthorized";
+
+    case HttpStatus_402_PaymentRequired:
+      return "Payment Required";
+
+    case HttpStatus_403_Forbidden:
+      return "Forbidden";
+
+    case HttpStatus_404_NotFound:
+      return "Not Found";
+
+    case HttpStatus_405_MethodNotAllowed:
+      return "Method Not Allowed";
+
+    case HttpStatus_406_NotAcceptable:
+      return "Not Acceptable";
+
+    case HttpStatus_407_ProxyAuthenticationRequired:
+      return "Proxy Authentication Required";
+
+    case HttpStatus_408_RequestTimeout:
+      return "Request Timeout";
+
+    case HttpStatus_409_Conflict:
+      return "Conflict";
+
+    case HttpStatus_410_Gone:
+      return "Gone";
+
+    case HttpStatus_411_LengthRequired:
+      return "Length Required";
+
+    case HttpStatus_412_PreconditionFailed:
+      return "Precondition Failed";
+
+    case HttpStatus_413_RequestEntityTooLarge:
+      return "Request Entity Too Large";
+
+    case HttpStatus_414_RequestUriTooLong:
+      return "Request-URI Too Long";
+
+    case HttpStatus_415_UnsupportedMediaType:
+      return "Unsupported Media Type";
+
+    case HttpStatus_416_RequestedRangeNotSatisfiable:
+      return "Requested Range Not Satisfiable";
+
+    case HttpStatus_417_ExpectationFailed:
+      return "Expectation Failed";
+
+    case HttpStatus_422_UnprocessableEntity:
+      return "Unprocessable Entity";
+
+    case HttpStatus_423_Locked:
+      return "Locked";
+
+    case HttpStatus_424_FailedDependency:
+      return "Failed Dependency";
+
+    case HttpStatus_426_UpgradeRequired:
+      return "Upgrade Required";
+
+    case HttpStatus_500_InternalServerError:
+      return "Internal Server Error";
+
+    case HttpStatus_501_NotImplemented:
+      return "Not Implemented";
+
+    case HttpStatus_502_BadGateway:
+      return "Bad Gateway";
+
+    case HttpStatus_503_ServiceUnavailable:
+      return "Service Unavailable";
+
+    case HttpStatus_504_GatewayTimeout:
+      return "Gateway Timeout";
+
+    case HttpStatus_505_HttpVersionNotSupported:
+      return "HTTP Version Not Supported";
+
+    case HttpStatus_506_VariantAlsoNegotiates:
+      return "Variant Also Negotiates";
+
+    case HttpStatus_507_InsufficientStorage:
+      return "Insufficient Storage";
+
+    case HttpStatus_509_BandwidthLimitExceeded:
+      return "Bandwidth Limit Exceeded";
+
+    case HttpStatus_510_NotExtended:
+      return "Not Extended";
+
+    default:
+      throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(ResourceType type)
+  {
+    switch (type)
+    {
+      case ResourceType_Patient:
+        return "Patient";
+
+      case ResourceType_Study:
+        return "Study";
+
+      case ResourceType_Series:
+        return "Series";
+
+      case ResourceType_Instance:
+        return "Instance";
+      
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(ImageFormat format)
+  {
+    switch (format)
+    {
+      case ImageFormat_Png:
+        return "Png";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(Encoding encoding)
+  {
+    switch (encoding)
+    {
+      case Encoding_Ascii:
+        return "Ascii";
+
+      case Encoding_Utf8:
+        return "Utf8";
+
+      case Encoding_Latin1:
+        return "Latin1";
+
+      case Encoding_Latin2:
+        return "Latin2";
+
+      case Encoding_Latin3:
+        return "Latin3";
+
+      case Encoding_Latin4:
+        return "Latin4";
+
+      case Encoding_Latin5:
+        return "Latin5";
+
+      case Encoding_Cyrillic:
+        return "Cyrillic";
+
+      case Encoding_Windows1251:
+        return "Windows1251";
+
+      case Encoding_Arabic:
+        return "Arabic";
+
+      case Encoding_Greek:
+        return "Greek";
+
+      case Encoding_Hebrew:
+        return "Hebrew";
+
+      case Encoding_Thai:
+        return "Thai";
+
+      case Encoding_Japanese:
+        return "Japanese";
+
+      case Encoding_Chinese:
+        return "Chinese";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  const char* EnumerationToString(PhotometricInterpretation photometric)
+  {
+    switch (photometric)
+    {
+      case PhotometricInterpretation_RGB:
+        return "RGB";
+
+      case PhotometricInterpretation_Monochrome1:
+        return "Monochrome1";
+
+      case PhotometricInterpretation_Monochrome2:
+        return "Monochrome2";
+
+      case PhotometricInterpretation_ARGB:
+        return "ARGB";
+
+      case PhotometricInterpretation_CMYK:
+        return "CMYK";
+
+      case PhotometricInterpretation_HSV:
+        return "HSV";
+
+      case PhotometricInterpretation_Palette:
+        return "Palette color";
+
+      case PhotometricInterpretation_YBRFull:
+        return "YBR full";
+
+      case PhotometricInterpretation_YBRFull422:
+        return "YBR full 422";
+
+      case PhotometricInterpretation_YBRPartial420:
+        return "YBR partial 420"; 
+
+      case PhotometricInterpretation_YBRPartial422:
+        return "YBR partial 422"; 
+
+      case PhotometricInterpretation_YBR_ICT:
+        return "YBR ICT"; 
+
+      case PhotometricInterpretation_YBR_RCT:
+        return "YBR RCT"; 
+
+      case PhotometricInterpretation_Unknown:
+        return "Unknown";
+
+      default:
+        throw OrthancException(ErrorCode_ParameterOutOfRange);
+    }
+  }
+
+
+  Encoding StringToEncoding(const char* encoding)
+  {
+    std::string s(encoding);
+    Toolbox::ToUpperCase(s);
+
+    if (s == "UTF8")
+    {
+      return Encoding_Utf8;
+    }
+
+    if (s == "ASCII")
+    {
+      return Encoding_Ascii;
+    }
+
+    if (s == "LATIN1")
+    {
+      return Encoding_Latin1;
+    }
+
+    if (s == "LATIN2")
+    {
+      return Encoding_Latin2;
+    }
+
+    if (s == "LATIN3")
+    {
+      return Encoding_Latin3;
+    }
+
+    if (s == "LATIN4")
+    {
+      return Encoding_Latin4;
+    }
+
+    if (s == "LATIN5")
+    {
+      return Encoding_Latin5;
+    }
+
+    if (s == "CYRILLIC")
+    {
+      return Encoding_Cyrillic;
+    }
+
+    if (s == "WINDOWS1251")
+    {
+      return Encoding_Windows1251;
+    }
+
+    if (s == "ARABIC")
+    {
+      return Encoding_Arabic;
+    }
+
+    if (s == "GREEK")
+    {
+      return Encoding_Greek;
+    }
+
+    if (s == "HEBREW")
+    {
+      return Encoding_Hebrew;
+    }
+
+    if (s == "THAI")
+    {
+      return Encoding_Thai;
+    }
+
+    if (s == "JAPANESE")
+    {
+      return Encoding_Japanese;
+    }
+
+    if (s == "CHINESE")
+    {
+      return Encoding_Chinese;
+    }
+
+    throw OrthancException(ErrorCode_ParameterOutOfRange);
+  }
+
+
+  ResourceType StringToResourceType(const char* type)
+  {
+    std::string s(type);
+    Toolbox::ToUpperCase(s);
+
+    if (s == "PATIENT" || s == "PATIENTS")
+    {
+      return ResourceType_Patient;
+    }
+    else if (s == "STUDY" || s == "STUDIES")
+    {
+      return ResourceType_Study;
+    }
+    else if (s == "SERIES")
+    {
+      return ResourceType_Series;
+    }
+    else if (s == "INSTANCE"  || s == "IMAGE" || 
+             s == "INSTANCES" || s == "IMAGES")
+    {
+      return ResourceType_Instance;
+    }
+
+    throw OrthancException(ErrorCode_ParameterOutOfRange);
+  }
+
+
+  ImageFormat StringToImageFormat(const char* format)
+  {
+    std::string s(format);
+    Toolbox::ToUpperCase(s);
+
+    if (s == "PNG")
+    {
+      return ImageFormat_Png;
+    }
+
+    throw OrthancException(ErrorCode_ParameterOutOfRange);
+  }
+
+
   unsigned int GetBytesPerPixel(PixelFormat format)
   {
     switch (format)
@@ -58,4 +514,115 @@
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
   }
+
+
+  bool GetDicomEncoding(Encoding& encoding,
+                        const char* specificCharacterSet)
+  {
+    std::string s = specificCharacterSet;
+    Toolbox::ToUpperCase(s);
+
+    // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/
+    // https://github.com/dcm4che/dcm4che/blob/master/dcm4che-core/src/main/java/org/dcm4che3/data/SpecificCharacterSet.java
+    if (s == "ISO_IR 6" ||
+        s == "ISO_IR 192" ||
+        s == "ISO 2022 IR 6")
+    {
+      encoding = Encoding_Utf8;
+    }
+    else if (s == "ISO_IR 100" ||
+             s == "ISO 2022 IR 100")
+    {
+      encoding = Encoding_Latin1;
+    }
+    else if (s == "ISO_IR 101" ||
+             s == "ISO 2022 IR 101")
+    {
+      encoding = Encoding_Latin2;
+    }
+    else if (s == "ISO_IR 109" ||
+             s == "ISO 2022 IR 109")
+    {
+      encoding = Encoding_Latin3;
+    }
+    else if (s == "ISO_IR 110" ||
+             s == "ISO 2022 IR 110")
+    {
+      encoding = Encoding_Latin4;
+    }
+    else if (s == "ISO_IR 148" ||
+             s == "ISO 2022 IR 148")
+    {
+      encoding = Encoding_Latin5;
+    }
+    else if (s == "ISO_IR 144" ||
+             s == "ISO 2022 IR 144")
+    {
+      encoding = Encoding_Cyrillic;
+    }
+    else if (s == "ISO_IR 127" ||
+             s == "ISO 2022 IR 127")
+    {
+      encoding = Encoding_Arabic;
+    }
+    else if (s == "ISO_IR 126" ||
+             s == "ISO 2022 IR 126")
+    {
+      encoding = Encoding_Greek;
+    }
+    else if (s == "ISO_IR 138" ||
+             s == "ISO 2022 IR 138")
+    {
+      encoding = Encoding_Hebrew;
+    }
+    else if (s == "ISO_IR 166" || s == "ISO 2022 IR 166")
+    {
+      encoding = Encoding_Thai;
+    }
+    else if (s == "ISO_IR 13" || s == "ISO 2022 IR 13")
+    {
+      encoding = Encoding_Japanese;
+    }
+    else if (s == "GB18030")
+    {
+      encoding = Encoding_Chinese;
+    }
+    /*
+      else if (s == "ISO 2022 IR 149")
+      {
+      TODO
+      }
+      else if (s == "ISO 2022 IR 159")
+      {
+      TODO
+      }
+      else if (s == "ISO 2022 IR 87")
+      {
+      TODO
+      }
+    */
+    else
+    {
+      return false;
+    }
+
+    // The encoding was properly detected
+    return true;
+  }
+
+
+  const char* GetMimeType(FileContentType type)
+  {
+    switch (type)
+    {
+      case FileContentType_Dicom:
+        return "application/dicom";
+
+      case FileContentType_DicomAsJson:
+        return "application/json";
+
+      default:
+        return "application/octet-stream";
+    }
+  }
 }
--- a/Orthanc/Enumerations.h	Fri May 29 11:07:38 2015 +0200
+++ b/Orthanc/Enumerations.h	Mon Jun 01 11:52:28 2015 +0200
@@ -76,6 +76,7 @@
     ErrorCode_Plugin
   };
 
+
   /**
    * {summary}{The memory layout of the pixels (resp. voxels) of a 2D (resp. 3D) image.}
    **/
@@ -115,5 +116,230 @@
   };
 
 
+  /**
+   * {summary}{The extraction mode specifies the way the values of the pixels are scaled when downloading a 2D image.}
+   **/
+  enum ImageExtractionMode
+  {
+    /**
+     * {summary}{Rescaled to 8bpp.}
+     * {description}{The minimum value of the image is set to 0, and its maximum value is set to 255.}
+     **/
+    ImageExtractionMode_Preview = 1,
+
+    /**
+     * {summary}{Truncation to the [0, 255] range.}
+     **/
+    ImageExtractionMode_UInt8 = 2,
+
+    /**
+     * {summary}{Truncation to the [0, 65535] range.}
+     **/
+    ImageExtractionMode_UInt16 = 3,
+
+    /**
+     * {summary}{Truncation to the [-32768, 32767] range.}
+     **/
+    ImageExtractionMode_Int16 = 4
+  };
+
+
+  /**
+   * Most common, non-joke and non-experimental HTTP status codes
+   * http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
+   **/
+  enum HttpStatus
+  {
+    HttpStatus_None = -1,
+
+    // 1xx Informational
+    HttpStatus_100_Continue = 100,
+    HttpStatus_101_SwitchingProtocols = 101,
+    HttpStatus_102_Processing = 102,
+
+    // 2xx Success
+    HttpStatus_200_Ok = 200,
+    HttpStatus_201_Created = 201,
+    HttpStatus_202_Accepted = 202,
+    HttpStatus_203_NonAuthoritativeInformation = 203,
+    HttpStatus_204_NoContent = 204,
+    HttpStatus_205_ResetContent = 205,
+    HttpStatus_206_PartialContent = 206,
+    HttpStatus_207_MultiStatus = 207,
+    HttpStatus_208_AlreadyReported = 208,
+    HttpStatus_226_IMUsed = 226,
+
+    // 3xx Redirection
+    HttpStatus_300_MultipleChoices = 300,
+    HttpStatus_301_MovedPermanently = 301,
+    HttpStatus_302_Found = 302,
+    HttpStatus_303_SeeOther = 303,
+    HttpStatus_304_NotModified = 304,
+    HttpStatus_305_UseProxy = 305,
+    HttpStatus_307_TemporaryRedirect = 307,
+
+    // 4xx Client Error
+    HttpStatus_400_BadRequest = 400,
+    HttpStatus_401_Unauthorized = 401,
+    HttpStatus_402_PaymentRequired = 402,
+    HttpStatus_403_Forbidden = 403,
+    HttpStatus_404_NotFound = 404,
+    HttpStatus_405_MethodNotAllowed = 405,
+    HttpStatus_406_NotAcceptable = 406,
+    HttpStatus_407_ProxyAuthenticationRequired = 407,
+    HttpStatus_408_RequestTimeout = 408,
+    HttpStatus_409_Conflict = 409,
+    HttpStatus_410_Gone = 410,
+    HttpStatus_411_LengthRequired = 411,
+    HttpStatus_412_PreconditionFailed = 412,
+    HttpStatus_413_RequestEntityTooLarge = 413,
+    HttpStatus_414_RequestUriTooLong = 414,
+    HttpStatus_415_UnsupportedMediaType = 415,
+    HttpStatus_416_RequestedRangeNotSatisfiable = 416,
+    HttpStatus_417_ExpectationFailed = 417,
+    HttpStatus_422_UnprocessableEntity = 422,
+    HttpStatus_423_Locked = 423,
+    HttpStatus_424_FailedDependency = 424,
+    HttpStatus_426_UpgradeRequired = 426,
+
+    // 5xx Server Error
+    HttpStatus_500_InternalServerError = 500,
+    HttpStatus_501_NotImplemented = 501,
+    HttpStatus_502_BadGateway = 502,
+    HttpStatus_503_ServiceUnavailable = 503,
+    HttpStatus_504_GatewayTimeout = 504,
+    HttpStatus_505_HttpVersionNotSupported = 505,
+    HttpStatus_506_VariantAlsoNegotiates = 506,
+    HttpStatus_507_InsufficientStorage = 507,
+    HttpStatus_509_BandwidthLimitExceeded = 509,
+    HttpStatus_510_NotExtended = 510
+  };
+
+
+  enum HttpMethod
+  {
+    HttpMethod_Get = 0,
+    HttpMethod_Post = 1,
+    HttpMethod_Delete = 2,
+    HttpMethod_Put = 3
+  };
+
+
+  enum ImageFormat
+  {
+    ImageFormat_Png = 1
+  };
+
+
+  // http://www.dabsoft.ch/dicom/3/C.12.1.1.2/
+  enum Encoding
+  {
+    Encoding_Ascii,
+    Encoding_Utf8,
+    Encoding_Latin1,
+    Encoding_Latin2,
+    Encoding_Latin3,
+    Encoding_Latin4,
+    Encoding_Latin5,                        // Turkish
+    Encoding_Cyrillic,
+    Encoding_Windows1251,                   // Windows-1251 (commonly used for Cyrillic)
+    Encoding_Arabic,
+    Encoding_Greek,
+    Encoding_Hebrew,
+    Encoding_Thai,                          // TIS 620-2533
+    Encoding_Japanese,                      // JIS X 0201 (Shift JIS): Katakana
+    Encoding_Chinese                        // GB18030 - Chinese simplified
+    //Encoding_JapaneseKanji,               // Multibyte - JIS X 0208: Kanji
+    //Encoding_JapaneseSupplementaryKanji,  // Multibyte - JIS X 0212: Supplementary Kanji set
+    //Encoding_Korean,                      // Multibyte - KS X 1001: Hangul and Hanja
+  };
+
+
+  // https://www.dabsoft.ch/dicom/3/C.7.6.3.1.2/
+  enum PhotometricInterpretation
+  {
+    PhotometricInterpretation_ARGB,  // Retired
+    PhotometricInterpretation_CMYK,  // Retired
+    PhotometricInterpretation_HSV,   // Retired
+    PhotometricInterpretation_Monochrome1,
+    PhotometricInterpretation_Monochrome2,
+    PhotometricInterpretation_Palette,
+    PhotometricInterpretation_RGB,
+    PhotometricInterpretation_YBRFull,
+    PhotometricInterpretation_YBRFull422,
+    PhotometricInterpretation_YBRPartial420,
+    PhotometricInterpretation_YBRPartial422,
+    PhotometricInterpretation_YBR_ICT,
+    PhotometricInterpretation_YBR_RCT,
+    PhotometricInterpretation_Unknown
+  };
+
+  enum DicomModule
+  {
+    DicomModule_Patient,
+    DicomModule_Study,
+    DicomModule_Series,
+    DicomModule_Instance,
+    DicomModule_Image
+  };
+
+
+  /**
+   * WARNING: Do not change the explicit values in the enumerations
+   * below this point. This would result in incompatible databases
+   * between versions of Orthanc!
+   **/
+
+  enum CompressionType
+  {
+    CompressionType_None = 1,
+    CompressionType_Zlib = 2
+  };
+
+  enum FileContentType
+  {
+    // If you add a value below, insert it in "PluginStorageArea" in
+    // the file "Plugins/Engine/OrthancPlugins.cpp"
+    FileContentType_Unknown = 0,
+    FileContentType_Dicom = 1,
+    FileContentType_DicomAsJson = 2,
+
+    // Make sure that the value "65535" can be stored into this enumeration
+    FileContentType_StartUser = 1024,
+    FileContentType_EndUser = 65535
+  };
+
+  enum ResourceType
+  {
+    ResourceType_Patient = 1,
+    ResourceType_Study = 2,
+    ResourceType_Series = 3,
+    ResourceType_Instance = 4
+  };
+
+
+  const char* EnumerationToString(HttpMethod method);
+
+  const char* EnumerationToString(HttpStatus status);
+
+  const char* EnumerationToString(ResourceType type);
+
+  const char* EnumerationToString(ImageFormat format);
+
+  const char* EnumerationToString(Encoding encoding);
+
+  const char* EnumerationToString(PhotometricInterpretation photometric);
+
+  Encoding StringToEncoding(const char* encoding);
+
+  ResourceType StringToResourceType(const char* type);
+
+  ImageFormat StringToImageFormat(const char* format);
+
   unsigned int GetBytesPerPixel(PixelFormat format);
+
+  bool GetDicomEncoding(Encoding& encoding,
+                        const char* specificCharacterSet);
+
+  const char* GetMimeType(FileContentType type);
 }
--- a/Orthanc/FileStorage/FilesystemStorage.cpp	Fri May 29 11:07:38 2015 +0200
+++ b/Orthanc/FileStorage/FilesystemStorage.cpp	Mon Jun 01 11:52:28 2015 +0200
@@ -42,6 +42,10 @@
 
 #include <boost/filesystem/fstream.hpp>
 
+#if HAVE_GOOGLE_LOG == 1
+#include <glog/logging.h>
+#endif
+
 static std::string ToString(const boost::filesystem::path& p)
 {
 #if BOOST_HAS_FILESYSTEM_V3 == 1
@@ -81,12 +85,13 @@
     //root_ = boost::filesystem::absolute(root).string();
     root_ = root;
 
-    Toolbox::CreateNewDirectory(root);
+    Toolbox::MakeDirectory(root);
   }
 
   void FilesystemStorage::Create(const std::string& uuid,
                                  const void* content, 
-                                 size_t size)
+                                 size_t size,
+                                 FileContentType /*type*/)
   {
     boost::filesystem::path path;
     
@@ -136,7 +141,8 @@
 
 
   void FilesystemStorage::Read(std::string& content,
-                               const std::string& uuid)
+                               const std::string& uuid,
+                               FileContentType /*type*/)
   {
     content.clear();
     Toolbox::ReadFile(content, GetPath(uuid).string());
@@ -201,13 +207,18 @@
 
     for (List::const_iterator it = result.begin(); it != result.end(); ++it)
     {
-      Remove(*it);
+      Remove(*it, FileContentType_Unknown /*ignored in this class*/);
     }
   }
 
 
-  void FilesystemStorage::Remove(const std::string& uuid)
+  void FilesystemStorage::Remove(const std::string& uuid,
+                                 FileContentType /*type*/)
   {
+#if HAVE_GOOGLE_LOG == 1
+    LOG(INFO) << "Deleting file " << uuid;
+#endif
+
     namespace fs = boost::filesystem;
 
     fs::path p = GetPath(uuid);
--- a/Orthanc/FileStorage/FilesystemStorage.h	Fri May 29 11:07:38 2015 +0200
+++ b/Orthanc/FileStorage/FilesystemStorage.h	Mon Jun 01 11:52:28 2015 +0200
@@ -32,14 +32,19 @@
 
 #pragma once
 
+#include "IStorageArea.h"
+
 #include <boost/filesystem.hpp>
 #include <set>
-#include <stdint.h>
 
 namespace Orthanc
 {
-  class FilesystemStorage : public boost::noncopyable
+  class FilesystemStorage : public IStorageArea
   {
+    // TODO REMOVE THIS
+    friend class FilesystemHttpSender;
+    friend class FileStorageAccessor;
+
   private:
     boost::filesystem::path root_;
 
@@ -50,12 +55,15 @@
 
     virtual void Create(const std::string& uuid,
                         const void* content, 
-                        size_t size);
+                        size_t size,
+                        FileContentType type);
 
     virtual void Read(std::string& content,
-                      const std::string& uuid);
+                      const std::string& uuid,
+                      FileContentType type);
 
-    virtual void Remove(const std::string& uuid);
+    virtual void Remove(const std::string& uuid,
+                        FileContentType type);
 
     void ListAllFiles(std::set<std::string>& result) const;
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orthanc/FileStorage/IStorageArea.h	Mon Jun 01 11:52:28 2015 +0200
@@ -0,0 +1,61 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2015 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Enumerations.h"
+
+#include <string>
+#include <boost/noncopyable.hpp>
+
+namespace Orthanc
+{
+  class IStorageArea : public boost::noncopyable
+  {
+  public:
+    virtual ~IStorageArea()
+    {
+    }
+
+    virtual void Create(const std::string& uuid,
+                        const void* content,
+                        size_t size,
+                        FileContentType type) = 0;
+
+    virtual void Read(std::string& content,
+                      const std::string& uuid,
+                      FileContentType type) = 0;
+
+    virtual void Remove(const std::string& uuid,
+                        FileContentType type) = 0;
+  };
+}
--- a/Orthanc/ImageFormats/ImageAccessor.cpp	Fri May 29 11:07:38 2015 +0200
+++ b/Orthanc/ImageFormats/ImageAccessor.cpp	Mon Jun 01 11:52:28 2015 +0200
@@ -34,17 +34,84 @@
 #include "ImageAccessor.h"
 
 #include "../OrthancException.h"
+#include "../ChunkedBuffer.h"
 
 #include <stdint.h>
 #include <cassert>
 #include <boost/lexical_cast.hpp>
 
+#if HAVE_GOOGLE_LOG == 1
+#include <glog/logging.h>
+#endif
+
+
 namespace Orthanc
 {
+  template <typename PixelType>
+  static void ToMatlabStringInternal(ChunkedBuffer& target,
+                                     const ImageAccessor& source)
+  {
+    target.AddChunk("double([ ");
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      const PixelType* p = reinterpret_cast<const PixelType*>(source.GetConstRow(y));
+
+      std::string s;
+      if (y > 0)
+      {
+        s = "; ";
+      }
+
+      s.reserve(source.GetWidth() * 8);
+
+      for (unsigned int x = 0; x < source.GetWidth(); x++, p++)
+      {
+        s += boost::lexical_cast<std::string>(static_cast<int>(*p)) + " ";
+      }
+
+      target.AddChunk(s);
+    }
+
+    target.AddChunk("])");
+  }
+
+
+  static void RGB24ToMatlabString(ChunkedBuffer& target,
+                                  const ImageAccessor& source)
+  {
+    assert(source.GetFormat() == PixelFormat_RGB24);
+
+    target.AddChunk("double(permute(reshape([ ");
+
+    for (unsigned int y = 0; y < source.GetHeight(); y++)
+    {
+      const uint8_t* p = reinterpret_cast<const uint8_t*>(source.GetConstRow(y));
+      
+      std::string s;
+      s.reserve(source.GetWidth() * 3 * 8);
+      
+      for (unsigned int x = 0; x < 3 * source.GetWidth(); x++, p++)
+      {
+        s += boost::lexical_cast<std::string>(static_cast<int>(*p)) + " ";
+      }
+      
+      target.AddChunk(s);
+    }
+
+    target.AddChunk("], [ 3 " + boost::lexical_cast<std::string>(source.GetHeight()) +
+                    " " + boost::lexical_cast<std::string>(source.GetWidth()) + " ]), [ 3 2 1 ]))");
+  }
+
+
   void* ImageAccessor::GetBuffer() const
   {
     if (readOnly_)
     {
+#if HAVE_GOOGLE_LOG == 1
+      LOG(ERROR) << "Trying to write on a read-only image";
+#endif
+
       throw OrthancException(ErrorCode_ReadOnly);
     }
 
@@ -69,6 +136,10 @@
   {
     if (readOnly_)
     {
+#if HAVE_GOOGLE_LOG == 1
+      LOG(ERROR) << "Trying to write on a read-only image";
+#endif
+
       throw OrthancException(ErrorCode_ReadOnly);
     }
 
@@ -126,4 +197,35 @@
 
     assert(GetBytesPerPixel() * width_ <= pitch_);
   }
+
+
+  void ImageAccessor::ToMatlabString(std::string& target) const
+  {
+    ChunkedBuffer buffer;
+
+    switch (GetFormat())
+    {
+      case PixelFormat_Grayscale8:
+        ToMatlabStringInternal<uint8_t>(buffer, *this);
+        break;
+
+      case PixelFormat_Grayscale16:
+        ToMatlabStringInternal<uint16_t>(buffer, *this);
+        break;
+
+      case PixelFormat_SignedGrayscale16:
+        ToMatlabStringInternal<int16_t>(buffer, *this);
+        break;
+
+      case PixelFormat_RGB24:
+        RGB24ToMatlabString(buffer, *this);
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }   
+
+    buffer.Flatten(target);
+  }
+
 }
--- a/Orthanc/ImageFormats/ImageAccessor.h	Fri May 29 11:07:38 2015 +0200
+++ b/Orthanc/ImageFormats/ImageAccessor.h	Mon Jun 01 11:52:28 2015 +0200
@@ -34,6 +34,8 @@
 
 #include "../Enumerations.h"
 
+#include <string>
+
 namespace Orthanc
 {
   class ImageAccessor
@@ -111,5 +113,7 @@
                         unsigned int height,
                         unsigned int pitch,
                         void *buffer);
+
+    void ToMatlabString(std::string& target) const; 
   };
 }
--- a/Orthanc/ImageFormats/ImageProcessing.cpp	Fri May 29 11:07:38 2015 +0200
+++ b/Orthanc/ImageFormats/ImageProcessing.cpp	Mon Jun 01 11:52:28 2015 +0200
@@ -209,7 +209,7 @@
   void MultiplyConstantInternal(ImageAccessor& image,
                                 float factor)
   {
-    if (abs(factor - 1.0f) <= std::numeric_limits<float>::epsilon())
+    if (std::abs(factor - 1.0f) <= std::numeric_limits<float>::epsilon())
     {
       return;
     }
--- a/Orthanc/MultiThreading/SharedMessageQueue.cpp	Fri May 29 11:07:38 2015 +0200
+++ b/Orthanc/MultiThreading/SharedMessageQueue.cpp	Mon Jun 01 11:52:28 2015 +0200
@@ -61,7 +61,6 @@
  **/
 
 
-
 namespace Orthanc
 {
   SharedMessageQueue::SharedMessageQueue(unsigned int maxSize) :
--- a/Orthanc/PrecompiledHeaders.h	Fri May 29 11:07:38 2015 +0200
+++ b/Orthanc/PrecompiledHeaders.h	Mon Jun 01 11:52:28 2015 +0200
@@ -38,12 +38,21 @@
 
 #if ORTHANC_USE_PRECOMPILED_HEADERS == 1
 
+#include <boost/date_time/posix_time/posix_time.hpp>
 #include <boost/filesystem.hpp>
 #include <boost/lexical_cast.hpp>
+#include <boost/locale.hpp>
+#include <boost/regex.hpp>
 #include <boost/thread.hpp>
 #include <boost/thread/shared_mutex.hpp>
+
+#include <glog/logging.h>
 #include <json/value.h>
 
+#if ORTHANC_PUGIXML_ENABLED == 1
+#include <pugixml.hpp>
+#endif
+
 #include "Enumerations.h"
 #include "OrthancException.h"
 #include "Toolbox.h"
--- a/Orthanc/README.txt	Fri May 29 11:07:38 2015 +0200
+++ b/Orthanc/README.txt	Mon Jun 01 11:52:28 2015 +0200
@@ -1,1 +1,1 @@
-This folder contains an excerpt of the source code of Orthanc 0.8.6.
+This folder contains an excerpt of the source code of Orthanc.
--- a/Orthanc/SQLite/FunctionContext.cpp	Fri May 29 11:07:38 2015 +0200
+++ b/Orthanc/SQLite/FunctionContext.cpp	Mon Jun 01 11:52:28 2015 +0200
@@ -40,7 +40,6 @@
 #include "OrthancSQLiteException.h"
 
 #include <sqlite3.h>
-#include <string>
 
 namespace Orthanc
 {
--- a/Orthanc/SQLite/StatementReference.cpp	Fri May 29 11:07:38 2015 +0200
+++ b/Orthanc/SQLite/StatementReference.cpp	Mon Jun 01 11:52:28 2015 +0200
@@ -47,7 +47,6 @@
 #endif
 
 #include <cassert>
-#include <string>
 #include "sqlite3.h"
 
 namespace Orthanc
--- a/Orthanc/Toolbox.cpp	Fri May 29 11:07:38 2015 +0200
+++ b/Orthanc/Toolbox.cpp	Mon Jun 01 11:52:28 2015 +0200
@@ -35,13 +35,28 @@
 
 #include "OrthancException.h"
 
+#include <string>
 #include <stdint.h>
 #include <string.h>
 #include <boost/filesystem.hpp>
 #include <boost/filesystem/fstream.hpp>
+#include <boost/uuid/sha1.hpp>
+#include <boost/lexical_cast.hpp>
 #include <algorithm>
 #include <ctype.h>
 
+#if BOOST_HAS_DATE_TIME == 1
+#include <boost/date_time/posix_time/posix_time.hpp>
+#endif
+
+#if BOOST_HAS_REGEX == 1
+#include <boost/regex.hpp> 
+#endif
+
+#if HAVE_GOOGLE_LOG == 1
+#include <glog/logging.h>
+#endif
+
 #if defined(_WIN32)
 #include <windows.h>
 #include <process.h>   // For "_spawnvp()"
@@ -55,7 +70,7 @@
 #include <limits.h>      /* PATH_MAX */
 #endif
 
-#if defined(__linux) || defined(__FreeBSD_kernel__)
+#if defined(__linux) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
 #include <limits.h>      /* PATH_MAX */
 #include <signal.h>
 #include <unistd.h>
@@ -67,57 +82,122 @@
 
 #include <boost/locale.hpp>
 
+#include "../Resources/ThirdParty/md5/md5.h"
+#include "../Resources/ThirdParty/base64/base64.h"
+
+
+#if defined(_MSC_VER) && (_MSC_VER < 1800)
+// Patch for the missing "_strtoll" symbol when compiling with Visual Studio < 2013
+extern "C"
+{
+  int64_t _strtoi64(const char *nptr, char **endptr, int base);
+  int64_t strtoll(const char *nptr, char **endptr, int base)
+  {
+    return _strtoi64(nptr, endptr, base);
+  } 
+}
+#endif
+
+
+#if ORTHANC_PUGIXML_ENABLED == 1
+#include "ChunkedBuffer.h"
+#include <pugixml.hpp>
+#endif
 
 
 namespace Orthanc
 {
-  void Toolbox::TokenizeString(std::vector<std::string>& result,
-                               const std::string& value,
-                               char separator)
-  {
-    result.clear();
-
-    std::string currentItem;
+  static bool finish;
 
-    for (size_t i = 0; i < value.size(); i++)
-    {
-      if (value[i] == separator)
-      {
-        result.push_back(currentItem);
-        currentItem.clear();
-      }
-      else
-      {
-        currentItem.push_back(value[i]);
-      }
-    }
+#if defined(_WIN32)
+  static BOOL WINAPI ConsoleControlHandler(DWORD dwCtrlType)
+  {
+    // http://msdn.microsoft.com/en-us/library/ms683242(v=vs.85).aspx
+    finish = true;
+    return true;
+  }
+#else
+  static void SignalHandler(int)
+  {
+    finish = true;
+  }
+#endif
 
-    result.push_back(currentItem);
+  void Toolbox::USleep(uint64_t microSeconds)
+  {
+#if defined(_WIN32)
+    ::Sleep(static_cast<DWORD>(microSeconds / static_cast<uint64_t>(1000)));
+#elif defined(__linux) || defined(__APPLE__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
+    usleep(microSeconds);
+#else
+#error Support your platform here
+#endif
   }
 
 
-  void Toolbox::CreateNewDirectory(const std::string& path)
+  static void ServerBarrierInternal(const bool* stopFlag)
   {
-    if (boost::filesystem::exists(path))
+#if defined(_WIN32)
+    SetConsoleCtrlHandler(ConsoleControlHandler, true);
+#else
+    signal(SIGINT, SignalHandler);
+    signal(SIGQUIT, SignalHandler);
+    signal(SIGTERM, SignalHandler);
+#endif
+  
+    // Active loop that awakens every 100ms
+    finish = false;
+    while (!(*stopFlag || finish))
     {
-      if (!boost::filesystem::is_directory(path))
-      {
-        throw OrthancException("Cannot create the directory over an existing file: " + path);
-      }
+      Toolbox::USleep(100 * 1000);
     }
-    else
-    {
-      if (!boost::filesystem::create_directories(path))
-      {
-        throw OrthancException("Unable to create the directory: " + path);
-      }
-    }
+
+#if defined(_WIN32)
+    SetConsoleCtrlHandler(ConsoleControlHandler, false);
+#else
+    signal(SIGINT, NULL);
+    signal(SIGQUIT, NULL);
+    signal(SIGTERM, NULL);
+#endif
   }
 
 
-  bool Toolbox::IsExistingFile(const std::string& path)
+  void Toolbox::ServerBarrier(const bool& stopFlag)
+  {
+    ServerBarrierInternal(&stopFlag);
+  }
+
+  void Toolbox::ServerBarrier()
+  {
+    const bool stopFlag = false;
+    ServerBarrierInternal(&stopFlag);
+  }
+
+
+  void Toolbox::ToUpperCase(std::string& s)
   {
-    return boost::filesystem::exists(path);
+    std::transform(s.begin(), s.end(), s.begin(), toupper);
+  }
+
+
+  void Toolbox::ToLowerCase(std::string& s)
+  {
+    std::transform(s.begin(), s.end(), s.begin(), tolower);
+  }
+
+
+  void Toolbox::ToUpperCase(std::string& result,
+                            const std::string& source)
+  {
+    result = source;
+    ToUpperCase(result);
+  }
+
+  void Toolbox::ToLowerCase(std::string& result,
+                            const std::string& source)
+  {
+    result = source;
+    ToLowerCase(result);
   }
 
 
@@ -179,15 +259,539 @@
 
 
 
-  void Toolbox::USleep(uint64_t microSeconds)
+  void Toolbox::SplitUriComponents(UriComponents& components,
+                                   const std::string& uri)
+  {
+    static const char URI_SEPARATOR = '/';
+
+    components.clear();
+
+    if (uri.size() == 0 ||
+        uri[0] != URI_SEPARATOR)
+    {
+      throw OrthancException(ErrorCode_UriSyntax);
+    }
+
+    // Count the number of slashes in the URI to make an assumption
+    // about the number of components in the URI
+    unsigned int estimatedSize = 0;
+    for (unsigned int i = 0; i < uri.size(); i++)
+    {
+      if (uri[i] == URI_SEPARATOR)
+        estimatedSize++;
+    }
+
+    components.reserve(estimatedSize - 1);
+
+    unsigned int start = 1;
+    unsigned int end = 1;
+    while (end < uri.size())
+    {
+      // This is the loop invariant
+      assert(uri[start - 1] == '/' && (end >= start));
+
+      if (uri[end] == '/')
+      {
+        components.push_back(std::string(&uri[start], end - start));
+        end++;
+        start = end;
+      }
+      else
+      {
+        end++;
+      }
+    }
+
+    if (start < uri.size())
+    {
+      components.push_back(std::string(&uri[start], end - start));
+    }
+
+    for (size_t i = 0; i < components.size(); i++)
+    {
+      if (components[i].size() == 0)
+      {
+        // Empty component, as in: "/coucou//e"
+        throw OrthancException(ErrorCode_UriSyntax);
+      }
+    }
+  }
+
+
+  void Toolbox::TruncateUri(UriComponents& target,
+                            const UriComponents& source,
+                            size_t fromLevel)
+  {
+    target.clear();
+
+    if (source.size() > fromLevel)
+    {
+      target.resize(source.size() - fromLevel);
+
+      size_t j = 0;
+      for (size_t i = fromLevel; i < source.size(); i++, j++)
+      {
+        target[j] = source[i];
+      }
+
+      assert(j == target.size());
+    }
+  }
+  
+
+
+  bool Toolbox::IsChildUri(const UriComponents& baseUri,
+                           const UriComponents& testedUri)
+  {
+    if (testedUri.size() < baseUri.size())
+    {
+      return false;
+    }
+
+    for (size_t i = 0; i < baseUri.size(); i++)
+    {
+      if (baseUri[i] != testedUri[i])
+        return false;
+    }
+
+    return true;
+  }
+
+
+  std::string Toolbox::AutodetectMimeType(const std::string& path)
   {
+    std::string contentType;
+    size_t lastDot = path.rfind('.');
+    size_t lastSlash = path.rfind('/');
+
+    if (lastDot == std::string::npos ||
+        (lastSlash != std::string::npos && lastDot < lastSlash))
+    {
+      // No trailing dot, unable to detect the content type
+    }
+    else
+    {
+      const char* extension = &path[lastDot + 1];
+    
+      // http://en.wikipedia.org/wiki/Mime_types
+      // Text types
+      if (!strcmp(extension, "txt"))
+        contentType = "text/plain";
+      else if (!strcmp(extension, "html"))
+        contentType = "text/html";
+      else if (!strcmp(extension, "xml"))
+        contentType = "text/xml";
+      else if (!strcmp(extension, "css"))
+        contentType = "text/css";
+
+      // Application types
+      else if (!strcmp(extension, "js"))
+        contentType = "application/javascript";
+      else if (!strcmp(extension, "json"))
+        contentType = "application/json";
+      else if (!strcmp(extension, "pdf"))
+        contentType = "application/pdf";
+
+      // Images types
+      else if (!strcmp(extension, "jpg") || !strcmp(extension, "jpeg"))
+        contentType = "image/jpeg";
+      else if (!strcmp(extension, "gif"))
+        contentType = "image/gif";
+      else if (!strcmp(extension, "png"))
+        contentType = "image/png";
+    }
+
+    return contentType;
+  }
+
+
+  std::string Toolbox::FlattenUri(const UriComponents& components,
+                                  size_t fromLevel)
+  {
+    if (components.size() <= fromLevel)
+    {
+      return "/";
+    }
+    else
+    {
+      std::string r;
+
+      for (size_t i = fromLevel; i < components.size(); i++)
+      {
+        r += "/" + components[i];
+      }
+
+      return r;
+    }
+  }
+
+
+
+  uint64_t Toolbox::GetFileSize(const std::string& path)
+  {
+    try
+    {
+      return static_cast<uint64_t>(boost::filesystem::file_size(path));
+    }
+    catch (boost::filesystem::filesystem_error)
+    {
+      throw OrthancException(ErrorCode_InexistentFile);
+    }
+  }
+
+
+  static char GetHexadecimalCharacter(uint8_t value)
+  {
+    assert(value < 16);
+
+    if (value < 10)
+      return value + '0';
+    else
+      return (value - 10) + 'a';
+  }
+
+
+  void Toolbox::ComputeMD5(std::string& result,
+                           const std::string& data)
+  {
+    if (data.size() > 0)
+    {
+      ComputeMD5(result, &data[0], data.size());
+    }
+    else
+    {
+      ComputeMD5(result, NULL, 0);
+    }
+  }
+
+
+  void Toolbox::ComputeMD5(std::string& result,
+                           const void* data,
+                           size_t length)
+  {
+    md5_state_s state;
+    md5_init(&state);
+
+    if (length > 0)
+    {
+      md5_append(&state, 
+                 reinterpret_cast<const md5_byte_t*>(data), 
+                 static_cast<int>(length));
+    }
+
+    md5_byte_t actualHash[16];
+    md5_finish(&state, actualHash);
+
+    result.resize(32);
+    for (unsigned int i = 0; i < 16; i++)
+    {
+      result[2 * i] = GetHexadecimalCharacter(actualHash[i] / 16);
+      result[2 * i + 1] = GetHexadecimalCharacter(actualHash[i] % 16);
+    }
+  }
+
+
+  void Toolbox::EncodeBase64(std::string& result, 
+                             const std::string& data)
+  {
+    result = base64_encode(data);
+  }
+
+  void Toolbox::DecodeBase64(std::string& result, 
+                             const std::string& data)
+  {
+    result = base64_decode(data);
+  }
+
+
 #if defined(_WIN32)
-    ::Sleep(static_cast<DWORD>(microSeconds / static_cast<uint64_t>(1000)));
-#elif defined(__linux) || defined(__APPLE__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
-    usleep(microSeconds);
+  static std::string GetPathToExecutableInternal()
+  {
+    // Yes, this is ugly, but there is no simple way to get the 
+    // required buffer size, so we use a big constant
+    std::vector<char> buffer(32768);
+    /*int bytes =*/ GetModuleFileNameA(NULL, &buffer[0], static_cast<DWORD>(buffer.size() - 1));
+    return std::string(&buffer[0]);
+  }
+
+#elif defined(__linux) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
+  static std::string GetPathToExecutableInternal()
+  {
+    std::vector<char> buffer(PATH_MAX + 1);
+    ssize_t bytes = readlink("/proc/self/exe", &buffer[0], buffer.size() - 1);
+    if (bytes == 0)
+    {
+      throw OrthancException("Unable to get the path to the executable");
+    }
+
+    return std::string(&buffer[0]);
+  }
+
+#elif defined(__APPLE__) && defined(__MACH__)
+  static std::string GetPathToExecutableInternal()
+  {
+    char pathbuf[PATH_MAX + 1];
+    unsigned int  bufsize = static_cast<int>(sizeof(pathbuf));
+
+    _NSGetExecutablePath( pathbuf, &bufsize);
+
+    return std::string(pathbuf);
+  }
+
 #else
 #error Support your platform here
 #endif
+
+
+  std::string Toolbox::GetPathToExecutable()
+  {
+    boost::filesystem::path p(GetPathToExecutableInternal());
+    return boost::filesystem::absolute(p).string();
+  }
+
+
+  std::string Toolbox::GetDirectoryOfExecutable()
+  {
+    boost::filesystem::path p(GetPathToExecutableInternal());
+    return boost::filesystem::absolute(p.parent_path()).string();
+  }
+
+
+  std::string Toolbox::ConvertToUtf8(const std::string& source,
+                                     const Encoding sourceEncoding)
+  {
+    const char* encoding;
+
+
+    // http://bradleyross.users.sourceforge.net/docs/dicom/doc/src-html/org/dcm4che2/data/SpecificCharacterSet.html
+    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;
+
+      case Encoding_Latin2:
+        encoding = "ISO-8859-2";
+        break;
+
+      case Encoding_Latin3:
+        encoding = "ISO-8859-3";
+        break;
+
+      case Encoding_Latin4:
+        encoding = "ISO-8859-4";
+        break;
+
+      case Encoding_Latin5:
+        encoding = "ISO-8859-9";
+        break;
+
+      case Encoding_Cyrillic:
+        encoding = "ISO-8859-5";
+        break;
+
+      case Encoding_Windows1251:
+        encoding = "WINDOWS-1251";
+        break;
+
+      case Encoding_Arabic:
+        encoding = "ISO-8859-6";
+        break;
+
+      case Encoding_Greek:
+        encoding = "ISO-8859-7";
+        break;
+
+      case Encoding_Hebrew:
+        encoding = "ISO-8859-8";
+        break;
+        
+      case Encoding_Japanese:
+        encoding = "SHIFT-JIS";
+        break;
+
+      case Encoding_Chinese:
+        encoding = "GB18030";
+        break;
+
+      case Encoding_Thai:
+        encoding = "TIS620.2533-0";
+        break;
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+
+    try
+    {
+      return boost::locale::conv::to_utf<char>(source, encoding);
+    }
+    catch (std::runtime_error&)
+    {
+      // Bad input string or bad encoding
+      return ConvertToAscii(source);
+    }
+  }
+
+
+  std::string Toolbox::ConvertToAscii(const std::string& source)
+  {
+    std::string result;
+
+    result.reserve(source.size() + 1);
+    for (size_t i = 0; i < source.size(); i++)
+    {
+      if (source[i] <= 127 && source[i] >= 0 && !iscntrl(source[i]))
+      {
+        result.push_back(source[i]);
+      }
+    }
+
+    return result;
+  }
+
+  void Toolbox::ComputeSHA1(std::string& result,
+                            const std::string& data)
+  {
+    boost::uuids::detail::sha1 sha1;
+
+    if (data.size() > 0)
+    {
+      sha1.process_bytes(&data[0], data.size());
+    }
+
+    unsigned int digest[5];
+
+    // Sanity check for the memory layout: A SHA-1 digest is 160 bits wide
+    assert(sizeof(unsigned int) == 4 && sizeof(digest) == (160 / 8)); 
+    
+    sha1.get_digest(digest);
+
+    result.resize(8 * 5 + 4);
+    sprintf(&result[0], "%08x-%08x-%08x-%08x-%08x",
+            digest[0],
+            digest[1],
+            digest[2],
+            digest[3],
+            digest[4]);
+  }
+
+  bool Toolbox::IsSHA1(const std::string& str)
+  {
+    if (str.size() != 44)
+    {
+      return false;
+    }
+
+    for (unsigned int i = 0; i < 44; i++)
+    {
+      if (i == 8 ||
+          i == 17 ||
+          i == 26 ||
+          i == 35)
+      {
+        if (str[i] != '-')
+          return false;
+      }
+      else
+      {
+        if (!isalnum(str[i]))
+          return false;
+      }
+    }
+
+    return true;
+  }
+
+
+#if BOOST_HAS_DATE_TIME == 1
+  std::string Toolbox::GetNowIsoString()
+  {
+    boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
+    return boost::posix_time::to_iso_string(now);
+  }
+#endif
+
+
+  std::string Toolbox::StripSpaces(const std::string& source)
+  {
+    size_t first = 0;
+
+    while (first < source.length() &&
+           isspace(source[first]))
+    {
+      first++;
+    }
+
+    if (first == source.length())
+    {
+      // String containing only spaces
+      return "";
+    }
+
+    size_t last = source.length();
+    while (last > first &&
+           isspace(source[last - 1]))
+    {
+      last--;
+    }          
+    
+    assert(first <= last);
+    return source.substr(first, last - first);
+  }
+
+
+  static char Hex2Dec(char c)
+  {
+    return ((c >= '0' && c <= '9') ? c - '0' :
+            ((c >= 'a' && c <= 'f') ? c - 'a' + 10 : c - 'A' + 10));
+  }
+
+  void Toolbox::UrlDecode(std::string& s)
+  {
+    // http://en.wikipedia.org/wiki/Percent-encoding
+    // http://www.w3schools.com/tags/ref_urlencode.asp
+    // http://stackoverflow.com/questions/154536/encode-decode-urls-in-c
+
+    if (s.size() == 0)
+    {
+      return;
+    }
+
+    size_t source = 0;
+    size_t target = 0;
+
+    while (source < s.size())
+    {
+      if (s[source] == '%' &&
+          source + 2 < s.size() &&
+          isalnum(s[source + 1]) &&
+          isalnum(s[source + 2]))
+      {
+        s[target] = (Hex2Dec(s[source + 1]) << 4) | Hex2Dec(s[source + 2]);
+        source += 3;
+        target += 1;
+      }
+      else
+      {
+        if (s[source] == '+')
+          s[target] = ' ';
+        else
+          s[target] = s[source];
+
+        source++;
+        target++;
+      }
+    }
+
+    s.resize(target);
   }
 
 
@@ -216,31 +820,322 @@
   }
 
 
-  std::string Toolbox::StripSpaces(const std::string& source)
+#if BOOST_HAS_REGEX == 1
+  std::string Toolbox::WildcardToRegularExpression(const std::string& source)
   {
-    size_t first = 0;
+    // TODO - Speed up this with a regular expression
+
+    std::string result = source;
+
+    // Escape all special characters
+    boost::replace_all(result, "\\", "\\\\");
+    boost::replace_all(result, "^", "\\^");
+    boost::replace_all(result, ".", "\\.");
+    boost::replace_all(result, "$", "\\$");
+    boost::replace_all(result, "|", "\\|");
+    boost::replace_all(result, "(", "\\(");
+    boost::replace_all(result, ")", "\\)");
+    boost::replace_all(result, "[", "\\[");
+    boost::replace_all(result, "]", "\\]");
+    boost::replace_all(result, "+", "\\+");
+    boost::replace_all(result, "/", "\\/");
+    boost::replace_all(result, "{", "\\{");
+    boost::replace_all(result, "}", "\\}");
+
+    // Convert wildcards '*' and '?' to their regex equivalents
+    boost::replace_all(result, "?", ".");
+    boost::replace_all(result, "*", ".*");
+
+    return result;
+  }
+#endif
+
+
+
+  void Toolbox::TokenizeString(std::vector<std::string>& result,
+                               const std::string& value,
+                               char separator)
+  {
+    result.clear();
+
+    std::string currentItem;
+
+    for (size_t i = 0; i < value.size(); i++)
+    {
+      if (value[i] == separator)
+      {
+        result.push_back(currentItem);
+        currentItem.clear();
+      }
+      else
+      {
+        currentItem.push_back(value[i]);
+      }
+    }
+
+    result.push_back(currentItem);
+  }
+
 
-    while (first < source.length() &&
-           isspace(source[first]))
+#if BOOST_HAS_REGEX == 1
+  void Toolbox::DecodeDataUriScheme(std::string& mime,
+                                    std::string& content,
+                                    const std::string& source)
+  {
+    boost::regex pattern("data:([^;]+);base64,([a-zA-Z0-9=+/]*)",
+                         boost::regex::icase /* case insensitive search */);
+
+    boost::cmatch what;
+    if (regex_match(source.c_str(), what, pattern))
+    {
+      mime = what[1];
+      content = what[2];
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadFileFormat);
+    }
+  }
+#endif
+
+
+  void Toolbox::MakeDirectory(const std::string& path)
+  {
+    if (boost::filesystem::exists(path))
     {
-      first++;
+      if (!boost::filesystem::is_directory(path))
+      {
+        throw OrthancException("Cannot create the directory over an existing file: " + path);
+      }
+    }
+    else
+    {
+      if (!boost::filesystem::create_directories(path))
+      {
+        throw OrthancException("Unable to create the directory: " + path);
+      }
+    }
+  }
+
+
+  bool Toolbox::IsExistingFile(const std::string& path)
+  {
+    return boost::filesystem::exists(path);
+  }
+
+
+#if ORTHANC_PUGIXML_ENABLED == 1
+  class ChunkedBufferWriter : public pugi::xml_writer
+  {
+  private:
+    ChunkedBuffer buffer_;
+
+  public:
+    virtual void write(const void *data, size_t size)
+    {
+      if (size > 0)
+      {
+        buffer_.AddChunk(reinterpret_cast<const char*>(data), size);
+      }
     }
 
-    if (first == source.length())
+    void Flatten(std::string& s)
+    {
+      buffer_.Flatten(s);
+    }
+  };
+
+
+  static void JsonToXmlInternal(pugi::xml_node& target,
+                                const Json::Value& source,
+                                const std::string& arrayElement)
+  {
+    // http://jsoncpp.sourceforge.net/value_8h_source.html#l00030
+
+    switch (source.type())
     {
-      // String containing only spaces
-      return "";
+      case Json::nullValue:
+      {
+        target.append_child(pugi::node_pcdata).set_value("null");
+        break;
+      }
+
+      case Json::intValue:
+      {
+        std::string s = boost::lexical_cast<std::string>(source.asInt());
+        target.append_child(pugi::node_pcdata).set_value(s.c_str());
+        break;
+      }
+
+      case Json::uintValue:
+      {
+        std::string s = boost::lexical_cast<std::string>(source.asUInt());
+        target.append_child(pugi::node_pcdata).set_value(s.c_str());
+        break;
+      }
+
+      case Json::realValue:
+      {
+        std::string s = boost::lexical_cast<std::string>(source.asFloat());
+        target.append_child(pugi::node_pcdata).set_value(s.c_str());
+        break;
+      }
+
+      case Json::stringValue:
+      {
+        target.append_child(pugi::node_pcdata).set_value(source.asString().c_str());
+        break;
+      }
+
+      case Json::booleanValue:
+      {
+        target.append_child(pugi::node_pcdata).set_value(source.asBool() ? "true" : "false");
+        break;
+      }
+
+      case Json::arrayValue:
+      {
+        for (Json::Value::ArrayIndex i = 0; i < source.size(); i++)
+        {
+          pugi::xml_node node = target.append_child();
+          node.set_name(arrayElement.c_str());
+          JsonToXmlInternal(node, source[i], arrayElement);
+        }
+        break;
+      }
+        
+      case Json::objectValue:
+      {
+        Json::Value::Members members = source.getMemberNames();
+
+        for (size_t i = 0; i < members.size(); i++)
+        {
+          pugi::xml_node node = target.append_child();
+          node.set_name(members[i].c_str());
+          JsonToXmlInternal(node, source[members[i]], arrayElement);          
+        }
+
+        break;
+      }
+
+      default:
+        throw OrthancException(ErrorCode_NotImplemented);
+    }
+  }
+
+
+  void Toolbox::JsonToXml(std::string& target,
+                          const Json::Value& source,
+                          const std::string& rootElement,
+                          const std::string& arrayElement)
+  {
+    pugi::xml_document doc;
+
+    pugi::xml_node n = doc.append_child(rootElement.c_str());
+    JsonToXmlInternal(n, source, arrayElement);
+
+    pugi::xml_node decl = doc.prepend_child(pugi::node_declaration);
+    decl.append_attribute("version").set_value("1.0");
+    decl.append_attribute("encoding").set_value("utf-8");
+
+    ChunkedBufferWriter writer;
+    doc.save(writer, "  ", pugi::format_default, pugi::encoding_utf8);
+    writer.Flatten(target);
+  }
+
+#endif
+
+
+  void Toolbox::ExecuteSystemCommand(const std::string& command,
+                                     const std::vector<std::string>& arguments)
+  {
+    // Convert the arguments as a C array
+    std::vector<char*>  args(arguments.size() + 2);
+
+    args.front() = const_cast<char*>(command.c_str());
+
+    for (size_t i = 0; i < arguments.size(); i++)
+    {
+      args[i + 1] = const_cast<char*>(arguments[i].c_str());
     }
 
-    size_t last = source.length();
-    while (last > first &&
-           isspace(source[last - 1]))
+    args.back() = NULL;
+
+    int status;
+
+#if defined(_WIN32)
+    // http://msdn.microsoft.com/en-us/library/275khfab.aspx
+    status = static_cast<int>(_spawnvp(_P_OVERLAY, command.c_str(), &args[0]));
+
+#else
+    int pid = fork();
+
+    if (pid == -1)
+    {
+      // Error in fork()
+#if HAVE_GOOGLE_LOG == 1
+      LOG(ERROR) << "Cannot fork a child process";
+#endif
+
+      throw OrthancException(ErrorCode_SystemCommand);
+    }
+    else if (pid == 0)
+    {
+      // Execute the system command in the child process
+      execvp(command.c_str(), &args[0]);
+
+      // We should never get here
+      _exit(1);
+    }
+    else
+    {
+      // Wait for the system command to exit
+      waitpid(pid, &status, 0);
+    }
+#endif
+
+    if (status != 0)
     {
-      last--;
-    }          
-    
-    assert(first <= last);
-    return source.substr(first, last - first);
+#if HAVE_GOOGLE_LOG == 1
+      LOG(ERROR) << "System command failed with status code " << status;
+#endif
+
+      throw OrthancException(ErrorCode_SystemCommand);
+    }
+  }
+
+  
+  bool Toolbox::IsInteger(const std::string& str)
+  {
+    std::string s = StripSpaces(str);
+
+    if (s.size() == 0)
+    {
+      return false;
+    }
+
+    size_t pos = 0;
+    if (s[0] == '-')
+    {
+      if (s.size() == 1)
+      {
+        return false;
+      }
+
+      pos = 1;
+    }
+
+    while (pos < s.size())
+    {
+      if (!isdigit(s[pos]))
+      {
+        return false;
+      }
+
+      pos++;
+    }
+
+    return true;
   }
 }
 
--- a/Orthanc/Toolbox.h	Fri May 29 11:07:38 2015 +0200
+++ b/Orthanc/Toolbox.h	Mon Jun 01 11:52:28 2015 +0200
@@ -37,23 +37,34 @@
 #include <stdint.h>
 #include <vector>
 #include <string>
+
+#if ORTHANC_PUGIXML_ENABLED == 1
 #include <json/json.h>
+#endif
 
 namespace Orthanc
 {
   typedef std::vector<std::string> UriComponents;
 
+  class NullType
+  {
+  };
+
   namespace Toolbox
   {
-    Endianness DetectEndianness();
+    void ServerBarrier(const bool& stopFlag);
+
+    void ServerBarrier();
+
+    void ToUpperCase(std::string& s);  // Inplace version
 
-    void TokenizeString(std::vector<std::string>& result,
-                        const std::string& source,
-                        char separator);
+    void ToLowerCase(std::string& s);  // Inplace version
 
-    void CreateNewDirectory(const std::string& path);
+    void ToUpperCase(std::string& result,
+                     const std::string& source);
 
-    bool IsExistingFile(const std::string& path);
+    void ToLowerCase(std::string& result,
+                     const std::string& source);
 
     void ReadFile(std::string& content,
                   const std::string& path);
@@ -65,6 +76,89 @@
 
     void RemoveFile(const std::string& path);
 
+    void SplitUriComponents(UriComponents& components,
+                            const std::string& uri);
+  
+    void TruncateUri(UriComponents& target,
+                     const UriComponents& source,
+                     size_t fromLevel);
+  
+    bool IsChildUri(const UriComponents& baseUri,
+                    const UriComponents& testedUri);
+
+    std::string AutodetectMimeType(const std::string& path);
+
+    std::string FlattenUri(const UriComponents& components,
+                           size_t fromLevel = 0);
+
+    uint64_t GetFileSize(const std::string& path);
+
+    void ComputeMD5(std::string& result,
+                    const std::string& data);
+
+    void ComputeMD5(std::string& result,
+                    const void* data,
+                    size_t length);
+
+    void ComputeSHA1(std::string& result,
+                     const std::string& data);
+
+    bool IsSHA1(const std::string& str);
+
+    void DecodeBase64(std::string& result, 
+                      const std::string& data);
+
+    void EncodeBase64(std::string& result, 
+                      const std::string& data);
+
+    std::string GetPathToExecutable();
+
+    std::string GetDirectoryOfExecutable();
+
+    std::string ConvertToUtf8(const std::string& source,
+                              const Encoding sourceEncoding);
+
+    std::string ConvertToAscii(const std::string& source);
+
     std::string StripSpaces(const std::string& source);
+
+#if BOOST_HAS_DATE_TIME == 1
+    std::string GetNowIsoString();
+#endif
+
+    // In-place percent-decoding for URL
+    void UrlDecode(std::string& s);
+
+    Endianness DetectEndianness();
+
+#if BOOST_HAS_REGEX == 1
+    std::string WildcardToRegularExpression(const std::string& s);
+#endif
+
+    void TokenizeString(std::vector<std::string>& result,
+                        const std::string& source,
+                        char separator);
+
+#if BOOST_HAS_REGEX == 1
+    void DecodeDataUriScheme(std::string& mime,
+                             std::string& content,
+                             const std::string& source);
+#endif
+
+    void MakeDirectory(const std::string& path);
+
+    bool IsExistingFile(const std::string& path);
+
+#if ORTHANC_PUGIXML_ENABLED == 1
+    void JsonToXml(std::string& target,
+                   const Json::Value& source,
+                   const std::string& rootElement = "root",
+                   const std::string& arrayElement = "item");
+#endif
+
+    void ExecuteSystemCommand(const std::string& command,
+                              const std::vector<std::string>& arguments);
+
+    bool IsInteger(const std::string& str);
   }
 }
--- a/Plugin/Cache/CacheManager.cpp	Fri May 29 11:07:38 2015 +0200
+++ b/Plugin/Cache/CacheManager.cpp	Mon Jun 01 11:52:28 2015 +0200
@@ -226,7 +226,7 @@
     for (std::list<std::string>::const_iterator
            it = toRemove.begin(); it != toRemove.end(); it++)
     {
-      pimpl_->storage_.Remove(*it);
+      pimpl_->storage_.Remove(*it, Orthanc::FileContentType_Unknown);
     }
 
     pimpl_->bundles_[bundleIndex] = bundle;
@@ -339,7 +339,7 @@
     // Store the cached content on the disk
     const char* data = content.size() ? &content[0] : NULL;
     std::string uuid = Toolbox::GenerateUuid();
-    pimpl_->storage_.Create(uuid, data, content.size());
+    pimpl_->storage_.Create(uuid, data, content.size(), Orthanc::FileContentType_Unknown);
 
     bool ok = true;
 
@@ -378,7 +378,7 @@
     if (!ok)
     {
       // Error: Remove the stored file
-      pimpl_->storage_.Remove(uuid);
+      pimpl_->storage_.Remove(uuid, Orthanc::FileContentType_Unknown);
     }
     else
     {
@@ -389,7 +389,7 @@
       for (std::list<std::string>::const_iterator
              it = toRemove.begin(); it != toRemove.end(); it++)
       {
-        pimpl_->storage_.Remove(*it);
+        pimpl_->storage_.Remove(*it, Orthanc::FileContentType_Unknown);
       }
     }
 
@@ -466,7 +466,7 @@
     bool ok;
     try
     {
-      pimpl_->storage_.Read(content, uuid);
+      pimpl_->storage_.Read(content, uuid, Orthanc::FileContentType_Unknown);
       ok = (content.size() == size);
     }
     catch (std::runtime_error&)
@@ -512,7 +512,7 @@
       {
         transaction->Commit();
         pimpl_->bundles_[bundleIndex] = bundle;
-        pimpl_->storage_.Remove(uuid);
+        pimpl_->storage_.Remove(uuid, Orthanc::FileContentType_Unknown);
       }
     }
   }
@@ -558,7 +558,7 @@
     SQLite::Statement s(pimpl_->db_, SQLITE_FROM_HERE, "SELECT fileUuid FROM Cache");
     while (s.Step())
     {
-      pimpl_->storage_.Remove(s.ColumnString(0));    
+      pimpl_->storage_.Remove(s.ColumnString(0), Orthanc::FileContentType_Unknown);
     }  
 
     SQLite::Statement t(pimpl_->db_, SQLITE_FROM_HERE, "DELETE FROM Cache");
@@ -579,7 +579,7 @@
     s.BindInt(0, bundle);
     while (s.Step())
     {
-      pimpl_->storage_.Remove(s.ColumnString(0));
+      pimpl_->storage_.Remove(s.ColumnString(0), Orthanc::FileContentType_Unknown);
     }  
 
     SQLite::Statement t(pimpl_->db_, SQLITE_FROM_HERE, "DELETE FROM Cache WHERE bundle=?");
--- a/Resources/CMake/BoostConfiguration.cmake	Fri May 29 11:07:38 2015 +0200
+++ b/Resources/CMake/BoostConfiguration.cmake	Mon Jun 01 11:52:28 2015 +0200
@@ -21,7 +21,7 @@
 else()
   include(FindBoost)
   set(BOOST_STATIC 0)
-  find_package(Boost COMPONENTS system thread filesystem)
+  find_package(Boost COMPONENTS system thread filesystem locale)
 
   if (NOT Boost_FOUND)
     message(FATAL_ERROR "Unable to locate Boost on this system")
@@ -68,6 +68,7 @@
 
   list(APPEND BOOST_SOURCES
     ${BOOST_SOURCES_DIR}/libs/system/src/error_code.cpp
+    ${BOOST_SOURCES_DIR}/libs/locale/src/encoding/codepage.cpp
     )
 
 
@@ -121,4 +122,6 @@
 add_definitions(
   -DBOOST_HAS_FILESYSTEM_V3=1
   -DBOOST_HAS_LOCALE=1
+  -DBOOST_HAS_DATE_TIME=0
+  -DBOOST_HAS_REGEX=0
   )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/SyncOrthancFolder.py	Mon Jun 01 11:52:28 2015 +0200
@@ -0,0 +1,67 @@
+#!/usr/bin/python
+
+#
+# This maintenance script updates the content of the "Orthanc" folder
+# to match the latest version of the Orthanc source code.
+#
+
+import os
+import shutil
+
+SOURCE = '/home/jodogne/Subversion/Orthanc/Core'
+TARGET = os.path.join(os.path.dirname(__file__), '..', 'Orthanc')
+
+FILES = [
+    'ChunkedBuffer.cpp',
+    'ChunkedBuffer.h',
+    'Enumerations.cpp',
+    'Enumerations.h',
+    'FileStorage/FilesystemStorage.cpp',
+    'FileStorage/FilesystemStorage.h',
+    'FileStorage/IStorageArea.h',
+    'IDynamicObject.h',
+    'ImageFormats/ImageAccessor.cpp',
+    'ImageFormats/ImageAccessor.h',
+    'ImageFormats/ImageBuffer.cpp',
+    'ImageFormats/ImageBuffer.h',
+    'ImageFormats/ImageProcessing.cpp',
+    'ImageFormats/ImageProcessing.h',
+    'ImageFormats/PngWriter.cpp',
+    'ImageFormats/PngWriter.h',
+    'MultiThreading/SharedMessageQueue.cpp',
+    'MultiThreading/SharedMessageQueue.h',
+    'OrthancException.cpp',
+    'OrthancException.h',
+    'PrecompiledHeaders.cpp',
+    'PrecompiledHeaders.h',
+    'SQLite/Connection.cpp',
+    'SQLite/Connection.h',
+    'SQLite/FunctionContext.cpp',
+    'SQLite/FunctionContext.h',
+    'SQLite/IScalarFunction.h',
+    'SQLite/ITransaction.h',
+    'SQLite/NonCopyable.h',
+    'SQLite/OrthancSQLiteException.h',
+    'SQLite/Statement.cpp',
+    'SQLite/Statement.h',
+    'SQLite/StatementId.cpp',
+    'SQLite/StatementId.h',
+    'SQLite/StatementReference.cpp',
+    'SQLite/StatementReference.h',
+    'SQLite/Transaction.cpp',
+    'SQLite/Transaction.h',
+    'Toolbox.cpp',
+    'Toolbox.h',
+    'Uuid.cpp',
+    'Uuid.h',
+]
+
+for f in FILES:
+    source = os.path.join(SOURCE, f)
+    target = os.path.join(TARGET, f)
+    try:
+        os.makedirs(os.path.dirname(target))
+    except:
+        pass
+
+    shutil.copy(source, target)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Resources/ThirdParty/md5/md5.c	Mon Jun 01 11:52:28 2015 +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	Mon Jun 01 11:52:28 2015 +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 */