changeset 5103:899f303613fb

merge from 1.11.2 branch
author Alain Mazy <am@osimis.io>
date Thu, 27 Oct 2022 12:53:47 +0200
parents fcb2ed6c88ff (diff) 3b1ae7a81d9b (current diff)
children ea3224848d05
files TODO
diffstat 17 files changed, 228 insertions(+), 226 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Mon Oct 17 10:09:24 2022 +0200
+++ b/NEWS	Thu Oct 27 12:53:47 2022 +0200
@@ -1,3 +1,40 @@
+Pending changes in the mainline
+===============================
+
+General
+-------
+
+* C-Store SCU now gives priority to the preferred TransferSyntax proposed by the receiving SCP
+  instead of Orthanc own AcceptedTransferSyntaxes.
+* Made the default SQLite DB more robust wrt future updates like adding new columns in DB.
+* Made the HTTP Client errors more verbose by including the url in the logs.
+
+REST API
+--------
+
+* Loosen the sanity checks for DICOM modifications:
+  - allow modification of PatientID at study level
+  - allow modification of PatientID, StudyInstanceUID at series level
+  - allow modification of PatientID, StudyInstanceUID, SeriesInstanceUID at instance level
+  - allow modification of a patient without changing her PatientID
+  Users should be careful to preserve the DICOM model when modifying high level tags.  E.g.
+  if you modify the PatientID at study level, also make sure to modify all other Patient related
+  tags (PatientName, PatientBirthDate, ...)
+* Allow the HTTP server to return responses > 2GB (fixes asynchronous download of zip studies > 2GB)
+
+
+OrthancFramework (C++)
+----------------------
+
+* DicomModification::SetAllowManualIdentifiers() has been removed since it was always true -> code cleanup.
+
+
+Common plugins code (C++)
+-------------------------
+
+* Added a 'header' argument to all OrthancPeers::DoPost, DoPut, ... 
+
+
 version 1.11.2 (2022-08-30)
 ===========================
 
--- a/OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake	Mon Oct 17 10:09:24 2022 +0200
+++ b/OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake	Thu Oct 27 12:53:47 2022 +0200
@@ -24,7 +24,7 @@
 #####################################################################
 
 # Version of the build, should always be "mainline" except in release branches
-set(ORTHANC_VERSION "1.11.2")
+set(ORTHANC_VERSION "mainline")
 
 # Version of the database schema. History:
 #   * Orthanc 0.1.0 -> Orthanc 0.3.0 = no versioning
--- a/OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp	Mon Oct 17 10:09:24 2022 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp	Thu Oct 27 12:53:47 2022 +0200
@@ -521,10 +521,12 @@
                  * Accept in the order "least wanted" to "most wanted"
                  * transfer syntax.  Accepting a transfer syntax will
                  * override previously accepted transfer syntaxes.
+                 * Since Orthanc 1.11.2+, we give priority to the transfer
+                 * syntaxes proposed in the presentation context.
                  **/
