changeset 47:1691da4ae9c3

merge patch-azure: 'create container if not exists' option
author Alain Mazy <am@osimis.io>
date Fri, 02 Apr 2021 09:30:52 +0200
parents f10874e13d11 (diff) 3b8fab63313d (current diff)
children ff113c3561c5
files Aws/CMakeLists.txt Azure/AzureBlobStoragePlugin.cpp Azure/CMakeLists.txt Google/CMakeLists.txt
diffstat 25 files changed, 770 insertions(+), 260 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Wed Mar 31 12:10:00 2021 -0400
+++ b/.hgignore	Fri Apr 02 09:30:52 2021 +0200
@@ -1,1 +1,5 @@
+syntax: glob
 CMakeLists.txt.user*
+*~
+*/ThirdPartyDownloads/*
+.vscode/
\ No newline at end of file
--- a/Aws/AwsS3StoragePlugin.cpp	Wed Mar 31 12:10:00 2021 -0400
+++ b/Aws/AwsS3StoragePlugin.cpp	Fri Apr 02 09:30:52 2021 +0200
@@ -1,6 +1,6 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ * Copyright (C) 2020-2021 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -130,7 +130,11 @@
       {
         return objectList[0].GetSize();
       }
-      throw StoragePluginException(std::string("error while reading file ") + path_ + ": multiple objet with same name !");
+      else if (objectList.size() > 1)
+      {
+        throw StoragePluginException(std::string("error while reading file ") + path_ + ": multiple objet with same name !");
+      }
+      throw StoragePluginException(std::string("error while reading file ") + path_ + ": object not found !");
     }
     else
     {
@@ -138,12 +142,29 @@
     }
   }
 
-  virtual void Read(char* data, size_t size)
+  virtual void ReadWhole(char* data, size_t size)
+  {
+    _Read(data, size, 0, false);
+  }
+
+  virtual void ReadRange(char* data, size_t size, size_t fromOffset)
+  {
+    _Read(data, size, fromOffset, true);
+  }
+
+  void _Read(char* data, size_t size, size_t fromOffset, bool useRange)
   {
     Aws::S3::Model::GetObjectRequest getObjectRequest;
     getObjectRequest.SetBucket(bucketName_.c_str());
     getObjectRequest.SetKey(path_.c_str());
 
+    if (useRange)
+    {
+      // https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests
+      std::string range = std::string("bytes=") + boost::lexical_cast<std::string>(fromOffset) + "-" + boost::lexical_cast<std::string>(fromOffset + size -1);
+      getObjectRequest.SetRange(range.c_str());
+    }
+
     getObjectRequest.SetResponseStreamFactory(
           [data, size]()
     {
@@ -171,6 +192,8 @@
 
 
 
+
+
 const char* AwsS3StoragePluginFactory::GetStoragePluginName()
 {
   return "AWS S3 Storage";
@@ -226,7 +249,8 @@
   std::string endpoint = pluginSection.GetStringValue("Endpoint", "");
   unsigned int connectTimeout = pluginSection.GetUnsignedIntegerValue("ConnectTimeout", 30);
   unsigned int requestTimeout = pluginSection.GetUnsignedIntegerValue("RequestTimeout", 1200);
-
+  bool virtualAddressing = pluginSection.GetBooleanValue("VirtualAddressing", true);
+  
   try
   {
     Aws::SDKOptions options;
@@ -238,13 +262,14 @@
     configuration.scheme = Aws::Http::Scheme::HTTPS;
     configuration.connectTimeoutMs = connectTimeout * 1000;
     configuration.requestTimeoutMs  = requestTimeout * 1000;
+    configuration.httpRequestTimeoutMs = requestTimeout * 1000;
 
     if (!endpoint.empty())
     {
       configuration.endpointOverride = endpoint.c_str();
     }
 
-    Aws::S3::S3Client client(credentials, configuration);
+    Aws::S3::S3Client client(credentials, configuration, Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Never, virtualAddressing);
 
     OrthancPlugins::LogInfo("AWS S3 storage initialized");
 
--- a/Aws/AwsS3StoragePlugin.h	Wed Mar 31 12:10:00 2021 -0400
+++ b/Aws/AwsS3StoragePlugin.h	Fri Apr 02 09:30:52 2021 +0200
@@ -1,6 +1,6 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ * Copyright (C) 2020-2021 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
--- a/Aws/CMakeLists.txt	Wed Mar 31 12:10:00 2021 -0400
+++ b/Aws/CMakeLists.txt	Fri Apr 02 09:30:52 2021 +0200
@@ -4,21 +4,28 @@
 
 project(OrthancAwsS3Storage)
 
-set(PLUGIN_VERSION "1.1.0")
+set(PLUGIN_VERSION "mainline")
 
 include(CheckIncludeFileCXX)
 
 set(ORTHANC_FRAMEWORK_SOURCE "hg" CACHE STRING "orthanc source")
-set(ORTHANC_FRAMEWORK_VERSION "1.7.3" CACHE STRING "orthanc framework version")
-set(ALLOW_DOWNLOADS ON)
+set(ORTHANC_FRAMEWORK_VERSION "1.9.1" CACHE STRING "orthanc framework version")
+set(USE_VCPKG_PACKAGES ON CACHE BOOL "Use Microsoft vcpkg to link against crypto++ and AWS SDK")
+set(STATIC_AWS_CLIENT ON CACHE BOOL "Statically link against AWS client library (only if USE_VCPKG_PACKAGES=OFF)")
+set(USE_SYSTEM_CRYPTOPP ON CACHE BOOL "Use the system version of crypto++")
 
 # Download and setup the Orthanc framework
+set(ALLOW_DOWNLOADS ON)
 include(${CMAKE_SOURCE_DIR}/../Common/Resources/DownloadOrthancFramework.cmake)
 
 include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkParameters.cmake)
 
+set(ENABLE_WEB_CLIENT ON)  # Access options related to curl
 set(ENABLE_GOOGLE_TEST ON)
 set(ORTHANC_FRAMEWORK_PLUGIN ON)
+set(ENABLE_MODULE_IMAGES OFF)
+set(ENABLE_MODULE_JOBS OFF)
+set(ENABLE_MODULE_DICOM OFF)
 
 include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkConfiguration.cmake)
 include(${ORTHANC_FRAMEWORK_ROOT}/../../OrthancServer/Plugins/Samples/Common/OrthancPluginsExports.cmake)
@@ -38,10 +45,69 @@
   )
 
 
-find_package(cryptopp CONFIG REQUIRED)
-find_package(AWSSDK REQUIRED COMPONENTS s3)
+if (NOT STATIC_BUILD AND USE_VCPKG_PACKAGES)
+  # Use vcpkg by Microsoft
+  find_package(cryptopp CONFIG REQUIRED)
+  find_package(AWSSDK REQUIRED COMPONENTS s3)
+  set(CRYPTOPP_LIBRARIES cryptopp-static)
+else()
+  include(${CMAKE_SOURCE_DIR}/../Common/CryptoPPConfiguration.cmake)
+  
+  ##
+  ## Building the C++ SDK for Amazon AWS
+  ## WARNING: This is *not* compatible with Ninja (yet)
+  ##
+  if (STATIC_AWS_CLIENT)
+    set(Flags -DBUILD_SHARED_LIBS=OFF)  # Create static library
+  else()
+    set(Flags -DBUILD_SHARED_LIBS=ON)
+  endif()
+
+  include(ExternalProject)
+  externalproject_add(AwsSdkCpp
+    GIT_REPOSITORY https://github.com/aws/aws-sdk-cpp
+    GIT_TAG 1.8.127
+
+    CMAKE_ARGS
+    -DBUILD_ONLY=s3   #-DBUILD_ONLY=s3;transfer
+    -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
+    -DENABLE_TESTING=OFF
+    ${Flags}
 
-include_directories(${WASTORAGE_INCLUDE_DIR})
+    UPDATE_COMMAND ""    # Don't run "cmake" on AWS each time "make/ninja" is run
+    INSTALL_COMMAND ""   # No install
+    )
+
+  ExternalProject_Get_Property(AwsSdkCpp SOURCE_DIR)
+  include_directories(
+    ${SOURCE_DIR}/aws-cpp-sdk-core/include/
+    ${SOURCE_DIR}/aws-cpp-sdk-s3/include/
+    )
+
+  ExternalProject_Get_Property(AwsSdkCpp BINARY_DIR)
+  if (STATIC_AWS_CLIENT)
+    set(AWSSDK_LINK_LIBRARIES
+      ${BINARY_DIR}/aws-cpp-sdk-s3/libaws-cpp-sdk-s3.a
+      ${BINARY_DIR}/aws-cpp-sdk-core/libaws-cpp-sdk-core.a
+      ${BINARY_DIR}/.deps/install/lib/libaws-c-event-stream.a
+      ${BINARY_DIR}/.deps/install/lib/libaws-checksums.a
+      ${BINARY_DIR}/.deps/install/lib/libaws-c-common.a
+      curl
+      crypto
+      )
+    if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+      list(APPEND AWSSDK_LINK_LIBRARIES
+        gcc   # for "undefined reference to `__cpu_model'" on Ubuntu 16.04
+        )
+    endif()
+  else()
+    set(AWSSDK_LINK_LIBRARIES
+      ${BINARY_DIR}/aws-cpp-sdk-core/libaws-cpp-sdk-core.so
+      ${BINARY_DIR}/aws-cpp-sdk-s3/libaws-cpp-sdk-s3.so
+      )
+  endif()
+endif()
+
 
 set(COMMON_SOURCES
     ${CMAKE_SOURCE_DIR}/../Common/IStoragePlugin.h
@@ -54,6 +120,7 @@
     ${ORTHANC_FRAMEWORK_ROOT}/../../OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp
 
     ${ORTHANC_CORE_SOURCES}
+    ${CRYPTOPP_SOURCES}
   )
 
 add_library(OrthancAwsS3Storage SHARED
@@ -62,7 +129,7 @@
     ${CMAKE_SOURCE_DIR}/../Common/StoragePlugin.cpp
 
     ${COMMON_SOURCES}
-  )
+    )
 
 set_target_properties(OrthancAwsS3Storage PROPERTIES
   VERSION ${PLUGIN_VERSION}
@@ -71,7 +138,7 @@
 
 target_link_libraries(OrthancAwsS3Storage
   PRIVATE
-  cryptopp-static
+  ${CRYPTOPP_LIBRARIES}
   ${AWSSDK_LINK_LIBRARIES}
   )
 
@@ -87,7 +154,13 @@
 
 target_link_libraries(UnitTests
   PRIVATE
-  cryptopp-static
   ${GOOGLE_TEST_LIBRARIES}
+  ${CRYPTOPP_LIBRARIES}
   ${AWSSDK_LINK_LIBRARIES}
   )
+
+
+if (STATIC_BUILD OR NOT USE_VCPKG_PACKAGES)
+  add_dependencies(OrthancAwsS3Storage AwsSdkCpp)
+  add_dependencies(UnitTests AwsSdkCpp)
+endif()
--- a/Azure/AzureBlobStoragePlugin.cpp	Wed Mar 31 12:10:00 2021 -0400
+++ b/Azure/AzureBlobStoragePlugin.cpp	Fri Apr 02 09:30:52 2021 +0200
@@ -1,6 +1,6 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ * Copyright (C) 2020-2021 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -73,7 +73,7 @@
     try
     {
       concurrency::streams::istream inputStream = concurrency::streams::rawptr_stream<uint8_t>::open_istream(reinterpret_cast<const uint8_t*>(data), size);
-      azure::storage::cloud_block_blob blob = container_.get_block_blob_reference(path_);
+      azure::storage::cloud_block_blob blob = container_.get_block_blob_reference(utility::conversions::to_string_t(path_));
       blob.upload_from_stream(inputStream);
       inputStream.close().wait();
     }
@@ -100,7 +100,7 @@
   {
     try
     {
-      block_ = container_.get_block_blob_reference(path_);
+      block_ = container_.get_block_blob_reference(utility::conversions::to_string_t(path_));
       block_.download_attributes(); // to retrieve the properties
     }
     catch (std::exception& ex)
@@ -125,7 +125,7 @@
     }
   }
 
-  virtual void Read(char* data, size_t size)
+  virtual void ReadWhole(char* data, size_t size)
   {
     try
     {
@@ -139,6 +139,20 @@
     }
   }
 
+  virtual void ReadRange(char* data, size_t size, size_t fromOffset)
+  {
+    try
+    {
+      concurrency::streams::ostream outputStream = concurrency::streams::rawptr_stream<uint8_t>::open_ostream(reinterpret_cast<uint8_t*>(data), size);
+
+      block_.download_range_to_stream(outputStream, fromOffset, size);
+    }
+    catch (std::exception& ex)
+    {
+      throw StoragePluginException("AzureBlobStorage: error while reading partial file " + std::string(path_) + ": " + ex.what());
+    }
+  }
+
 };
 
 
@@ -246,13 +260,13 @@
   {
     OrthancPlugins::LogInfo("Connecting to Azure storage ...");
 
-    as::cloud_storage_account storageAccount = as::cloud_storage_account::parse(connectionString);
+    as::cloud_storage_account storageAccount = as::cloud_storage_account::parse(utility::conversions::to_string_t(connectionString));
     OrthancPlugins::LogInfo("Storage account created");
 
     as::cloud_blob_client blobClient = storageAccount.create_cloud_blob_client();
     OrthancPlugins::LogInfo("Blob client created");
 
-    as::cloud_blob_container blobContainer = blobClient.get_container_reference(containerName);
+    as::cloud_blob_container blobContainer = blobClient.get_container_reference(utility::conversions::to_string_t(containerName));
     OrthancPlugins::LogInfo("Accessing blob container");
 
     // blobContainer.create_if_not_exists() throws an error if a service SAS (for a blob container)
@@ -308,7 +322,7 @@
 
   try
   {
-    as::cloud_block_blob blockBlob = blobContainer_.get_block_blob_reference(path);
+    as::cloud_block_blob blockBlob = blobContainer_.get_block_blob_reference(utility::conversions::to_string_t(path));
 
     blockBlob.delete_blob();
   }
--- a/Azure/AzureBlobStoragePlugin.h	Wed Mar 31 12:10:00 2021 -0400
+++ b/Azure/AzureBlobStoragePlugin.h	Fri Apr 02 09:30:52 2021 +0200
@@ -1,6 +1,6 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ * Copyright (C) 2020-2021 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
--- a/Azure/CMakeLists.txt	Wed Mar 31 12:10:00 2021 -0400
+++ b/Azure/CMakeLists.txt	Fri Apr 02 09:30:52 2021 +0200
@@ -2,12 +2,12 @@
 
 project(OrthancAzureBlobStorage)
 
-set(PLUGIN_VERSION "1.1.0")
+set(PLUGIN_VERSION "mainline")
 
 include(CheckIncludeFileCXX)
 
 set(ORTHANC_FRAMEWORK_SOURCE "hg" CACHE STRING "orthanc source")
-set(ORTHANC_FRAMEWORK_VERSION "1.7.3" CACHE STRING "orthanc framework version")
+set(ORTHANC_FRAMEWORK_VERSION "1.9.1" CACHE STRING "orthanc framework version")
 set(ALLOW_DOWNLOADS ON)
 
 # Download and setup the Orthanc framework
@@ -17,6 +17,9 @@
 
 set(ENABLE_GOOGLE_TEST ON)
 set(ORTHANC_FRAMEWORK_PLUGIN ON)
+set(ENABLE_MODULE_IMAGES OFF)
+set(ENABLE_MODULE_JOBS OFF)
+set(ENABLE_MODULE_DICOM OFF)
 
 include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkConfiguration.cmake)
 include(${ORTHANC_FRAMEWORK_ROOT}/../../OrthancServer/Plugins/Samples/Common/OrthancPluginsExports.cmake)
@@ -38,14 +41,32 @@
 find_package(cryptopp CONFIG REQUIRED)
 
 # Azure stuff (from https://github.com/Microsoft/vcpkg/issues/6277)
-find_package(cpprestsdk CONFIG REQUIRED)
-find_path(WASTORAGE_INCLUDE_DIR was/blob.h)
-find_library(WASTORAGE_LIBRARY azurestorage)
-find_package(Boost REQUIRED COMPONENTS log)
-find_library(UUID_LIBRARY uuid)
-find_package(LibXml2 REQUIRED)
+if (NOT WIN32)
+  find_package(cpprestsdk CONFIG REQUIRED)
+  find_path(WASTORAGE_INCLUDE_DIR was/blob.h)
+  find_library(WASTORAGE_LIBRARY azurestorage)
+  find_package(Boost REQUIRED COMPONENTS log)
+  find_library(UUID_LIBRARY uuid)
+  find_package(LibXml2 REQUIRED)
+else()  # inspired from https://github.com/phongcao/azure-storage-cpp-sas-sample/blob/master/CMakeLists.txt
+  find_path(WASTORAGE_INCLUDE_DIR was/blob.h)
+  find_library(CPPREST_LIBRARY
+    NAMES cpprest cpprest_2_10)
+  find_library(WASTORAGE_LIBRARY wastorage)
 
-include_directories(${WASTORAGE_INCLUDE_DIR})
+  set (CMAKE_CXX_STANDARD 11)
+  set (CMAKE_CXX_STANDARD_REQUIRED ON)
+  set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHa")
+  set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi")
+  set (CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /DEBUG /OPT:REF /OPT:ICF")
+
+  add_definitions(-D_NO_WASTORAGE_API=1)   # from https://github.com/Azure/azure-storage-cpp/issues/263
+endif()
+
+
+if (NOT WIN32)
+  include_directories(${WASTORAGE_INCLUDE_DIR})
+endif()
 
 set(COMMON_SOURCES
     ${CMAKE_SOURCE_DIR}/../Common/IStoragePlugin.h
@@ -73,25 +94,47 @@
   SOVERSION ${PLUGIN_VERSION}
   )
 
-target_link_libraries(OrthancAzureBlobStorage
-  PRIVATE
-  cryptopp-static
-  ${WASTORAGE_LIBRARY} ${UUID_LIBRARY} ${Boost_LIBRARIES} ${LIBXML2_LIBRARIES} cpprestsdk::cpprest
+if (NOT WIN32)
+
+  target_link_libraries(OrthancAzureBlobStorage
+    PRIVATE
+    cryptopp-static
+    ${WASTORAGE_LIBRARY} 
+    ${UUID_LIBRARY} 
+    ${Boost_LIBRARIES} 
+    ${LIBXML2_LIBRARIES} 
+    cpprestsdk::cpprest
+    )
+else()
+  target_link_libraries(OrthancAzureBlobStorage
+    PRIVATE
+    cryptopp-static
+    ${OPENSSL_LIBRARY}
+    ${WASTORAGE_LIBRARY} 
+    ${CPPREST_LIBRARY} 
+    Winhttp.lib
+    Crypt32.lib
+    xmllite.lib
   )
 
+endif()
 
 
-add_executable(UnitTests
-    ${GOOGLE_TEST_SOURCES}
-    ${COMMON_SOURCES}
+# add_executable(UnitTests
+#     ${GOOGLE_TEST_SOURCES}
+#     ${COMMON_SOURCES}
+
+#     ${CMAKE_SOURCE_DIR}/../UnitTestsSources/EncryptionTests.cpp
+#     ${CMAKE_SOURCE_DIR}/../UnitTestsSources/UnitTestsMain.cpp
+#     )
 
-    ${CMAKE_SOURCE_DIR}/../UnitTestsSources/EncryptionTests.cpp
-    ${CMAKE_SOURCE_DIR}/../UnitTestsSources/UnitTestsMain.cpp
-    )
-
-target_link_libraries(UnitTests
-  PRIVATE
-  cryptopp-static
-  ${GOOGLE_TEST_LIBRARIES}
-  ${WASTORAGE_LIBRARY} ${UUID_LIBRARY} ${Boost_LIBRARIES} ${LIBXML2_LIBRARIES} cpprestsdk::cpprest
-  )
+# target_link_libraries(UnitTests
+#   PRIVATE
+#   cryptopp-static
+#   ${GOOGLE_TEST_LIBRARIES}
+#   ${WASTORAGE_LIBRARY} 
+#   # ${UUID_LIBRARY} 
+#   # ${Boost_LIBRARIES} 
+#   # ${LIBXML2_LIBRARIES} 
+#   cpprestsdk::cpprest
+#   )
--- a/Common/BaseStoragePlugin.cpp	Wed Mar 31 12:10:00 2021 -0400
+++ b/Common/BaseStoragePlugin.cpp	Fri Apr 02 09:30:52 2021 +0200
@@ -34,6 +34,10 @@
     {
       filename += ".json";
     }
+    else if (type == 3) // TODO once using OrthancFramework 1.9.1+: use OrthancPluginContentType_DicomUntilPixelData
+    {
+      filename += "dcm.head";
+    }
     else
     {
       filename += ".unk";
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Common/CryptoPPConfiguration.cmake	Fri Apr 02 09:30:52 2021 +0200
@@ -0,0 +1,254 @@
+# Cloud storage plugins for Orthanc
+# Copyright (C) 2020-2021 Osimis S.A., Belgium
+#
+# This program is free software: you can redistribute it and/or
+# modify it under the terms of the GNU Affero General Public License
+# as published by the Free Software Foundation, either version 3 of
+# the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_CRYPTOPP)
+  # The .tar.gz package was created by "./CryptoPPPackage.sh"
+  SET(CRYPTOPP_SOURCES_DIR ${CMAKE_BINARY_DIR}/cryptopp-840)
+  SET(CRYPTOPP_URL "http://orthanc.osimis.io/ThirdPartyDownloads/cryptopp-840.tar.gz")
+  SET(CRYPTOPP_MD5 "d42363e8a12c06a000720335a4da70d3")
+
+  DownloadPackage(${CRYPTOPP_MD5} ${CRYPTOPP_URL} "${CRYPTOPP_SOURCES_DIR}")
+
+  include_directories(
+    ${CRYPTOPP_SOURCES_DIR}
+    )
+
+  # TODO - Consider adding SIMD support
+  add_definitions(
+    -DCRYPTOPP_DISABLE_ASM
+    )
+
+  set(CRYPTOPP_SOURCES
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/aria_simd.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/bench1.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/bench2.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/bench3.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/blake2b_simd.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/blake2s_simd.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/chacha_avx.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/chacha_simd.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/cham_simd.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/crc_simd.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/datatest.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/dlltest.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/fipsalgt.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/fipstest.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/gcm_simd.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/gf2n_simd.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/keccak_simd.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/lea_simd.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/neon_simd.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/ppc_simd.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/regtest1.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/regtest2.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/regtest3.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/regtest4.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/rijndael_simd.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/sha_simd.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/shacal2_simd.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/simon128_simd.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/sm4_simd.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/speck128_simd.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/sse_simd.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/test.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/validat0.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/validat1.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/validat10.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/validat2.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/validat3.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/validat4.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/validat5.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/validat6.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/validat7.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/validat8.cpp
+    # ${CRYPTOPP_SOURCES_DIR}/cryptopp/validat9.cpp
+
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/3way.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/adler32.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/algebra.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/algparam.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/allocate.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/arc4.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/aria.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/ariatab.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/asn.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/authenc.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/base32.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/base64.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/basecode.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/bfinit.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/blake2.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/blowfish.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/blumshub.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/camellia.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/cast.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/casts.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/cbcmac.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/ccm.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/chacha.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/chachapoly.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/cham.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/channels.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/cmac.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/cpu.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/crc.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/cryptlib.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/darn.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/default.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/des.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/dessp.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/dh.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/dh2.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/dll.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/donna_32.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/donna_64.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/donna_sse.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/dsa.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/eax.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/ec2n.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/eccrypto.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/ecp.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/elgamal.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/emsa2.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/eprecomp.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/esign.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/files.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/filters.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/fips140.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/gcm.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/gf256.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/gf2_32.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/gf2n.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/gfpcrypt.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/gost.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/gzip.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/hc128.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/hc256.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/hex.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/hight.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/hmac.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/hrtimer.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/ida.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/idea.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/integer.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/iterhash.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/kalyna.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/kalynatab.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/keccak.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/keccak_core.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/lea.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/luc.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/mars.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/marss.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/md2.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/md4.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/md5.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/misc.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/modes.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/mqueue.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/mqv.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/nbtheory.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/oaep.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/osrng.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/padlkrng.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/panama.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/pch.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/pkcspad.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/poly1305.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/polynomi.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/ppc_power7.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/ppc_power8.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/ppc_power9.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/pssr.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/pubkey.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/queue.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/rabbit.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/rabin.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/randpool.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/rc2.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/rc5.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/rc6.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/rdrand.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/rdtables.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/rijndael.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/ripemd.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/rng.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/rsa.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/rw.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/safer.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/salsa.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/scrypt.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/seal.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/seed.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/serpent.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/sha.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/sha3.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/shacal2.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/shake.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/shark.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/sharkbox.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/simeck.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/simon.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/simple.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/skipjack.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/sm3.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/sm4.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/sosemanuk.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/speck.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/square.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/squaretb.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/strciphr.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/tea.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/tftables.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/threefish.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/tiger.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/tigertab.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/ttmac.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/tweetnacl.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/twofish.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/vmac.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/wake.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/whrlpool.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/xed25519.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/xtr.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/xtrcrypt.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/xts.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/zdeflate.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/zinflate.cpp
+    ${CRYPTOPP_SOURCES_DIR}/cryptopp/zlib.cpp
+    )
+
+  source_group(ThirdParty\\cryptopp REGULAR_EXPRESSION ${CRYPTOPP_SOURCES_DIR}/.*)
+
+else()
+  ##
+  ## Inclusion of system-wide crypto++
+  ##
+  check_include_file_cxx(cryptopp/cryptlib.h HAVE_CRYPTOPP_H)
+  if (NOT HAVE_CRYPTOPP_H)
+    message(FATAL_ERROR "Please install the libcrypto++-dev package")
+  endif()
+
+  include(CheckCXXSymbolExists)
+  set(CMAKE_REQUIRED_LIBRARIES cryptopp)
+  check_cxx_symbol_exists("CryptoPP::SHA1::InitState" cryptopp/sha.h HAVE_LIBCRYPTOPP)
+  if (NOT HAVE_LIBCRYPTOPP)
+    message(FATAL_ERROR "Unable to find the cryptopp library")
+  endif()
+
+  set(CRYPTOPP_LIBRARIES cryptopp)
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Common/CryptoPPPackage.sh	Fri Apr 02 09:30:52 2021 +0200
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+set -ex
+
+VERSION=840
+
+TARGET=/tmp/cryptopp-${VERSION}
+rm -rf ${TARGET}
+mkdir -p ${TARGET}/cryptopp
+cd ${TARGET}/cryptopp
+wget https://www.cryptopp.com/cryptopp${VERSION}.zip
+unzip ./cryptopp${VERSION}.zip
+rm ./cryptopp${VERSION}.zip
+rm -rf ./TestData
+rm -rf ./TestPrograms
+rm -rf ./TestVectors
+cd /tmp
+tar cvfz cryptopp-${VERSION}.tar.gz cryptopp-${VERSION}
+
+md5sum /tmp/cryptopp-${VERSION}.tar.gz
--- a/Common/EncryptionConfigurator.cpp	Wed Mar 31 12:10:00 2021 -0400
+++ b/Common/EncryptionConfigurator.cpp	Fri Apr 02 09:30:52 2021 +0200
@@ -1,6 +1,6 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ * Copyright (C) 2020-2021 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
--- a/Common/EncryptionConfigurator.h	Wed Mar 31 12:10:00 2021 -0400
+++ b/Common/EncryptionConfigurator.h	Fri Apr 02 09:30:52 2021 +0200
@@ -1,6 +1,6 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ * Copyright (C) 2020-2021 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
--- a/Common/EncryptionHelpers.cpp	Wed Mar 31 12:10:00 2021 -0400
+++ b/Common/EncryptionHelpers.cpp	Fri Apr 02 09:30:52 2021 +0200
@@ -1,6 +1,6 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ * Copyright (C) 2020-2021 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -21,17 +21,19 @@
 
 #include <boost/lexical_cast.hpp>
 #include <iostream>
-#include "cryptopp/cryptlib.h"
-#include "cryptopp/modes.h"
-#include "cryptopp/hex.h"
-#include "cryptopp/gcm.h"
-#include "cryptopp/files.h"
+
+#include <cryptopp/cryptlib.h>
+#include <cryptopp/modes.h>
+#include <cryptopp/hex.h>
+#include <cryptopp/gcm.h>
+#include <cryptopp/files.h>
+#include <cryptopp/filters.h>
 
 const std::string EncryptionHelpers::HEADER_VERSION = "A1";
 
 using namespace  CryptoPP;
 
-std::string EncryptionHelpers::ToHexString(const byte* block, size_t size)
+std::string EncryptionHelpers::ToHexString(const void* block, size_t size)
 {
   std::string blockAsString = std::string(reinterpret_cast<const char*>(block), size);
 
@@ -275,7 +277,7 @@
   try
   {
     GCM<AES>::Encryption e;
-    e.SetKeyWithIV(dataKey, dataKey.size(), iv, sizeof(iv));
+    e.SetKeyWithIV(dataKey, dataKey.size(), iv, iv.size());
 
     // the output text starts with the unencrypted prefix
     output = prefix;
@@ -323,7 +325,7 @@
 //  std::cout << ToHexString(iv) << std::endl;
 
   GCM<AES>::Decryption d;
-  d.SetKeyWithIV(dataKey, sizeof(dataKey), iv, sizeof(iv));
+  d.SetKeyWithIV(dataKey, dataKey.size(), iv, iv.size());
 
   try {
     AuthenticatedDecryptionFilter df(d, NULL,
--- a/Common/EncryptionHelpers.h	Wed Mar 31 12:10:00 2021 -0400
+++ b/Common/EncryptionHelpers.h	Fri Apr 02 09:30:52 2021 +0200
@@ -1,6 +1,6 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ * Copyright (C) 2020-2021 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -19,7 +19,7 @@
 
 #include <memory.h>
 #include <cryptopp/secblock.h>
-#include "cryptopp/osrng.h"
+#include <cryptopp/osrng.h>
 #include <boost/thread/mutex.hpp>
 #include <MultiThreading/Semaphore.h>
 
@@ -100,7 +100,7 @@
 
 public:
 
-  static std::string ToHexString(const CryptoPP::byte* block, size_t size);
+  static std::string ToHexString(const void* block, size_t size);
   static std::string ToHexString(const std::string& block);
   static std::string ToHexString(const CryptoPP::SecByteBlock& block);
   static std::string ToString(const CryptoPP::SecByteBlock& block);
--- a/Common/IStoragePlugin.h	Wed Mar 31 12:10:00 2021 -0400
+++ b/Common/IStoragePlugin.h	Fri Apr 02 09:30:52 2021 +0200
@@ -1,6 +1,6 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ * Copyright (C) 2020-2021 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -62,7 +62,8 @@
 
     virtual ~IReader() {}
     virtual size_t GetSize() = 0;
-    virtual void Read(char* data, size_t size) = 0;
+    virtual void ReadWhole(char* data, size_t size) = 0;
+    virtual void ReadRange(char* data, size_t size, size_t fromOffset) = 0;
   };
 
 public:
--- a/Common/Resources/DownloadOrthancFramework.cmake	Wed Mar 31 12:10:00 2021 -0400
+++ b/Common/Resources/DownloadOrthancFramework.cmake	Fri Apr 02 09:30:52 2021 +0200
@@ -1,7 +1,7 @@
 # Orthanc - A Lightweight, RESTful DICOM Store
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
-# Copyright (C) 2017-2020 Osimis S.A., Belgium
+# Copyright (C) 2017-2021 Osimis S.A., Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public License
@@ -114,6 +114,18 @@
         set(ORTHANC_FRAMEWORK_MD5 "328f94dcbd78c169655a13f7ad58a2c2")
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.7.3")
         set(ORTHANC_FRAMEWORK_MD5 "3f1ba9502ec7c5449971d3b56087bcde")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.7.4")
+        set(ORTHANC_FRAMEWORK_MD5 "19fcb7c21876af86546baa048a22c6c0")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.8.0")
+        set(ORTHANC_FRAMEWORK_MD5 "f8ec7554ef5d23ea4ce474b1e8214de9")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.8.1")
+        set(ORTHANC_FRAMEWORK_MD5 "db094f96399cbe8b9bbdbce34884c220")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.8.2")
+        set(ORTHANC_FRAMEWORK_MD5 "8bfa10e66c9931e74111be0bfb1f4548")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.9.0")
+        set(ORTHANC_FRAMEWORK_MD5 "cea0b02ce184671eaf1bd668beefbf28")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.9.1")
+        set(ORTHANC_FRAMEWORK_MD5 "08eebc66ef93c3b40115c38501db5fbd")
 
       # Below this point are development snapshots that were used to
       # release some plugin, before an official release of the Orthanc
@@ -125,6 +137,15 @@
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "ae0e3fd609df")
         # DICOMweb 1.1 (framework pre-1.6.0)
         set(ORTHANC_FRAMEWORK_MD5 "7e09e9b530a2f527854f0b782d7e0645")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "82652c5fc04f")
+        # Stone Web viewer 1.0 (framework pre-1.8.1)
+        set(ORTHANC_FRAMEWORK_MD5 "d77331d68917e66a3f4f9b807bbdab7f")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "4a3ba4bf4ba7")
+        # PostgreSQL 3.3 (framework pre-1.8.2)
+        set(ORTHANC_FRAMEWORK_MD5 "2d82bddf06f9cfe82095495cb3b8abde")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "23ad1b9c7800")
+        # For "Toolbox::ReadJson()" and "Toolbox::Write{...}Json()" (pre-1.9.0)
+        set(ORTHANC_FRAMEWORK_MD5 "9af92080e57c60dd288eba46ce606c00")
       endif()
     endif()
   endif()
@@ -409,6 +430,8 @@
 
 if (ORTHANC_FRAMEWORK_SOURCE STREQUAL "system")
   set(ORTHANC_FRAMEWORK_LIBDIR "" CACHE PATH "")
+  set(ORTHANC_FRAMEWORK_USE_SHARED ON CACHE BOOL "Whether to use the shared library or the static library")
+  set(ORTHANC_FRAMEWORK_ADDITIONAL_LIBRARIES "" CACHE STRING "Additional libraries to link against, separated by whitespaces, typically needed if using the static library (a common minimal value is \"boost_filesystem boost_iostreams boost_locale boost_regex boost_thread jsoncpp pugixml uuid\")")
 
   if (CMAKE_SYSTEM_NAME STREQUAL "Windows" AND
       CMAKE_COMPILER_IS_GNUCXX) # MinGW
@@ -425,103 +448,75 @@
   include(${CMAKE_CURRENT_LIST_DIR}/AutoGeneratedCode.cmake)
   set(EMBED_RESOURCES_PYTHON ${CMAKE_CURRENT_LIST_DIR}/EmbedResources.py)
 
-  if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows" OR
-      ORTHANC_FRAMEWORK_STATIC)
-    include_directories(${ORTHANC_FRAMEWORK_ROOT}/..)
+  if (ORTHANC_FRAMEWORK_USE_SHARED)
+    list(GET CMAKE_FIND_LIBRARY_PREFIXES 0 Prefix)
+    list(GET CMAKE_FIND_LIBRARY_SUFFIXES 0 Suffix)
   else()
-    # Look for mandatory dependency JsonCpp (cf. JsonCppConfiguration.cmake)
+    list(GET CMAKE_FIND_LIBRARY_PREFIXES 0 Prefix)
+    list(GET CMAKE_FIND_LIBRARY_SUFFIXES 1 Suffix)
+  endif()
+
+  # The "OrthancFramework" library must be the first one to be included
+  if ("${ORTHANC_FRAMEWORK_LIBDIR}" STREQUAL "")
+    set(ORTHANC_FRAMEWORK_LIBRARIES ${Prefix}OrthancFramework${Suffix})
+  else ()
+    set(ORTHANC_FRAMEWORK_LIBRARIES ${ORTHANC_FRAMEWORK_LIBDIR}/${Prefix}OrthancFramework${Suffix})
+  endif()
+
+  if (NOT ORTHANC_FRAMEWORK_ADDITIONAL_LIBRARIES STREQUAL "")
+    # https://stackoverflow.com/a/5272993/881731
+    string(REPLACE " " ";" tmp ${ORTHANC_FRAMEWORK_ADDITIONAL_LIBRARIES})
+    list(APPEND ORTHANC_FRAMEWORK_LIBRARIES ${tmp})
+  endif()
+
+  # Look for the version of the mandatory dependency JsonCpp (cf. JsonCppConfiguration.cmake)
+  if (CMAKE_CROSSCOMPILING)
+    set(JSONCPP_INCLUDE_DIR ${ORTHANC_FRAMEWORK_ROOT}/..)
+  else()
     find_path(JSONCPP_INCLUDE_DIR json/reader.h
+      ${ORTHANC_FRAMEWORK_ROOT}/..
       /usr/include/jsoncpp
       /usr/local/include/jsoncpp
       )
+  endif()
 
-    message("JsonCpp include dir: ${JSONCPP_INCLUDE_DIR}")
-    include_directories(${JSONCPP_INCLUDE_DIR})
-    link_libraries(jsoncpp)
+  message("JsonCpp include dir: ${JSONCPP_INCLUDE_DIR}")
+  include_directories(${JSONCPP_INCLUDE_DIR})
 
-    CHECK_INCLUDE_FILE_CXX(${JSONCPP_INCLUDE_DIR}/json/reader.h HAVE_JSONCPP_H)
-    if (NOT HAVE_JSONCPP_H)
-      message(FATAL_ERROR "Please install the libjsoncpp-dev package")
-    endif()
-
-    # Switch to the C++11 standard if the version of JsonCpp is 1.y.z
-    # (same as variable JSONCPP_CXX11 in the source code of Orthanc)
-    if (EXISTS ${JSONCPP_INCLUDE_DIR}/json/version.h)
-      file(STRINGS
-        "${JSONCPP_INCLUDE_DIR}/json/version.h" 
-        JSONCPP_VERSION_MAJOR1 REGEX
-        ".*define JSONCPP_VERSION_MAJOR.*")
+  CHECK_INCLUDE_FILE_CXX(${JSONCPP_INCLUDE_DIR}/json/reader.h HAVE_JSONCPP_H)
+  if (NOT HAVE_JSONCPP_H)
+    message(FATAL_ERROR "Please install the libjsoncpp-dev package")
+  endif()
 
-      if (NOT JSONCPP_VERSION_MAJOR1)
-        message(FATAL_ERROR "Unable to extract the major version of JsonCpp")
-      endif()
-      
-      string(REGEX REPLACE
-        ".*JSONCPP_VERSION_MAJOR.*([0-9]+)$" "\\1" 
-        JSONCPP_VERSION_MAJOR ${JSONCPP_VERSION_MAJOR1})
-      message("JsonCpp major version: ${JSONCPP_VERSION_MAJOR}")
+  # Switch to the C++11 standard if the version of JsonCpp is 1.y.z
+  # (same as variable JSONCPP_CXX11 in the source code of Orthanc)
+  if (EXISTS ${JSONCPP_INCLUDE_DIR}/json/version.h)
+    file(STRINGS
+      "${JSONCPP_INCLUDE_DIR}/json/version.h" 
+      JSONCPP_VERSION_MAJOR1 REGEX
+      ".*define JSONCPP_VERSION_MAJOR.*")
 
-      if (JSONCPP_VERSION_MAJOR GREATER 0)
-        message("Switching to C++11 standard, as version of JsonCpp is >= 1.0.0")
-        if (CMAKE_COMPILER_IS_GNUCXX)
-          set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
-        elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
-          set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
-        endif()
-      endif()
-    else()
-      message("Unable to detect the major version of JsonCpp, assuming < 1.0.0")
-    endif()
-
-    # Look for mandatory dependency Boost (cf. BoostConfiguration.cmake)
-    include(FindBoost)
-    find_package(Boost COMPONENTS filesystem thread system date_time regex ${ORTHANC_BOOST_COMPONENTS})
-
-    if (NOT Boost_FOUND)
-      message(FATAL_ERROR "Unable to locate Boost on this system")
+    if (NOT JSONCPP_VERSION_MAJOR1)
+      message(FATAL_ERROR "Unable to extract the major version of JsonCpp")
     endif()
     
-    include_directories(${Boost_INCLUDE_DIRS})
-    link_libraries(${Boost_LIBRARIES})
-
-    # Optional component - Lua
-    if (ENABLE_LUA)
-      include(FindLua)
+    string(REGEX REPLACE
+      ".*JSONCPP_VERSION_MAJOR.*([0-9]+)$" "\\1" 
+      JSONCPP_VERSION_MAJOR ${JSONCPP_VERSION_MAJOR1})
+    message("JsonCpp major version: ${JSONCPP_VERSION_MAJOR}")
 
-      if (NOT LUA_FOUND)
-        message(FATAL_ERROR "Please install the liblua-dev package")
-      endif()
-
-      include_directories(${LUA_INCLUDE_DIR})
-      link_libraries(${LUA_LIBRARIES})
-    endif()
-
-    # Optional component - SQLite
-    if (ENABLE_SQLITE)    
-      CHECK_INCLUDE_FILE(sqlite3.h HAVE_SQLITE_H)
-      if (NOT HAVE_SQLITE_H)
-        message(FATAL_ERROR "Please install the libsqlite3-dev package")
+    if (JSONCPP_VERSION_MAJOR GREATER 0)
+      message("Switching to C++11 standard, as version of JsonCpp is >= 1.0.0")
+      if (CMAKE_COMPILER_IS_GNUCXX)
+        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
+      elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
+        set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
       endif()
-      link_libraries(sqlite3)
     endif()
-
-    # Optional component - Pugixml
-    if (ENABLE_PUGIXML)
-      CHECK_INCLUDE_FILE_CXX(pugixml.hpp HAVE_PUGIXML_H)
-      if (NOT HAVE_PUGIXML_H)
-        message(FATAL_ERROR "Please install the libpugixml-dev package")
-      endif()      
-      link_libraries(pugixml)
-    endif()
-
-    # Optional component - DCMTK
-    if (ENABLE_DCMTK)
-      include(FindDCMTK)
-      include_directories(${DCMTK_INCLUDE_DIRS})
-      link_libraries(${DCMTK_LIBRARIES})
-    endif()
+  else()
+    message("Unable to detect the major version of JsonCpp, assuming < 1.0.0")
   endif()
-
+  
   # Look for Orthanc framework shared library
   include(CheckCXXSymbolExists)
 
@@ -541,28 +536,17 @@
   
   message("Orthanc framework include dir: ${ORTHANC_FRAMEWORK_INCLUDE_DIR}")
   include_directories(${ORTHANC_FRAMEWORK_INCLUDE_DIR})
-  
-  if ("${ORTHANC_FRAMEWORK_LIBDIR}" STREQUAL "")
-    set(ORTHANC_FRAMEWORK_LIBRARIES OrthancFramework)
-  else()
-    if (MSVC)
-      set(Suffix ".lib")
-      set(Prefix "")
-    else()
-      list(GET CMAKE_FIND_LIBRARY_PREFIXES 0 Prefix)
-      list(GET CMAKE_FIND_LIBRARY_SUFFIXES 0 Suffix)
+
+  if (ORTHANC_FRAMEWORK_USE_SHARED)
+    set(CMAKE_REQUIRED_INCLUDES "${ORTHANC_FRAMEWORK_INCLUDE_DIR}")
+    set(CMAKE_REQUIRED_LIBRARIES "${ORTHANC_FRAMEWORK_LIBRARIES}")
+    
+    check_cxx_symbol_exists("Orthanc::InitializeFramework" "OrthancFramework.h" HAVE_ORTHANC_FRAMEWORK)
+    if (NOT HAVE_ORTHANC_FRAMEWORK)
+      message(FATAL_ERROR "Cannot find the Orthanc framework")
     endif()
-    set(ORTHANC_FRAMEWORK_LIBRARIES ${ORTHANC_FRAMEWORK_LIBDIR}/${Prefix}OrthancFramework${Suffix})
+    
+    unset(CMAKE_REQUIRED_INCLUDES)
+    unset(CMAKE_REQUIRED_LIBRARIES)
   endif()
-
-  set(CMAKE_REQUIRED_INCLUDES "${ORTHANC_FRAMEWORK_INCLUDE_DIR}")
-  set(CMAKE_REQUIRED_LIBRARIES "${ORTHANC_FRAMEWORK_LIBRARIES}")
-  
-  check_cxx_symbol_exists("Orthanc::InitializeFramework" "OrthancFramework.h" HAVE_ORTHANC_FRAMEWORK)
-  if (NOT HAVE_ORTHANC_FRAMEWORK)
-    message(FATAL_ERROR "Cannot find the Orthanc framework")
-  endif()
-
-  unset(CMAKE_REQUIRED_INCLUDES)
-  unset(CMAKE_REQUIRED_LIBRARIES)
 endif()
--- a/Common/StoragePlugin.cpp	Wed Mar 31 12:10:00 2021 -0400
+++ b/Common/StoragePlugin.cpp	Fri Apr 02 09:30:52 2021 +0200
@@ -1,6 +1,6 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ * Copyright (C) 2020-2021 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -39,6 +39,8 @@
 
 #include "../Common/EncryptionHelpers.h"
 #include "../Common/EncryptionConfigurator.h"
+
+#include <Logging.h>
 #include <SystemToolbox.h>
 
 static std::unique_ptr<IStoragePlugin> plugin;
@@ -49,29 +51,6 @@
 static bool migrationFromFileSystemEnabled = false;
 static std::string objectsRootPath;
 
-// class to free memory allocated by malloc if an exception occurs
-// This is to avoid an issue in which the blob storage read method
-// crashed if the buffer was allocated through:
-//   auto buffer = std::unique_ptr<void, void(*)(void*)>{malloc(static_cast<uint64_t>(*size)), free};
-
-class ScopedFree
-{
-  void* buffer_;
-public:
-  ScopedFree(void* buffer)
-  : buffer_(buffer)
-  {
-  }
-  ~ScopedFree()
-  {
-    free(buffer_);
-  }
-
-  void Release() // abandon ownership
-  {
-    buffer_ = nullptr;
-  }
-};
 
 static OrthancPluginErrorCode StorageCreate(const char* uuid,
                                             const void* content,
@@ -113,49 +92,85 @@
 }
 
 
-static OrthancPluginErrorCode StorageRead(void** content,
-                                          int64_t* size,
-                                          const char* uuid,
-                                          OrthancPluginContentType type)
+static OrthancPluginErrorCode StorageReadRange(OrthancPluginMemoryBuffer64* target, // Memory buffer where to store the content of the range.  The memory buffer is allocated and freed by Orthanc. The length of the range of interest corresponds to the size of this buffer.
+                                               const char* uuid,
+                                               OrthancPluginContentType type,
+                                               uint64_t rangeStart)
+{
+  assert(!cryptoEnabled);
+
+  try
+  {
+    std::unique_ptr<IStoragePlugin::IReader> reader(plugin->GetReaderForObject(uuid, type, cryptoEnabled));
+    reader->ReadRange(reinterpret_cast<char*>(target->data), target->size, rangeStart);
+  }
+  catch (StoragePluginException& ex)
+  {
+    if (migrationFromFileSystemEnabled)
+    {
+      try
+      {
+        OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + ": error while reading object " + std::string(uuid) + ": " + ex.what() + ", will now try to read it from legacy orthanc storage");
+        std::string path = BaseStoragePlugin::GetOrthancFileSystemPath(uuid, fileSystemRootPath);
+
+        std::string stringBuffer;
+        Orthanc::SystemToolbox::ReadFileRange(stringBuffer, path, rangeStart, rangeStart + target->size, true);
+
+        memcpy(target->data, stringBuffer.data(), stringBuffer.size());
+
+        return OrthancPluginErrorCode_Success;
+      }
+      catch(Orthanc::OrthancException& e)
+      {
+        OrthancPlugins::LogError(std::string(StoragePluginFactory::GetStoragePluginName()) + ": error while reading object " + std::string(uuid) + ": " + std::string(e.What()));
+        return OrthancPluginErrorCode_StorageAreaPlugin;
+      }
+    }
+  }
+  return OrthancPluginErrorCode_Success;
+}
+
+
+static OrthancPluginErrorCode StorageReadWhole(OrthancPluginMemoryBuffer64* target, // Memory buffer where to store the content of the file. It must be allocated by the plugin using OrthancPluginCreateMemoryBuffer64(). The core of Orthanc will free it.
+                                               const char* uuid,
+                                               OrthancPluginContentType type)
 {
   try
   {
     std::unique_ptr<IStoragePlugin::IReader> reader(plugin->GetReaderForObject(uuid, type, cryptoEnabled));
 
     size_t fileSize = reader->GetSize();
+    size_t size;
 
     if (cryptoEnabled)
     {
-      *size = fileSize - crypto->OVERHEAD_SIZE;
+      size = fileSize - crypto->OVERHEAD_SIZE;
     }
     else
     {
-      *size = fileSize;
+      size = fileSize;
     }
 
-    if (*size <= 0)
+    if (size <= 0)
     {
       OrthancPlugins::LogError(std::string(StoragePluginFactory::GetStoragePluginName()) + ": error while reading object " + std::string(uuid) + ", size of file is too small: " + boost::lexical_cast<std::string>(fileSize) + " bytes");
       return OrthancPluginErrorCode_StorageAreaPlugin;
     }
 
-    *content = malloc(static_cast<uint64_t>(*size));
-    ScopedFree freeContent(*content);
-
-    if (*content == nullptr)
+    if (OrthancPluginCreateMemoryBuffer64(OrthancPlugins::GetGlobalContext(), target, size) != OrthancPluginErrorCode_Success)
     {
-      OrthancPlugins::LogError(std::string(StoragePluginFactory::GetStoragePluginName()) + ": error while reading object " + std::string(uuid) + ", cannot allocate memory of size " + boost::lexical_cast<std::string>(*size) + " bytes");
+      OrthancPlugins::LogError(std::string(StoragePluginFactory::GetStoragePluginName()) + ": error while reading object " + std::string(uuid) + ", cannot allocate memory of size " + boost::lexical_cast<std::string>(size) + " bytes");
       return OrthancPluginErrorCode_StorageAreaPlugin;
     }
 
     if (cryptoEnabled)
     {
       std::vector<char> encrypted(fileSize);
-      reader->Read(encrypted.data(), fileSize);
+      reader->ReadWhole(encrypted.data(), fileSize);
 
       try
       {
-        crypto->Decrypt((char*)(*content), encrypted.data(), fileSize);
+        crypto->Decrypt(reinterpret_cast<char*>(target->data), encrypted.data(), fileSize);
       }
       catch (EncryptionException& ex)
       {
@@ -165,11 +180,8 @@
     }
     else
     {
-      reader->Read(*(reinterpret_cast<char**>(content)), fileSize);
+      reader->ReadWhole(reinterpret_cast<char*>(target->data), fileSize);
     }
-
-    // transmit ownership to content
-    freeContent.Release();
   }
   catch (StoragePluginException& ex)
   {
@@ -183,19 +195,13 @@
         std::string stringBuffer;
         Orthanc::SystemToolbox::ReadFile(stringBuffer, path);
 
-        *content = malloc(static_cast<uint64_t>(stringBuffer.size()));
-        ScopedFree freeContent(*content);
-
-        if (*content == nullptr)
+        if (OrthancPluginCreateMemoryBuffer64(OrthancPlugins::GetGlobalContext(), target, stringBuffer.size()) != OrthancPluginErrorCode_Success)
         {
-          OrthancPlugins::LogError(std::string(StoragePluginFactory::GetStoragePluginName()) + ": error while reading object " + std::string(uuid) + ", cannot allocate memory of size " + boost::lexical_cast<std::string>(*size) + " bytes");
+          OrthancPlugins::LogError(std::string(StoragePluginFactory::GetStoragePluginName()) + ": error while reading object " + std::string(uuid) + ", cannot allocate memory of size " + boost::lexical_cast<std::string>(stringBuffer.size()) + " bytes");
           return OrthancPluginErrorCode_StorageAreaPlugin;
         }
-        *size = stringBuffer.size();
-        memcpy(*content, stringBuffer.data(), stringBuffer.size());
 
-        // transmit ownership to content
-        freeContent.Release();
+        memcpy(target->data, stringBuffer.data(), stringBuffer.size());
 
         return OrthancPluginErrorCode_Success;
       }
@@ -215,6 +221,23 @@
   return OrthancPluginErrorCode_Success;
 }
 
+static OrthancPluginErrorCode StorageReadWholeLegacy(void** content,
+                                                     int64_t* size,
+                                                     const char* uuid,
+                                                     OrthancPluginContentType type)
+{
+  OrthancPluginMemoryBuffer64 buffer;
+  OrthancPluginErrorCode result = StorageReadWhole(&buffer, uuid, type); // will allocate OrthancPluginMemoryBuffer64
+
+  if (result == OrthancPluginErrorCode_Success)
+  {
+    *size = buffer.size;
+    *content = buffer.data; // orthanc will free the buffer (we don't have to delete it ourselves)
+  }
+
+  return result;
+}
+
 
 static OrthancPluginErrorCode StorageRemove(const char* uuid,
                                             OrthancPluginContentType type)
@@ -301,52 +324,75 @@
       return -1;
     }
 
-    plugin.reset(StoragePluginFactory::CreateStoragePlugin(orthancConfig));
-
-    if (plugin.get() == nullptr)
+    try
     {
-      return -1;
-    }
-
-    const char* pluginSectionName = plugin->GetConfigurationSectionName();
-    static const char* const ENCRYPTION_SECTION = "StorageEncryption";
+      plugin.reset(StoragePluginFactory::CreateStoragePlugin(orthancConfig));
 
-    if (orthancConfig.IsSection(pluginSectionName))
-    {
-      OrthancPlugins::OrthancConfiguration pluginSection;
-      orthancConfig.GetSection(pluginSection, pluginSectionName);
-
-      migrationFromFileSystemEnabled = pluginSection.GetBooleanValue("MigrationFromFileSystemEnabled", false);
-
-      if (migrationFromFileSystemEnabled)
+      if (plugin.get() == nullptr)
       {
-        fileSystemRootPath = orthancConfig.GetStringValue("StorageDirectory", "OrthancStorageNotDefined");
-        OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + ": migration from file system enabled, source: " + fileSystemRootPath);
+        return -1;
       }
 
-      objectsRootPath = pluginSection.GetStringValue("RootPath", std::string());
-      plugin->SetRootPath(objectsRootPath);
+      const char* pluginSectionName = plugin->GetConfigurationSectionName();
+      static const char* const ENCRYPTION_SECTION = "StorageEncryption";
 
-      if (pluginSection.IsSection(ENCRYPTION_SECTION))
+      if (orthancConfig.IsSection(pluginSectionName))
       {
-        OrthancPlugins::OrthancConfiguration cryptoSection;
-        pluginSection.GetSection(cryptoSection, ENCRYPTION_SECTION);
+        OrthancPlugins::OrthancConfiguration pluginSection;
+        orthancConfig.GetSection(pluginSection, pluginSectionName);
+
+        migrationFromFileSystemEnabled = pluginSection.GetBooleanValue("MigrationFromFileSystemEnabled", false);
+
+        if (migrationFromFileSystemEnabled)
+        {
+          fileSystemRootPath = orthancConfig.GetStringValue("StorageDirectory", "OrthancStorageNotDefined");
+          OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + ": migration from file system enabled, source: " + fileSystemRootPath);
+        }
+
+        objectsRootPath = pluginSection.GetStringValue("RootPath", std::string());
+
+        if (objectsRootPath.size() >= 1 && objectsRootPath[0] == '/')
+        {
+          OrthancPlugins::LogError(std::string(StoragePluginFactory::GetStoragePluginName()) + ": The RootPath shall not start with a '/': " + objectsRootPath);
+          return -1;
+        }
 
-        crypto.reset(EncryptionConfigurator::CreateEncryptionHelpers(cryptoSection));
-        cryptoEnabled = crypto.get() != nullptr;
+        plugin->SetRootPath(objectsRootPath);
+
+        if (pluginSection.IsSection(ENCRYPTION_SECTION))
+        {
+          OrthancPlugins::OrthancConfiguration cryptoSection;
+          pluginSection.GetSection(cryptoSection, ENCRYPTION_SECTION);
+
+          crypto.reset(EncryptionConfigurator::CreateEncryptionHelpers(cryptoSection));
+          cryptoEnabled = crypto.get() != nullptr;
+        }
+
+        if (cryptoEnabled)
+        {
+          OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + ": client-side encryption is enabled");
+        }
+        else
+        {
+          OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + ": client-side encryption is disabled");
+        }
       }
 
       if (cryptoEnabled)
       {
-        OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + ": client-side encryption is enabled");
+        // with encrypted file, we do not want to support ReadRange.  Therefore, we register the old interface
+        OrthancPluginRegisterStorageArea(context, StorageCreate, StorageReadWholeLegacy, StorageRemove);
       }
       else
       {
-        OrthancPlugins::LogWarning(std::string(StoragePluginFactory::GetStoragePluginName()) + ": client-side encryption is disabled");
+        OrthancPluginRegisterStorageArea2(context, StorageCreate, StorageReadWhole, StorageReadRange, StorageRemove);
       }
     }
-
-    OrthancPluginRegisterStorageArea(context, StorageCreate, StorageRead, StorageRemove);
+    catch (Orthanc::OrthancException& e)
+    {
+      LOG(ERROR) << "Exception while creating the object storage plugin: " << e.What();
+      return -1;
+    }
 
     return 0;
   }
--- a/Google/CMakeLists.txt	Wed Mar 31 12:10:00 2021 -0400
+++ b/Google/CMakeLists.txt	Fri Apr 02 09:30:52 2021 +0200
@@ -2,12 +2,12 @@
 
 project(OrthancGoogleCloudStorage)
 
-set(PLUGIN_VERSION "1.1.0")
+set(PLUGIN_VERSION "mainline")
 
 include(CheckIncludeFileCXX)
 
 set(ORTHANC_FRAMEWORK_SOURCE "hg" CACHE STRING "orthanc source")
-set(ORTHANC_FRAMEWORK_VERSION "1.7.3" CACHE STRING "orthanc framework version")
+set(ORTHANC_FRAMEWORK_VERSION "1.9.1" CACHE STRING "orthanc framework version")
 set(ALLOW_DOWNLOADS ON)
 
 # Download and setup the Orthanc framework
@@ -17,6 +17,9 @@
 
 set(ENABLE_GOOGLE_TEST ON)
 set(ORTHANC_FRAMEWORK_PLUGIN ON)
+set(ENABLE_MODULE_IMAGES OFF)
+set(ENABLE_MODULE_JOBS OFF)
+set(ENABLE_MODULE_DICOM OFF)
 
 include(${ORTHANC_FRAMEWORK_ROOT}/../Resources/CMake/OrthancFrameworkConfiguration.cmake)
 include(${ORTHANC_FRAMEWORK_ROOT}/../../OrthancServer/Plugins/Samples/Common/OrthancPluginsExports.cmake)
--- a/Google/GoogleStoragePlugin.cpp	Wed Mar 31 12:10:00 2021 -0400
+++ b/Google/GoogleStoragePlugin.cpp	Fri Apr 02 09:30:52 2021 +0200
@@ -1,6 +1,6 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ * Copyright (C) 2020-2021 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -103,7 +103,7 @@
     }
   }
 
-  virtual void Read(char* data, size_t size)
+  virtual void ReadWhole(char* data, size_t size)
   {
     auto reader = client_.ReadObject(bucketName_, path_);
 
@@ -120,6 +120,24 @@
     }
   }
 
+  virtual void ReadRange(char* data, size_t size, size_t fromOffset)
+  {
+    auto reader = client_.ReadObject(bucketName_, path_, gcs::ReadRange(fromOffset, fromOffset + size));
+
+    if (!reader)
+    {
+      throw StoragePluginException("error while opening/reading file " + std::string(path_) + ": " + reader.status().message());
+    }
+
+    reader.read(data, size);
+
+    if (!reader)
+    {
+      throw StoragePluginException("error while reading file " + std::string(path_) + ": " + reader.status().message());
+    }
+  }
+
+
 };
 
 
--- a/Google/GoogleStoragePlugin.h	Wed Mar 31 12:10:00 2021 -0400
+++ b/Google/GoogleStoragePlugin.h	Fri Apr 02 09:30:52 2021 +0200
@@ -1,6 +1,6 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ * Copyright (C) 2020-2021 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
--- a/NEWS	Wed Mar 31 12:10:00 2021 -0400
+++ b/NEWS	Fri Apr 02 09:30:52 2021 +0200
@@ -1,6 +1,10 @@
 Pending changes in the mainline
 ===============================
 
+* Allow compilation of the AWS S3 plugin without vcpkg
+* Added "VirtualAddressing" configuration option in the AWS S3 plugin (for compatibility with minio)
+
+
 2020-09-07 - v 1.1.0
 ====================
 
--- a/README.md	Wed Mar 31 12:10:00 2021 -0400
+++ b/README.md	Fri Apr 02 09:30:52 2021 +0200
@@ -55,12 +55,27 @@
 
 ### Compile Azure plugin ###
 
+On Linux:
+
 * `./vcpkg install cpprestsdk`
+* `./vcpkg install cryptopp`
 * `hg clone ...`
 * `mkdir -p build/azure`
 * `cd build/azure` 
 * `cmake -DCMAKE_TOOLCHAIN_FILE=[vcpkg root]\scripts\buildsystems\vcpkg.cmake ../../orthanc-object-storage/Azure`
 
+On Windows:
+
+* `.\vcpkg.exe install cpprestsdk:x64-windows-static`
+* `.\vcpkg.exe install azure-storage-cpp:x64-windows-static`
+* `.\vcpkg.exe install cryptopp:x64-windows-static`
+* `hg clone ...`
+* `mkdir -p build/azure`
+* `cd build/azure` 
+* `cmake -DCMAKE_TOOLCHAIN_FILE=[vcpkg root]\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static -DSTATIC_BUILD=ON -DCMAKE_BUILD_TYPE="Release" ../../orthanc-object-storage/Azure`
+* `cmake --build . --config Release`
+
+
 ### Azure plugin configuration ###
 
 ```
--- a/UnitTestsSources/EncryptionTests.cpp	Wed Mar 31 12:10:00 2021 -0400
+++ b/UnitTestsSources/EncryptionTests.cpp	Fri Apr 02 09:30:52 2021 +0200
@@ -1,6 +1,6 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ * Copyright (C) 2020-2021 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
@@ -32,8 +32,8 @@
 
   ASSERT_NE(key1, key2);
 
-  ASSERT_EQ(32, key1.size()); // right now, we work with 256bits key
-  ASSERT_EQ(32*2, EncryptionHelpers::ToHexString(key1).size());
+  ASSERT_EQ(32u, key1.size()); // right now, we work with 256bits key
+  ASSERT_EQ(32u * 2u, EncryptionHelpers::ToHexString(key1).size());
 }
 
 TEST(EncryptionHelpers, EncryptDecryptSimpleText)
--- a/UnitTestsSources/UnitTestsGcsClient.cpp	Wed Mar 31 12:10:00 2021 -0400
+++ b/UnitTestsSources/UnitTestsGcsClient.cpp	Fri Apr 02 09:30:52 2021 +0200
@@ -1,6 +1,6 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ * Copyright (C) 2020-2021 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License
--- a/UnitTestsSources/UnitTestsMain.cpp	Wed Mar 31 12:10:00 2021 -0400
+++ b/UnitTestsSources/UnitTestsMain.cpp	Fri Apr 02 09:30:52 2021 +0200
@@ -1,6 +1,6 @@
 /**
  * Cloud storage plugins for Orthanc
- * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ * Copyright (C) 2020-2021 Osimis S.A., Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU Affero General Public License