# HG changeset patch # User Alain Mazy # Date 1666868027 -7200 # Node ID 899f303613fb44545bbff1c29d12e99f131bb21c # Parent fcb2ed6c88ff4e2a0ab0fac1519da9a73b799682# Parent 3b1ae7a81d9b25136e21bced48410b7ebeb5c2df merge from 1.11.2 branch diff -r 3b1ae7a81d9b -r 899f303613fb NEWS --- 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) =========================== diff -r 3b1ae7a81d9b -r 899f303613fb OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake --- 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 diff -r 3b1ae7a81d9b -r 899f303613fb OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp --- 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(storageTransferSyntaxesC.size()) - 1; k >= 0; k--) + for (int j = static_cast(pc.transferSyntaxCount)-1; j >=0; j--) { - for (int j = 0; j < static_cast(pc.transferSyntaxCount); j++) + for (int k = static_cast(storageTransferSyntaxesC.size()) - 1; k >= 0; k--) { /** * If the transfer syntax was proposed then we can accept it diff -r 3b1ae7a81d9b -r 899f303613fb OrthancFramework/Sources/DicomParsing/DicomModification.cpp --- 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 diff -r 3b1ae7a81d9b -r 899f303613fb OrthancFramework/Sources/DicomParsing/DicomModification.h --- 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 diff -r 3b1ae7a81d9b -r 899f303613fb OrthancFramework/Sources/HttpClient.cpp --- 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(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) { diff -r 3b1ae7a81d9b -r 899f303613fb OrthancFramework/Sources/HttpServer/HttpServer.cpp --- 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 #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(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(INT_MAX)); + + int status = mg_write(connection_, &(reinterpret_cast(buffer)[offset]), packetSize); + + if (status != static_cast(packetSize)) + { + // status == 0 when the connection has been closed, -1 on error + throw OrthancException(ErrorCode_NetworkProtocol); + } + + offset += packetSize; + remainingSize -= packetSize; + } } } diff -r 3b1ae7a81d9b -r 899f303613fb OrthancFramework/Sources/OrthancException.cpp --- 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_; + } + } diff -r 3b1ae7a81d9b -r 899f303613fb OrthancFramework/Sources/OrthancException.h --- 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 details_; @@ -68,5 +69,7 @@ bool HasDetails() const; const char* GetDetails() const; + + bool HasBeenLogged() const; }; } diff -r 3b1ae7a81d9b -r 899f303613fb OrthancServer/Plugins/Engine/OrthancPlugins.cpp --- 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(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 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 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 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)); } diff -r 3b1ae7a81d9b -r 899f303613fb OrthancServer/Plugins/Engine/PluginsManager.cpp --- 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(e.GetErrorCode()); diff -r 3b1ae7a81d9b -r 899f303613fb OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp --- 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& 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(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& 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& 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& 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& 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& 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& 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& 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(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& 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(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& 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& 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(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& headers) const { size_t index; return (LookupName(index, name) && - DoDelete(index, uri)); + DoDelete(index, uri, headers)); } #endif diff -r 3b1ae7a81d9b -r 899f303613fb OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h --- 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& headers) const; bool DoGet(MemoryBuffer& target, const std::string& name, - const std::string& uri) const; + const std::string& uri, + const std::map& headers) const; bool DoGet(Json::Value& target, size_t index, - const std::string& uri) const; + const std::string& uri, + const std::map& headers) const; bool DoGet(Json::Value& target, const std::string& name, - const std::string& uri) const; + const std::string& uri, + const std::map& 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& 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& 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& 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& headers) const; bool DoPut(size_t index, const std::string& uri, - const std::string& body) const; + const std::string& body, + const std::map& headers) const; bool DoPut(const std::string& name, const std::string& uri, - const std::string& body) const; + const std::string& body, + const std::map& headers) const; bool DoDelete(size_t index, - const std::string& uri) const; + const std::string& uri, + const std::map& headers) const; bool DoDelete(const std::string& name, - const std::string& uri) const; + const std::string& uri, + const std::map& headers) const; }; #endif diff -r 3b1ae7a81d9b -r 899f303613fb OrthancServer/Sources/Database/SQLiteDatabaseWrapper.cpp --- 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()); diff -r 3b1ae7a81d9b -r 899f303613fb OrthancServer/Sources/OrthancRestApi/OrthancRestAnonymizeModify.cpp --- 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); diff -r 3b1ae7a81d9b -r 899f303613fb OrthancServer/Sources/ServerJobs/Operations/ModifyInstanceOperation.cpp --- 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); diff -r 3b1ae7a81d9b -r 899f303613fb TODO --- 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. ========