-                for (int k = static_cast<int>(storageTransferSyntaxesC.size()) - 1; k >= 0; k--)
+                for (int j = static_cast<int>(pc.transferSyntaxCount)-1; j >=0; j--)
                 {
-                  for (int j = 0; j < static_cast<int>(pc.transferSyntaxCount); j++)
+                  for (int k = static_cast<int>(storageTransferSyntaxesC.size()) - 1; k >= 0; k--)
                   {
                     /**
                      * If the transfer syntax was proposed then we can accept it
--- a/OrthancFramework/Sources/DicomParsing/DicomModification.cpp	Mon Oct 17 10:09:24 2022 +0200
+++ b/OrthancFramework/Sources/DicomParsing/DicomModification.cpp	Thu Oct 27 12:53:47 2022 +0200
@@ -522,7 +522,6 @@
   DicomModification::DicomModification() :
     removePrivateTags_(false),
     level_(ResourceType_Instance),
-    allowManualIdentifiers_(true),
     keepStudyInstanceUid_(false),
     keepSeriesInstanceUid_(false),
     keepSopInstanceUid_(false),
@@ -922,102 +921,6 @@
     }
     
 
-    // Sanity checks at the patient level
-    bool isReplacedPatientId = (IsReplaced(DICOM_TAG_PATIENT_ID) ||
-                                uids_.find(DICOM_TAG_PATIENT_ID) != uids_.end());
-    
-    if (level_ == ResourceType_Patient && !isReplacedPatientId)
-    {
-      throw OrthancException(ErrorCode_BadRequest,
-                             "When modifying a patient, her PatientID is required to be modified");
-    }
-
-    if (!allowManualIdentifiers_)
-    {
-      if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
-      {
-        throw OrthancException(ErrorCode_BadRequest,
-                               "When modifying a patient, the StudyInstanceUID cannot be manually modified");
-      }
-
-      if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
-      {
-        throw OrthancException(ErrorCode_BadRequest,
-                               "When modifying a patient, the SeriesInstanceUID cannot be manually modified");
-      }
-
-      if (level_ == ResourceType_Patient && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID))
-      {
-        throw OrthancException(ErrorCode_BadRequest,
-                               "When modifying a patient, the SopInstanceUID cannot be manually modified");
-      }
-    }
-
-
-    // Sanity checks at the study level
-    if (level_ == ResourceType_Study && isReplacedPatientId)
-    {
-      throw OrthancException(ErrorCode_BadRequest,
-                             "When modifying a study, the parent PatientID cannot be manually modified");
-    }
-
-    if (!allowManualIdentifiers_)
-    {
-      if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
-      {
-        throw OrthancException(ErrorCode_BadRequest,
-                               "When modifying a study, the SeriesInstanceUID cannot be manually modified");
-      }
-
-      if (level_ == ResourceType_Study && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID))
-      {
-        throw OrthancException(ErrorCode_BadRequest,
-                               "When modifying a study, the SopInstanceUID cannot be manually modified");
-      }
-    }
-
-
-    // Sanity checks at the series level
-    if (level_ == ResourceType_Series && isReplacedPatientId)
-    {
-      throw OrthancException(ErrorCode_BadRequest,
-                             "When modifying a series, the parent PatientID cannot be manually modified");
-    }
-
-    if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
-    {
-      throw OrthancException(ErrorCode_BadRequest,
-                             "When modifying a series, the parent StudyInstanceUID cannot be manually modified");
-    }
-
-    if (!allowManualIdentifiers_)
-    {
-      if (level_ == ResourceType_Series && IsReplaced(DICOM_TAG_SOP_INSTANCE_UID))
-      {
-        throw OrthancException(ErrorCode_BadRequest,
-                               "When modifying a series, the SopInstanceUID cannot be manually modified");
-      }
-    }
-
-
-    // Sanity checks at the instance level
-    if (level_ == ResourceType_Instance && isReplacedPatientId)
-    {
-      throw OrthancException(ErrorCode_BadRequest,
-                             "When modifying an instance, the parent PatientID cannot be manually modified");
-    }
-
-    if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_STUDY_INSTANCE_UID))
-    {
-      throw OrthancException(ErrorCode_BadRequest,
-                             "When modifying an instance, the parent StudyInstanceUID cannot be manually modified");
-    }
-
-    if (level_ == ResourceType_Instance && IsReplaced(DICOM_TAG_SERIES_INSTANCE_UID))
-    {
-      throw OrthancException(ErrorCode_BadRequest,
-                             "When modifying an instance, the parent SeriesInstanceUID cannot be manually modified");
-    }
 
     // (0) Create a summary of the source file, if a custom generator
     // is provided
@@ -1158,17 +1061,6 @@
     }
   }
 
-  void DicomModification::SetAllowManualIdentifiers(bool check)
-  {
-    allowManualIdentifiers_ = check;
-  }
-
-  bool DicomModification::AreAllowManualIdentifiers() const
-  {
-    return allowManualIdentifiers_;
-  }
-
-
   static bool IsDatabaseKey(const DicomTag& tag)
   {
     return (tag == DICOM_TAG_PATIENT_ID ||
@@ -1390,7 +1282,6 @@
 
   static const char* REMOVE_PRIVATE_TAGS = "RemovePrivateTags";
   static const char* LEVEL = "Level";
-  static const char* ALLOW_MANUAL_IDENTIFIERS = "AllowManualIdentifiers";
   static const char* KEEP_STUDY_INSTANCE_UID = "KeepStudyInstanceUID";
   static const char* KEEP_SERIES_INSTANCE_UID = "KeepSeriesInstanceUID";
   static const char* KEEP_SOP_INSTANCE_UID = "KeepSOPInstanceUID";
@@ -1422,7 +1313,6 @@
     value = Json::objectValue;
     value[REMOVE_PRIVATE_TAGS] = removePrivateTags_;
     value[LEVEL] = EnumerationToString(level_);
-    value[ALLOW_MANUAL_IDENTIFIERS] = allowManualIdentifiers_;
     value[KEEP_STUDY_INSTANCE_UID] = keepStudyInstanceUid_;
     value[KEEP_SERIES_INSTANCE_UID] = keepSeriesInstanceUid_;
     value[KEEP_SOP_INSTANCE_UID] = keepSopInstanceUid_;
@@ -1567,7 +1457,6 @@
   {
     removePrivateTags_ = SerializationToolbox::ReadBoolean(serialized, REMOVE_PRIVATE_TAGS);
     level_ = StringToResourceType(SerializationToolbox::ReadString(serialized, LEVEL).c_str());
-    allowManualIdentifiers_ = SerializationToolbox::ReadBoolean(serialized, ALLOW_MANUAL_IDENTIFIERS);
     keepStudyInstanceUid_ = SerializationToolbox::ReadBoolean(serialized, KEEP_STUDY_INSTANCE_UID);
     keepSeriesInstanceUid_ = SerializationToolbox::ReadBoolean(serialized, KEEP_SERIES_INSTANCE_UID);
     updateReferencedRelationships_ = SerializationToolbox::ReadBoolean
--- a/OrthancFramework/Sources/DicomParsing/DicomModification.h	Mon Oct 17 10:09:24 2022 +0200
+++ b/OrthancFramework/Sources/DicomParsing/DicomModification.h	Thu Oct 27 12:53:47 2022 +0200
@@ -133,7 +133,6 @@
     ResourceType level_;
     UidMap uidMap_;
     SetOfTags privateTagsToKeep_;
-    bool allowManualIdentifiers_;
     bool keepStudyInstanceUid_;
     bool keepSeriesInstanceUid_;
     bool keepSopInstanceUid_;
@@ -224,10 +223,6 @@
 
     void Apply(ParsedDicomFile& toModify);
 
-    void SetAllowManualIdentifiers(bool check);
-
-    bool AreAllowManualIdentifiers() const;
-
     void ParseModifyRequest(const Json::Value& request);
 
     // "patientNameOverridden" is set to "true" iff. the PatientName
--- a/OrthancFramework/Sources/HttpClient.cpp	Mon Oct 17 10:09:24 2022 +0200
+++ b/OrthancFramework/Sources/HttpClient.cpp	Thu Oct 27 12:53:47 2022 +0200
@@ -46,7 +46,7 @@
 
 extern "C"
 {
-  static CURLcode GetHttpStatus(CURLcode code, CURL* curl, long* status)
+  static CURLcode GetHttpStatus(CURLcode code, CURL* curl, long* status, const std::string& url)
   {
     if (code == CURLE_OK)
     {
@@ -55,8 +55,6 @@
     }
     else
     {
-      LOG(ERROR) << "Error code " << static_cast<int>(code)
-                 << " in libcurl: " << curl_easy_strerror(code);
       *status = 0;
       return code;
     }
@@ -68,10 +66,10 @@
 #if defined(__GNUC__) || defined(__clang__)
 __attribute__((noinline)) 
 #endif
-static CURLcode OrthancHttpClientPerformSSL(CURL* curl, long* status)
+static CURLcode OrthancHttpClientPerformSSL(CURL* curl, long* status, const std::string& url)
 {
 #if ORTHANC_ENABLE_SSL == 1
-  return GetHttpStatus(curl_easy_perform(curl), curl, status);
+  return GetHttpStatus(curl_easy_perform(curl), curl, status, url);
 #else
   throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError,
                                   "Orthanc was compiled without SSL support, "
@@ -101,6 +99,24 @@
     return code;
   }
 
+  static CURLcode CheckCode(CURLcode code, const std::string& url)
+  {
+    if (code == CURLE_NOT_BUILT_IN)
+    {
+      throw OrthancException(ErrorCode_InternalError,
+                             "Your libcurl does not contain a required feature, "
+                             "please recompile Orthanc with -DUSE_SYSTEM_CURL=OFF");
+    }
+
+    if (code != CURLE_OK)
+    {
+      throw OrthancException(ErrorCode_NetworkProtocol,
+                             "libCURL error: " + std::string(curl_easy_strerror(code)) + " while accessing " + url);
+    }
+
+    return code;
+  }
+
 
   // RAII pattern around a "curl_slist"
   class HttpClient::CurlHeaders : public boost::noncopyable
@@ -1045,11 +1061,11 @@
     
     if (boost::starts_with(url_, "https://"))
     {
-      code = OrthancHttpClientPerformSSL(pimpl_->curl_, &status);
+      code = OrthancHttpClientPerformSSL(pimpl_->curl_, &status, url_);
     }
     else
     {
-      code = GetHttpStatus(curl_easy_perform(pimpl_->curl_), pimpl_->curl_, &status);
+      code = GetHttpStatus(curl_easy_perform(pimpl_->curl_), pimpl_->curl_, &status, url_);
     }
 
     const boost::posix_time::ptime end = boost::posix_time::microsec_clock::universal_time();
@@ -1063,7 +1079,7 @@
       CLOG(INFO, HTTP) << "cURL status code: " << code;
     }
 
-    CheckCode(code);
+    CheckCode(code, url_);  // throws on HTTP error
 
     if (status == 0)
     {
--- a/OrthancFramework/Sources/HttpServer/HttpServer.cpp	Mon Oct 17 10:09:24 2022 +0200
+++ b/OrthancFramework/Sources/HttpServer/HttpServer.cpp	Thu Oct 27 12:53:47 2022 +0200
@@ -35,6 +35,7 @@
 #include "IHttpHandler.h"
 #include "MultipartStreamReader.h"
 #include "StringHttpOutput.h"
+#include <algorithm>
 
 #if ORTHANC_ENABLE_PUGIXML == 1
 #  include "IWebDavBucket.h"
@@ -109,12 +110,25 @@
       {
         if (length > 0)
         {
-          int status = mg_write(connection_, buffer, length);
-          if (status != static_cast<int>(length))
+          // mg_write does not support buffers > 2GB (INT_MAX) -> need to split it
+          size_t offset = 0;
+          size_t remainingSize = length;
+
+          while (remainingSize > 0)
           {
-            // status == 0 when the connection has been closed, -1 on error
-            throw OrthancException(ErrorCode_NetworkProtocol);
-          }
+            size_t packetSize = std::min(remainingSize, static_cast<size_t>(INT_MAX));
+
+            int status = mg_write(connection_, &(reinterpret_cast<const char*>(buffer)[offset]), packetSize);  
+
+            if (status != static_cast<int>(packetSize))
+            {
+              // status == 0 when the connection has been closed, -1 on error
+              throw OrthancException(ErrorCode_NetworkProtocol);
+            }
+
+            offset += packetSize;
+            remainingSize -= packetSize;
+          }  
         }
       }
 
--- a/OrthancFramework/Sources/OrthancException.cpp	Mon Oct 17 10:09:24 2022 +0200
+++ b/OrthancFramework/Sources/OrthancException.cpp	Thu Oct 27 12:53:47 2022 +0200
@@ -31,7 +31,8 @@
 {
   OrthancException::OrthancException(const OrthancException& other) : 
     errorCode_(other.errorCode_),
-    httpStatus_(other.httpStatus_)
+    httpStatus_(other.httpStatus_),
+    logged_(false)
   {
     if (other.details_.get() != NULL)
     {
@@ -41,7 +42,8 @@
 
   OrthancException::OrthancException(ErrorCode errorCode) : 
     errorCode_(errorCode),
-    httpStatus_(ConvertErrorCodeToHttpStatus(errorCode))
+    httpStatus_(ConvertErrorCodeToHttpStatus(errorCode)),
+    logged_(false)
   {
   }
 
@@ -50,6 +52,7 @@
                                      bool log) :
     errorCode_(errorCode),
     httpStatus_(ConvertErrorCodeToHttpStatus(errorCode)),
+    logged_(log),
     details_(new std::string(details))
   {
 #if ORTHANC_ENABLE_LOGGING == 1
@@ -63,7 +66,8 @@
   OrthancException::OrthancException(ErrorCode errorCode,
                                      HttpStatus httpStatus) :
     errorCode_(errorCode),
-    httpStatus_(httpStatus)
+    httpStatus_(httpStatus),
+    logged_(false)
   {
   }
 
@@ -73,6 +77,7 @@
                                      bool log) :
     errorCode_(errorCode),
     httpStatus_(httpStatus),
+    logged_(log),
     details_(new std::string(details))
   {
 #if ORTHANC_ENABLE_LOGGING == 1
@@ -114,4 +119,10 @@
       return details_->c_str();
     }
   }
+
+  bool OrthancException::HasBeenLogged() const
+  {
+    return logged_;
+  }
+
 }
--- a/OrthancFramework/Sources/OrthancException.h	Mon Oct 17 10:09:24 2022 +0200
+++ b/OrthancFramework/Sources/OrthancException.h	Thu Oct 27 12:53:47 2022 +0200
@@ -38,6 +38,7 @@
 
     ErrorCode  errorCode_;
     HttpStatus httpStatus_;
+    bool       logged_;    // has the exception already been logged ?  (to avoid double logs)
 
     // New in Orthanc 1.5.0
     std::unique_ptr<std::string>  details_;
@@ -68,5 +69,7 @@
     bool HasDetails() const;
 
     const char* GetDetails() const;
+
+    bool HasBeenLogged() const;
   };
 }
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Mon Oct 17 10:09:24 2022 +0200
+++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp	Thu Oct 27 12:53:47 2022 +0200
@@ -3113,6 +3113,27 @@
     CopyToMemoryBuffer(*p.target, dicom);
   }
 
+  static void ThrowOnHttpError(HttpStatus httpStatus)
+  {
+    int intHttpStatus = static_cast<int>(httpStatus);
+    if (intHttpStatus >= 200 && intHttpStatus <= 300)
+    {
+      return; // not an error
+    }
+    else if (intHttpStatus == 401 || intHttpStatus == 403)
+    {
+      throw OrthancException(ErrorCode_Unauthorized);
+    }
+    else if (intHttpStatus == 404)
+    {
+      throw OrthancException(ErrorCode_UnknownResource);
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadRequest);
+    }
+  }
+
 
   void OrthancPlugins::RestApiGet(const void* parameters,
                                   bool afterPlugins)
@@ -3133,14 +3154,9 @@
     std::map<std::string, std::string> httpHeaders;
 
     std::string result;
-    if (IHttpHandler::SimpleGet(result, NULL, *handler, RequestOrigin_Plugins, p.uri, httpHeaders) == HttpStatus_200_Ok)
-    {
-      CopyToMemoryBuffer(*p.target, result);
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_UnknownResource);
-    }
+
+    ThrowOnHttpError(IHttpHandler::SimpleGet(result, NULL, *handler, RequestOrigin_Plugins, p.uri, httpHeaders));
+    CopyToMemoryBuffer(*p.target, result);
   }
 
 
@@ -3169,14 +3185,9 @@
     }
       
     std::string result;
-    if (IHttpHandler::SimpleGet(result, NULL, *handler, RequestOrigin_Plugins, p.uri, headers) == HttpStatus_200_Ok)
-    {
-      CopyToMemoryBuffer(*p.target, result);
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_UnknownResource);
-    }
+
+    ThrowOnHttpError(IHttpHandler::SimpleGet(result, NULL, *handler, RequestOrigin_Plugins, p.uri, headers));
+    CopyToMemoryBuffer(*p.target, result);
   }
 
 
@@ -3200,18 +3211,13 @@
     std::map<std::string, std::string> httpHeaders;
 
     std::string result;
-    if (isPost ? 
+    
+    ThrowOnHttpError((isPost ? 
         IHttpHandler::SimplePost(result, NULL, *handler, RequestOrigin_Plugins, p.uri,
-                                 p.body, p.bodySize, httpHeaders) == HttpStatus_200_Ok :
+                                 p.body, p.bodySize, httpHeaders) :
         IHttpHandler::SimplePut(result, NULL, *handler, RequestOrigin_Plugins, p.uri,
-                                p.body, p.bodySize, httpHeaders) == HttpStatus_200_Ok)
-    {
-      CopyToMemoryBuffer(*p.target, result);
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_UnknownResource);
-    }
+                                p.body, p.bodySize, httpHeaders)));
+    CopyToMemoryBuffer(*p.target, result);
   }
 
 
@@ -3231,10 +3237,7 @@
       
     std::map<std::string, std::string> httpHeaders;
 
-    if (IHttpHandler::SimpleDelete(NULL, *handler, RequestOrigin_Plugins, uri, httpHeaders) != HttpStatus_200_Ok)
-    {
-      throw OrthancException(ErrorCode_UnknownResource);
-    }
+    ThrowOnHttpError(IHttpHandler::SimpleDelete(NULL, *handler, RequestOrigin_Plugins, uri, httpHeaders));
   }
 
 
--- a/OrthancServer/Plugins/Engine/PluginsManager.cpp	Mon Oct 17 10:09:24 2022 +0200
+++ b/OrthancServer/Plugins/Engine/PluginsManager.cpp	Thu Oct 27 12:53:47 2022 +0200
@@ -183,7 +183,10 @@
         // This service provider has failed
         if (e.GetErrorCode() != ErrorCode_UnknownResource)  // This error code is valid in plugins
         {
-          LOG(ERROR) << "Exception while invoking plugin service " << service << ": " << e.What();
+          if (!e.HasBeenLogged())
+          {
+            LOG(ERROR) << "Exception while invoking plugin service " << service << ": " << e.What();
+          }
         }
 
         return static_cast<OrthancPluginErrorCode>(e.GetErrorCode());
--- a/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Mon Oct 17 10:09:24 2022 +0200
+++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Thu Oct 27 12:53:47 2022 +0200
@@ -1885,7 +1885,8 @@
 
   bool OrthancPeers::DoGet(MemoryBuffer& target,
                            size_t index,
-                           const std::string& uri) const
+                           const std::string& uri,
+                           const std::map<std::string, std::string>& headers) const
   {
     if (index >= index_.size())
     {
@@ -1894,10 +1895,12 @@
 
     OrthancPlugins::MemoryBuffer answer;
     uint16_t status;
+    PluginHttpHeaders pluginHeaders(headers);
+
     OrthancPluginErrorCode code = OrthancPluginCallPeerApi
       (GetGlobalContext(), *answer, NULL, &status, peers_,
        static_cast<uint32_t>(index), OrthancPluginHttpMethod_Get, uri.c_str(),
-       0, NULL, NULL, NULL, 0, timeout_);
+       pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), NULL, 0, timeout_);
 
     if (code == OrthancPluginErrorCode_Success)
     {
@@ -1913,21 +1916,23 @@
 
   bool OrthancPeers::DoGet(MemoryBuffer& target,
                            const std::string& name,
-                           const std::string& uri) const
+                           const std::string& uri,
+                           const std::map<std::string, std::string>& headers) const
   {
     size_t index;
     return (LookupName(index, name) &&
-            DoGet(target, index, uri));
+            DoGet(target, index, uri, headers));
   }
 
 
   bool OrthancPeers::DoGet(Json::Value& target,
                            size_t index,
-                           const std::string& uri) const
+                           const std::string& uri,
+                           const std::map<std::string, std::string>& headers) const
   {
     MemoryBuffer buffer;
 
-    if (DoGet(buffer, index, uri))
+    if (DoGet(buffer, index, uri, headers))
     {
       buffer.ToJson(target);
       return true;
@@ -1941,11 +1946,12 @@
 
   bool OrthancPeers::DoGet(Json::Value& target,
                            const std::string& name,
-                           const std::string& uri) const
+                           const std::string& uri,
+                           const std::map<std::string, std::string>& headers) const
   {
     MemoryBuffer buffer;
 
-    if (DoGet(buffer, name, uri))
+    if (DoGet(buffer, name, uri, headers))
     {
       buffer.ToJson(target);
       return true;
@@ -1960,22 +1966,24 @@
   bool OrthancPeers::DoPost(MemoryBuffer& target,
                             const std::string& name,
                             const std::string& uri,
-                            const std::string& body) const
+                            const std::string& body,
+                            const std::map<std::string, std::string>& headers) const
   {
     size_t index;
     return (LookupName(index, name) &&
-            DoPost(target, index, uri, body));
+            DoPost(target, index, uri, body, headers));
   }
 
 
   bool OrthancPeers::DoPost(Json::Value& target,
                             size_t index,
                             const std::string& uri,
-                            const std::string& body) const
+                            const std::string& body,
+                            const std::map<std::string, std::string>& headers) const
   {
     MemoryBuffer buffer;
 
-    if (DoPost(buffer, index, uri, body))
+    if (DoPost(buffer, index, uri, body, headers))
     {
       buffer.ToJson(target);
       return true;
@@ -1990,11 +1998,12 @@
   bool OrthancPeers::DoPost(Json::Value& target,
                             const std::string& name,
                             const std::string& uri,
-                            const std::string& body) const
+                            const std::string& body,
+                            const std::map<std::string, std::string>& headers) const
   {
     MemoryBuffer buffer;
 
-    if (DoPost(buffer, name, uri, body))
+    if (DoPost(buffer, name, uri, body, headers))
     {
       buffer.ToJson(target);
       return true;
@@ -2009,7 +2018,8 @@
   bool OrthancPeers::DoPost(MemoryBuffer& target,
                             size_t index,
                             const std::string& uri,
-                            const std::string& body) const
+                            const std::string& body,
+                            const std::map<std::string, std::string>& headers) const
   {
     if (index >= index_.size())
     {
@@ -2024,10 +2034,12 @@
 
     OrthancPlugins::MemoryBuffer answer;
     uint16_t status;
+    PluginHttpHeaders pluginHeaders(headers);
+
     OrthancPluginErrorCode code = OrthancPluginCallPeerApi
       (GetGlobalContext(), *answer, NULL, &status, peers_,
        static_cast<uint32_t>(index), OrthancPluginHttpMethod_Post, uri.c_str(),
-       0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
+       pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), body.empty() ? NULL : body.c_str(), body.size(), timeout_);
 
     if (code == OrthancPluginErrorCode_Success)
     {
@@ -2043,7 +2055,8 @@
 
   bool OrthancPeers::DoPut(size_t index,
                            const std::string& uri,
-                           const std::string& body) const
+                           const std::string& body,
+                           const std::map<std::string, std::string>& headers) const
   {
     if (index >= index_.size())
     {
@@ -2058,10 +2071,12 @@
 
     OrthancPlugins::MemoryBuffer answer;
     uint16_t status;
+    PluginHttpHeaders pluginHeaders(headers);
+
     OrthancPluginErrorCode code = OrthancPluginCallPeerApi
       (GetGlobalContext(), *answer, NULL, &status, peers_,
        static_cast<uint32_t>(index), OrthancPluginHttpMethod_Put, uri.c_str(),
-       0, NULL, NULL, body.empty() ? NULL : body.c_str(), body.size(), timeout_);
+       pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), body.empty() ? NULL : body.c_str(), body.size(), timeout_);
 
     if (code == OrthancPluginErrorCode_Success)
     {
@@ -2076,16 +2091,18 @@
 
   bool OrthancPeers::DoPut(const std::string& name,
                            const std::string& uri,
-                           const std::string& body) const
+                           const std::string& body,
+                           const std::map<std::string, std::string>& headers) const
   {
     size_t index;
     return (LookupName(index, name) &&
-            DoPut(index, uri, body));
+            DoPut(index, uri, body, headers));
   }
 
 
   bool OrthancPeers::DoDelete(size_t index,
-                              const std::string& uri) const
+                              const std::string& uri,
+                              const std::map<std::string, std::string>& headers) const
   {
     if (index >= index_.size())
     {
@@ -2094,10 +2111,12 @@
 
     OrthancPlugins::MemoryBuffer answer;
     uint16_t status;
+    PluginHttpHeaders pluginHeaders(headers);
+
     OrthancPluginErrorCode code = OrthancPluginCallPeerApi
       (GetGlobalContext(), *answer, NULL, &status, peers_,
        static_cast<uint32_t>(index), OrthancPluginHttpMethod_Delete, uri.c_str(),
-       0, NULL, NULL, NULL, 0, timeout_);
+       pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), NULL, 0, timeout_);
 
     if (code == OrthancPluginErrorCode_Success)
     {
@@ -2111,11 +2130,12 @@
 
 
   bool OrthancPeers::DoDelete(const std::string& name,
-                              const std::string& uri) const
+                              const std::string& uri,
+                              const std::map<std::string, std::string>& headers) const
   {
     size_t index;
     return (LookupName(index, name) &&
-            DoDelete(index, uri));
+            DoDelete(index, uri, headers));
   }
 #endif
 
--- a/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Mon Oct 17 10:09:24 2022 +0200
+++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Thu Oct 27 12:53:47 2022 +0200
@@ -755,53 +755,65 @@
 
     bool DoGet(MemoryBuffer& target,
                size_t index,
-               const std::string& uri) const;
+               const std::string& uri,
+               const std::map<std::string, std::string>& headers) const;
 
     bool DoGet(MemoryBuffer& target,
                const std::string& name,
-               const std::string& uri) const;
+               const std::string& uri,
+               const std::map<std::string, std::string>& headers) const;
 
     bool DoGet(Json::Value& target,
                size_t index,
-               const std::string& uri) const;
+               const std::string& uri,
+               const std::map<std::string, std::string>& headers) const;
 
     bool DoGet(Json::Value& target,
                const std::string& name,
-               const std::string& uri) const;
+               const std::string& uri,
+               const std::map<std::string, std::string>& headers) const;
 
     bool DoPost(MemoryBuffer& target,
                 size_t index,
                 const std::string& uri,
-                const std::string& body) const;
+                const std::string& body,
+                const std::map<std::string, std::string>& headers) const;
 
     bool DoPost(MemoryBuffer& target,
                 const std::string& name,
                 const std::string& uri,
-                const std::string& body) const;
+                const std::string& body,
+                const std::map<std::string, std::string>& headers) const;
 
     bool DoPost(Json::Value& target,
                 size_t index,
                 const std::string& uri,
-                const std::string& body) const;
+                const std::string& body,
+                const std::map<std::string, std::string>& headers) const;
 
     bool DoPost(Json::Value& target,
                 const std::string& name,
                 const std::string& uri,
-                const std::string& body) const;
+                const std::string& body,
+                const std::map<std::string, std::string>& headers) const;
 
     bool DoPut(size_t index,
                const std::string& uri,
-               const std::string& body) const;
+               const std::string& body,
+               const std::map<std::string, std::string>& headers) const;
 
     bool DoPut(const std::string& name,
                const std::string& uri,
-               const std::string& body) const;
+               const std::string& body,
+               const std::map<std::string, std::string>& headers) const;
 
     bool DoDelete(size_t index,
-                  const std::string& uri) const;
+                  const std::string& uri,
+                  const std::map<std::string, std::string>& headers) const;
 
     bool DoDelete(const std::string& name,
-                  const std::string& uri) const;
+                  const std::string& uri,
+                  const std::map<std::string, std::string>& headers) const;
   };
 #endif
 
--- a/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp	Mon Oct 17 10:09:24 2022 +0200
+++ b/OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp	Thu Oct 27 12:53:47 2022 +0200
@@ -324,7 +324,7 @@
                                int64_t revision) ORTHANC_OVERRIDE
     {
       // TODO - REVISIONS
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles VALUES(?, ?, ?, ?, ?, ?, ?, ?)");
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO AttachedFiles (id, fileType, uuid, compressedSize, uncompressedSize, compressionType, uncompressedMD5, compressedMD5) VALUES(?, ?, ?, ?, ?, ?, ?, ?)");
       s.BindInt64(0, id);
       s.BindInt(1, attachment.GetContentType());
       s.BindString(2, attachment.GetUuid());
@@ -433,7 +433,7 @@
     virtual int64_t CreateResource(const std::string& publicId,
                                    ResourceType type) ORTHANC_OVERRIDE
     {
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(NULL, ?, ?, NULL)");
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Resources (internalId, resourceType, publicId, parentId) VALUES(NULL, ?, ?, NULL)");
       s.BindInt(0, type);
       s.BindString(1, publicId);
       s.Run();
@@ -768,7 +768,7 @@
     virtual void LogChange(int64_t internalId,
                            const ServerIndexChange& change) ORTHANC_OVERRIDE
     {
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes VALUES(NULL, ?, ?, ?, ?)");
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Changes (seq, changeType, internalId, resourceType, date) VALUES(NULL, ?, ?, ?, ?)");
       s.BindInt(0, change.GetChangeType());
       s.BindInt64(1, internalId);
       s.BindInt(2, change.GetResourceType());
@@ -780,7 +780,7 @@
     virtual void LogExportedResource(const ExportedResource& resource) ORTHANC_OVERRIDE
     {
       SQLite::Statement s(db_, SQLITE_FROM_HERE, 
-                          "INSERT INTO ExportedResources VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?)");
+                          "INSERT INTO ExportedResources (seq, resourceType, publicId, remoteModality, patientId, studyInstanceUid, seriesInstanceUid, sopInstanceUid, date) VALUES(NULL, ?, ?, ?, ?, ?, ?, ?, ?)");
 
       s.BindInt(0, resource.GetResourceType());
       s.BindString(1, resource.GetPublicId());
@@ -974,7 +974,7 @@
       // The "shared" info is not used by the SQLite database, as it
       // can only be used by one Orthanc server.
       
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO GlobalProperties VALUES(?, ?)");
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO GlobalProperties (property, value) VALUES(?, ?)");
       s.BindInt(0, property);
       s.BindString(1, value);
       s.Run();
@@ -986,7 +986,7 @@
                                   const DicomTag& tag,
                                   const std::string& value) ORTHANC_OVERRIDE
     {
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers VALUES(?, ?, ?, ?)");
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers (id, tagGroup, tagElement, value) VALUES(?, ?, ?, ?)");
       s.BindInt64(0, id);
       s.BindInt(1, tag.GetGroup());
       s.BindInt(2, tag.GetElement());
@@ -1006,7 +1006,7 @@
       }
       else if (IsProtectedPatient(internalId))
       {
-        SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)");
+        SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO PatientRecyclingOrder (seq, patientId) VALUES(NULL, ?)");
         s.BindInt64(0, internalId);
         s.Run();
       }
@@ -1022,7 +1022,7 @@
                                  const DicomTag& tag,
                                  const std::string& value) ORTHANC_OVERRIDE
     {
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)");
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags (id, tagGroup, tagElement, value) VALUES(?, ?, ?, ?)");
       s.BindInt64(0, id);
       s.BindInt(1, tag.GetGroup());
       s.BindInt(2, tag.GetElement());
@@ -1037,7 +1037,7 @@
                              int64_t revision) ORTHANC_OVERRIDE
     {
       // TODO - REVISIONS
-      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO Metadata VALUES(?, ?, ?)");
+      SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT OR REPLACE INTO Metadata (id, type, value) VALUES(?, ?, ?)");
       s.BindInt64(0, id);
       s.BindInt(1, type);
       s.BindString(2, value);
@@ -1072,7 +1072,7 @@
 
       {
         SQLite::Statement s(db_, SQLITE_FROM_HERE,
-                            "INSERT INTO PatientRecyclingOrder VALUES(NULL, ?)");
+                            "INSERT INTO PatientRecyclingOrder (seq, patientId) VALUES(NULL, ?)");
         s.BindInt64(0, patient);
         s.Run();
       }
@@ -1479,7 +1479,7 @@
   int64_t SQLiteDatabaseWrapper::UnitTestsTransaction::CreateResource(const std::string& publicId,
                                                                       ResourceType type)
   {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Resources VALUES(NULL, ?, ?, NULL)");
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO Resources (internalId, resourceType, publicId, parentId) VALUES(NULL, ?, ?, NULL)");
     s.BindInt(0, type);
     s.BindString(1, publicId);
     s.Run();
@@ -1501,7 +1501,7 @@
                                                                      const DicomTag& tag,
                                                                      const std::string& value)
   {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers VALUES(?, ?, ?, ?)");
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO DicomIdentifiers (id, tagGroup, tagElement, value) VALUES(?, ?, ?, ?)");
     s.BindInt64(0, id);
     s.BindInt(1, tag.GetGroup());
     s.BindInt(2, tag.GetElement());
@@ -1514,7 +1514,7 @@
                                                                     const DicomTag& tag,
                                                                     const std::string& value)
   {
-    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags VALUES(?, ?, ?, ?)");
+    SQLite::Statement s(db_, SQLITE_FROM_HERE, "INSERT INTO MainDicomTags (id, tagGroup, tagElement, value) VALUES(?, ?, ?, ?)");
     s.BindInt64(0, id);
     s.BindInt(1, tag.GetGroup());
     s.BindInt(2, tag.GetElement());
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Mon Oct 17 10:09:24 2022 +0200
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp	Thu Oct 27 12:53:47 2022 +0200
@@ -269,7 +269,6 @@
     }
 
     DicomModification modification;
-    modification.SetAllowManualIdentifiers(true);
 
     Json::Value request;
     ParseModifyRequest(request, modification, call);
@@ -314,7 +313,6 @@
     }
 
     DicomModification modification;
-    modification.SetAllowManualIdentifiers(true);
 
     Json::Value request;
     ParseAnonymizationRequest(request, modification, call);
--- a/OrthancServer/Sources/ServerJobs/Operations/ModifyInstanceOperation.cpp	Mon Oct 17 10:09:24 2022 +0200
+++ b/OrthancServer/Sources/ServerJobs/Operations/ModifyInstanceOperation.cpp	Thu Oct 27 12:53:47 2022 +0200
@@ -43,8 +43,6 @@
       throw OrthancException(ErrorCode_NullPointer);
     }
     
-    modification_->SetAllowManualIdentifiers(true);
-
     if (modification_->IsReplaced(DICOM_TAG_PATIENT_ID))
     {
       modification_->SetLevel(ResourceType_Patient);
--- a/TODO	Mon Oct 17 10:09:24 2022 +0200
+++ b/TODO	Thu Oct 27 12:53:47 2022 +0200
@@ -33,9 +33,6 @@
   Can maybe be achieved by adding a configuration to select the TLS Security Profile:
   https://github.com/DCMTK/dcmtk/blob/master/dcmtls/include/dcmtk/dcmtls/tlsciphr.h#L83 
   (e.g: TSP_Profile_BCP195_ND instead of TSP_Profile_BCP195)
-* Provide a configuration option related to MaximumStorageSize: instead of 
-  recycling older patients, simply block new ingests (from DICOM/DICOMweb/API)
-  by returning out-of-resource status (HTTP or DIMSE)
 * Add configurations to enable/disable warnings:
   - Modifying an instance while keeping its original SOPInstanceUID: This should be avoided!
   - Modifying a study while keeping its original StudyInstanceUID: This should be avoided!
@@ -186,11 +183,15 @@
   would be compatible with Transcoding.
   Use case: receiving 10 1GB instances in parallel can consume up to 20 GB
   Alternative option 1: write an "external application/plugin" that would take care of these receptions, write the
-  file at the right place and send a signal to Orthanc to "adopt" the file.
+    file at the right place and send a signal to Orthanc to "adopt" the file.
   Alternative option 2: declare a memory resource (X GB) that is available for reception.  Every time
-  Orthanc starts receiving a file, it reserves the memory or twice the memory (through a Semaphore)
-  if no memory is available, it waits and possibly timeouts returning a 503 or DIMSE A700 (out of resources).
-  This would at least protect from "out of memory" crashes.
+    Orthanc starts receiving a file, it reserves the memory or twice the memory (through a Semaphore)
+    if no memory is available, it waits and possibly timeouts returning a 503 or DIMSE A700 (out of resources).
+    This would at least protect from "out of memory" crashes.
+  Alternative option 3: Configure DCMTK to "stream" DICOM files on a temporary file on disk.  Pass the file handle
+    to Orthanc and/or the Storage plugin (instead of passing a memory buffer) -> the object-storage plugin could 
+    "stream" the file to the storage.  The HTTP server could also "stream" its response from file handles.
+    Transcoding should be "file based" too.
 
 
 ========