# HG changeset patch # User Sebastien Jodogne # Date 1411386248 -7200 # Node ID 059391d3f8df59857fb0ffc84d812dc91e815e84 # Parent 1ea4094d077cccb254dea1367a6cafa92f1cd649# Parent fd3128b2cf45acf5904336a0ba5643a5db570fcb integration mainline->db-changes diff -r 1ea4094d077c -r 059391d3f8df AUTHORS --- a/AUTHORS Wed Sep 17 13:25:37 2014 +0200 +++ b/AUTHORS Mon Sep 22 13:44:08 2014 +0200 @@ -8,7 +8,7 @@ * Sebastien Jodogne Department of Medical Physics, CHU of Liege, Belgium - Overall design and main developper. + Overall design and lead developer. Client library diff -r 1ea4094d077c -r 059391d3f8df CMakeLists.txt diff -r 1ea4094d077c -r 059391d3f8df Core/RestApi/RestApiOutput.cpp --- a/Core/RestApi/RestApiOutput.cpp Wed Sep 17 13:25:37 2014 +0200 +++ b/Core/RestApi/RestApiOutput.cpp Mon Sep 22 13:44:08 2014 +0200 @@ -129,6 +129,7 @@ void RestApiOutput::SignalError(HttpStatus status) { if (status != HttpStatus_403_Forbidden && + status != HttpStatus_500_InternalServerError && status != HttpStatus_415_UnsupportedMediaType) { throw OrthancException("This HTTP status is not allowed in a REST API"); diff -r 1ea4094d077c -r 059391d3f8df NEWS --- a/NEWS Wed Sep 17 13:25:37 2014 +0200 +++ b/NEWS Mon Sep 22 13:44:08 2014 +0200 @@ -1,6 +1,14 @@ Pending changes in the mainline =============================== + +Version 0.8.4 (2014/09/19) +========================== + +* "/instances-tags" to get the tags of all the child instances of a + patient/study/series with a single REST call (bulk tags retrieval) +* Configuration/Lua to select the accepted C-Store SCP transfer syntaxes +* Fix reporting of errors in Orthanc Explorer when sending images to peers/modalities * Installation of plugin SDK in CMake diff -r 1ea4094d077c -r 059391d3f8df OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp --- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp Wed Sep 17 13:25:37 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/ExternC.cpp Mon Sep 22 13:44:08 2014 +0200 @@ -1509,12 +1509,12 @@ LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetFileVersion() { - return "0.8.0.3"; + return "0.8.0.4"; } LAAW_EXPORT_DLL_API const char* LAAW_CALL_CONVENTION LAAW_EXTERNC_GetFullVersion() { - return "0.8.3"; + return "0.8.4"; } LAAW_EXPORT_DLL_API void LAAW_CALL_CONVENTION LAAW_EXTERNC_FreeString(char* str) diff -r 1ea4094d077c -r 059391d3f8df OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc --- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc Wed Sep 17 13:25:37 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows32.rc Mon Sep 22 13:44:08 2014 +0200 @@ -1,7 +1,7 @@ #include VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,8,0,3 + FILEVERSION 0,8,0,4 PRODUCTVERSION 0,8,0,0 FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_DLL @@ -10,10 +10,10 @@ BEGIN BLOCK "040904E4" BEGIN - VALUE "Comments", "Release 0.8.3" + VALUE "Comments", "Release 0.8.4" VALUE "CompanyName", "CHU of Liege" VALUE "FileDescription", "Native client to the REST API of Orthanc" - VALUE "FileVersion", "0.8.0.3" + VALUE "FileVersion", "0.8.0.4" VALUE "InternalName", "OrthancClient" VALUE "LegalCopyright", "(c) 2012-2014, Sebastien Jodogne, University Hospital of Liege" VALUE "LegalTrademarks", "Licensing information is available on https://code.google.com/p/orthanc/" diff -r 1ea4094d077c -r 059391d3f8df OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc --- a/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc Wed Sep 17 13:25:37 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/AUTOGENERATED/Windows64.rc Mon Sep 22 13:44:08 2014 +0200 @@ -1,7 +1,7 @@ #include VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,8,0,3 + FILEVERSION 0,8,0,4 PRODUCTVERSION 0,8,0,0 FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_DLL @@ -10,10 +10,10 @@ BEGIN BLOCK "040904E4" BEGIN - VALUE "Comments", "Release 0.8.3" + VALUE "Comments", "Release 0.8.4" VALUE "CompanyName", "CHU of Liege" VALUE "FileDescription", "Native client to the REST API of Orthanc" - VALUE "FileVersion", "0.8.0.3" + VALUE "FileVersion", "0.8.0.4" VALUE "InternalName", "OrthancClient" VALUE "LegalCopyright", "(c) 2012-2014, Sebastien Jodogne, University Hospital of Liege" VALUE "LegalTrademarks", "Licensing information is available on https://code.google.com/p/orthanc/" diff -r 1ea4094d077c -r 059391d3f8df OrthancCppClient/SharedLibrary/Product.json --- a/OrthancCppClient/SharedLibrary/Product.json Wed Sep 17 13:25:37 2014 +0200 +++ b/OrthancCppClient/SharedLibrary/Product.json Mon Sep 22 13:44:08 2014 +0200 @@ -4,5 +4,5 @@ "Company" : "CHU of Liege", "Copyright" : "(c) 2012-2014, Sebastien Jodogne, University Hospital of Liege", "Legal" : "Licensing information is available on https://code.google.com/p/orthanc/", - "Version" : "0.8.3" + "Version" : "0.8.4" } diff -r 1ea4094d077c -r 059391d3f8df OrthancServer/DicomProtocol/IApplicationEntityFilter.h --- a/OrthancServer/DicomProtocol/IApplicationEntityFilter.h Wed Sep 17 13:25:37 2014 +0200 +++ b/OrthancServer/DicomProtocol/IApplicationEntityFilter.h Mon Sep 22 13:44:08 2014 +0200 @@ -51,5 +51,9 @@ virtual bool IsAllowedRequest(const std::string& callingIp, const std::string& callingAet, DicomRequestType type) = 0; + + virtual bool IsAllowedTransferSyntax(const std::string& callingIp, + const std::string& callingAet, + TransferSyntax syntax) = 0; }; } diff -r 1ea4094d077c -r 059391d3f8df OrthancServer/Internals/CommandDispatcher.cpp --- a/OrthancServer/Internals/CommandDispatcher.cpp Wed Sep 17 13:25:37 2014 +0200 +++ b/OrthancServer/Internals/CommandDispatcher.cpp Mon Sep 22 13:44:08 2014 +0200 @@ -459,7 +459,38 @@ return NULL; } - LOG(INFO) << "Association Received"; + // Retrieve the AET and the IP address of the remote modality + std::string callingAet; + std::string callingIp; + std::string calledAet; + + { + DIC_AE callingAet_C; + DIC_AE calledAet_C; + DIC_AE callingIp_C; + DIC_AE calledIP_C; + if (ASC_getAPTitles(assoc->params, callingAet_C, calledAet_C, NULL).bad() || + ASC_getPresentationAddresses(assoc->params, callingIp_C, calledIP_C).bad()) + { + T_ASC_RejectParameters rej = + { + ASC_RESULT_REJECTEDPERMANENT, + ASC_SOURCE_SERVICEUSER, + ASC_REASON_SU_NOREASON + }; + ASC_rejectAssociation(assoc, &rej); + AssociationCleanup(assoc); + return NULL; + } + + callingIp = std::string(/*OFSTRING_GUARD*/(callingIp_C)); + callingAet = std::string(/*OFSTRING_GUARD*/(callingAet_C)); + calledAet = (/*OFSTRING_GUARD*/(calledAet_C)); + } + + LOG(INFO) << "Association Received from AET " << callingAet + << " on IP " << callingIp; + std::vector transferSyntaxes; @@ -469,36 +500,72 @@ transferSyntaxes.push_back(UID_LittleEndianImplicitTransferSyntax); // New transfer syntaxes supported since Orthanc 0.7.2 - transferSyntaxes.push_back(UID_DeflatedExplicitVRLittleEndianTransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess1TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess2_4TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess3_5TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess6_8TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess7_9TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess10_12TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess11_13TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess14TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess15TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess16_18TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess17_19TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess20_22TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess21_23TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess24_26TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess25_27TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess28TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess29TransferSyntax); - transferSyntaxes.push_back(UID_JPEGProcess14SV1TransferSyntax); - transferSyntaxes.push_back(UID_JPEGLSLosslessTransferSyntax); - transferSyntaxes.push_back(UID_JPEGLSLossyTransferSyntax); - transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax); - transferSyntaxes.push_back(UID_JPEG2000TransferSyntax); - transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionLosslessOnlyTransferSyntax); - transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionTransferSyntax); - transferSyntaxes.push_back(UID_JPIPReferencedTransferSyntax); - transferSyntaxes.push_back(UID_JPIPReferencedDeflateTransferSyntax); - transferSyntaxes.push_back(UID_MPEG2MainProfileAtMainLevelTransferSyntax); - transferSyntaxes.push_back(UID_MPEG2MainProfileAtHighLevelTransferSyntax); - transferSyntaxes.push_back(UID_RLELosslessTransferSyntax); + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(callingIp, callingAet, TransferSyntax_Deflated)) + { + transferSyntaxes.push_back(UID_DeflatedExplicitVRLittleEndianTransferSyntax); + } + + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(callingIp, callingAet, TransferSyntax_Jpeg)) + { + transferSyntaxes.push_back(UID_JPEGProcess1TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess2_4TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess3_5TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess6_8TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess7_9TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess10_12TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess11_13TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess14TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess15TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess16_18TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess17_19TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess20_22TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess21_23TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess24_26TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess25_27TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess28TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess29TransferSyntax); + transferSyntaxes.push_back(UID_JPEGProcess14SV1TransferSyntax); + transferSyntaxes.push_back(UID_JPEGLSLosslessTransferSyntax); + transferSyntaxes.push_back(UID_JPEGLSLossyTransferSyntax); + } + + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(callingIp, callingAet, TransferSyntax_Jpeg2000)) + { + transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax); + transferSyntaxes.push_back(UID_JPEG2000TransferSyntax); + } + + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(callingIp, callingAet, TransferSyntax_JpegLossless)) + { + transferSyntaxes.push_back(UID_JPEG2000LosslessOnlyTransferSyntax); + transferSyntaxes.push_back(UID_JPEG2000TransferSyntax); + transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionLosslessOnlyTransferSyntax); + transferSyntaxes.push_back(UID_JPEG2000Part2MulticomponentImageCompressionTransferSyntax); + } + + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(callingIp, callingAet, TransferSyntax_Jpip)) + { + transferSyntaxes.push_back(UID_JPIPReferencedTransferSyntax); + transferSyntaxes.push_back(UID_JPIPReferencedDeflateTransferSyntax); + } + + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(callingIp, callingAet, TransferSyntax_Mpeg2)) + { + transferSyntaxes.push_back(UID_MPEG2MainProfileAtMainLevelTransferSyntax); + transferSyntaxes.push_back(UID_MPEG2MainProfileAtHighLevelTransferSyntax); + } + + if (!server.HasApplicationEntityFilter() || + server.GetApplicationEntityFilter().IsAllowedTransferSyntax(callingIp, callingAet, TransferSyntax_Rle)) + { + transferSyntaxes.push_back(UID_RLELosslessTransferSyntax); + } /* accept the Verification SOP Class if presented */ cond = ASC_acceptContextsWithPreferredTransferSyntaxes(assoc->params, &knownAbstractSyntaxes[0], knownAbstractSyntaxes.size(), &transferSyntaxes[0], transferSyntaxes.size()); @@ -555,62 +622,38 @@ return NULL; } - std::string callingIP; - std::string callingTitle; - /* check the AETs */ + if (!server.IsMyAETitle(calledAet)) { - DIC_AE callingTitle_C; - DIC_AE calledTitle_C; - DIC_AE callingIP_C; - DIC_AE calledIP_C; - if (ASC_getAPTitles(assoc->params, callingTitle_C, calledTitle_C, NULL).bad() || - ASC_getPresentationAddresses(assoc->params, callingIP_C, calledIP_C).bad()) - { - T_ASC_RejectParameters rej = - { - ASC_RESULT_REJECTEDPERMANENT, - ASC_SOURCE_SERVICEUSER, - ASC_REASON_SU_NOREASON - }; - ASC_rejectAssociation(assoc, &rej); - AssociationCleanup(assoc); - return NULL; - } - - callingIP = std::string(/*OFSTRING_GUARD*/(callingIP_C)); - callingTitle = std::string(/*OFSTRING_GUARD*/(callingTitle_C)); - std::string calledTitle(/*OFSTRING_GUARD*/(calledTitle_C)); - - if (!server.IsMyAETitle(calledTitle)) - { - T_ASC_RejectParameters rej = - { - ASC_RESULT_REJECTEDPERMANENT, - ASC_SOURCE_SERVICEUSER, - ASC_REASON_SU_CALLEDAETITLENOTRECOGNIZED - }; - ASC_rejectAssociation(assoc, &rej); - AssociationCleanup(assoc); - return NULL; - } - - if (server.HasApplicationEntityFilter() && - !server.GetApplicationEntityFilter().IsAllowedConnection(callingIP, callingTitle)) - { - T_ASC_RejectParameters rej = - { - ASC_RESULT_REJECTEDPERMANENT, - ASC_SOURCE_SERVICEUSER, - ASC_REASON_SU_CALLINGAETITLENOTRECOGNIZED - }; - ASC_rejectAssociation(assoc, &rej); - AssociationCleanup(assoc); - return NULL; - } + LOG(WARNING) << "Rejected association, because of a bad called AET in the request (" << calledAet << ")"; + T_ASC_RejectParameters rej = + { + ASC_RESULT_REJECTEDPERMANENT, + ASC_SOURCE_SERVICEUSER, + ASC_REASON_SU_CALLEDAETITLENOTRECOGNIZED + }; + ASC_rejectAssociation(assoc, &rej); + AssociationCleanup(assoc); + return NULL; } - if (opt_rejectWithoutImplementationUID && strlen(assoc->params->theirImplementationClassUID) == 0) + if (server.HasApplicationEntityFilter() && + !server.GetApplicationEntityFilter().IsAllowedConnection(callingIp, callingAet)) + { + LOG(WARNING) << "Rejected association for remote AET " << callingAet << " on IP " << callingIp; + T_ASC_RejectParameters rej = + { + ASC_RESULT_REJECTEDPERMANENT, + ASC_SOURCE_SERVICEUSER, + ASC_REASON_SU_CALLINGAETITLENOTRECOGNIZED + }; + ASC_rejectAssociation(assoc, &rej); + AssociationCleanup(assoc); + return NULL; + } + + if (opt_rejectWithoutImplementationUID && + strlen(assoc->params->theirImplementationClassUID) == 0) { /* reject: the no implementation Class UID provided */ T_ASC_RejectParameters rej = @@ -644,7 +687,7 @@ } IApplicationEntityFilter* filter = server.HasApplicationEntityFilter() ? &server.GetApplicationEntityFilter() : NULL; - return new CommandDispatcher(server, assoc, callingIP, callingTitle, filter); + return new CommandDispatcher(server, assoc, callingIp, callingAet, filter); } bool CommandDispatcher::Step() diff -r 1ea4094d077c -r 059391d3f8df OrthancServer/OrthancRestApi/OrthancRestModalities.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Wed Sep 17 13:25:37 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestModalities.cpp Mon Sep 22 13:44:08 2014 +0200 @@ -80,12 +80,17 @@ { if (locker.GetConnection().Echo()) { + // Echo has succeeded call.GetOutput().AnswerBuffer("{}", "application/json"); + return; } } catch (OrthancException&) { } + + // Echo has failed + call.GetOutput().SignalError(HttpStatus_500_InternalServerError); } @@ -357,13 +362,20 @@ for (std::list::const_iterator it = instances.begin(); it != instances.end(); ++it) { - job.AddCommand(new StoreScuCommand(context, p)).AddInput(*it); + job.AddCommand(new StoreScuCommand(context, p, false)).AddInput(*it); } job.SetDescription("HTTP request: Store-SCU to peer \"" + remote + "\""); - context.GetScheduler().SubmitAndWait(job); - call.GetOutput().AnswerBuffer("{}", "application/json"); + if (context.GetScheduler().SubmitAndWait(job)) + { + // Success + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + else + { + call.GetOutput().SignalError(HttpStatus_500_InternalServerError); + } } @@ -421,13 +433,20 @@ for (std::list::const_iterator it = instances.begin(); it != instances.end(); ++it) { - job.AddCommand(new StorePeerCommand(context, peer)).AddInput(*it); + job.AddCommand(new StorePeerCommand(context, peer, false)).AddInput(*it); } job.SetDescription("HTTP request: POST to peer \"" + remote + "\""); - context.GetScheduler().SubmitAndWait(job); - call.GetOutput().AnswerBuffer("{}", "application/json"); + if (context.GetScheduler().SubmitAndWait(job)) + { + // Success + call.GetOutput().AnswerBuffer("{}", "application/json"); + } + else + { + call.GetOutput().SignalError(HttpStatus_500_InternalServerError); + } } diff -r 1ea4094d077c -r 059391d3f8df OrthancServer/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Wed Sep 17 13:25:37 2014 +0200 +++ b/OrthancServer/OrthancRestApi/OrthancRestResources.cpp Mon Sep 22 13:44:08 2014 +0200 @@ -876,6 +876,42 @@ } + static void GetChildInstancesTags(RestApiGetCall& call) + { + ServerContext& context = OrthancRestApi::GetContext(call); + std::string publicId = call.GetUriComponent("id", ""); + bool simplify = call.HasArgument("simplify"); + + // Retrieve all the instances of this patient/study/series + typedef std::list Instances; + Instances instances; + + context.GetIndex().GetChildInstances(instances, publicId); // (*) + + Json::Value result = Json::arrayValue; + + for (Instances::const_iterator it = instances.begin(); + it != instances.end(); it++) + { + Json::Value full; + context.ReadJson(full, *it); + + if (simplify) + { + Json::Value simplified; + SimplifyTags(simplified, full); + result.append(simplified); + } + else + { + result.append(full); + } + } + + call.GetOutput().AnswerJson(result); + } + + void OrthancRestApi::RegisterResources() { @@ -954,6 +990,10 @@ Register("/studies/{id}/instances", GetChildResources); Register("/series/{id}/instances", GetChildResources); + Register("/patients/{id}/instances-tags", GetChildInstancesTags); + Register("/studies/{id}/instances-tags", GetChildInstancesTags); + Register("/series/{id}/instances-tags", GetChildInstancesTags); + Register("/instances/{id}/content/*", GetRawContent); } } diff -r 1ea4094d077c -r 059391d3f8df OrthancServer/Scheduler/StorePeerCommand.cpp --- a/OrthancServer/Scheduler/StorePeerCommand.cpp Wed Sep 17 13:25:37 2014 +0200 +++ b/OrthancServer/Scheduler/StorePeerCommand.cpp Mon Sep 22 13:44:08 2014 +0200 @@ -39,9 +39,11 @@ namespace Orthanc { StorePeerCommand::StorePeerCommand(ServerContext& context, - const OrthancPeerParameters& peer) : + const OrthancPeerParameters& peer, + bool ignoreExceptions) : context_(context), - peer_(peer) + peer_(peer), + ignoreExceptions_(ignoreExceptions) { } @@ -84,6 +86,11 @@ { LOG(ERROR) << "Unable to forward to an Orthanc peer in a Lua script (instance " << *it << ", peer " << peer_.GetUrl() << "): " << e.What(); + + if (!ignoreExceptions_) + { + throw; + } } } diff -r 1ea4094d077c -r 059391d3f8df OrthancServer/Scheduler/StorePeerCommand.h --- a/OrthancServer/Scheduler/StorePeerCommand.h Wed Sep 17 13:25:37 2014 +0200 +++ b/OrthancServer/Scheduler/StorePeerCommand.h Mon Sep 22 13:44:08 2014 +0200 @@ -43,10 +43,12 @@ private: ServerContext& context_; OrthancPeerParameters peer_; + bool ignoreExceptions_; public: StorePeerCommand(ServerContext& context, - const OrthancPeerParameters& peer); + const OrthancPeerParameters& peer, + bool ignoreExceptions); virtual bool Apply(ListOfStrings& outputs, const ListOfStrings& inputs); diff -r 1ea4094d077c -r 059391d3f8df OrthancServer/Scheduler/StoreScuCommand.cpp --- a/OrthancServer/Scheduler/StoreScuCommand.cpp Wed Sep 17 13:25:37 2014 +0200 +++ b/OrthancServer/Scheduler/StoreScuCommand.cpp Mon Sep 22 13:44:08 2014 +0200 @@ -37,9 +37,11 @@ namespace Orthanc { StoreScuCommand::StoreScuCommand(ServerContext& context, - const RemoteModalityParameters& modality) : + const RemoteModalityParameters& modality, + bool ignoreExceptions) : context_(context), - modality_(modality) + modality_(modality), + ignoreExceptions_(ignoreExceptions) { } @@ -69,6 +71,11 @@ // powered off) LOG(ERROR) << "Unable to forward to a modality in a Lua script (instance " << *it << "): " << e.What(); + + if (!ignoreExceptions_) + { + throw; + } } } diff -r 1ea4094d077c -r 059391d3f8df OrthancServer/Scheduler/StoreScuCommand.h --- a/OrthancServer/Scheduler/StoreScuCommand.h Wed Sep 17 13:25:37 2014 +0200 +++ b/OrthancServer/Scheduler/StoreScuCommand.h Mon Sep 22 13:44:08 2014 +0200 @@ -42,10 +42,12 @@ private: ServerContext& context_; RemoteModalityParameters modality_; + bool ignoreExceptions_; public: StoreScuCommand(ServerContext& context, - const RemoteModalityParameters& modality); + const RemoteModalityParameters& modality, + bool ignoreExceptions); virtual bool Apply(ListOfStrings& outputs, const ListOfStrings& inputs); diff -r 1ea4094d077c -r 059391d3f8df OrthancServer/ServerContext.cpp --- a/OrthancServer/ServerContext.cpp Wed Sep 17 13:25:37 2014 +0200 +++ b/OrthancServer/ServerContext.cpp Mon Sep 22 13:44:08 2014 +0200 @@ -138,7 +138,7 @@ std::string modality = parameters["Modality"].asString(); LOG(INFO) << "Lua script to send instance " << parameters["Instance"].asString() << " to modality " << modality << " using Store-SCU"; - return new StoreScuCommand(context, Configuration::GetModalityUsingSymbolicName(modality)); + return new StoreScuCommand(context, Configuration::GetModalityUsingSymbolicName(modality), true); } if (operation == "store-peer") @@ -149,7 +149,7 @@ OrthancPeerParameters parameters; Configuration::GetOrthancPeer(parameters, peer); - return new StorePeerCommand(context, parameters); + return new StorePeerCommand(context, parameters, true); } if (operation == "modify") diff -r 1ea4094d077c -r 059391d3f8df OrthancServer/ServerEnumerations.cpp --- a/OrthancServer/ServerEnumerations.cpp Wed Sep 17 13:25:37 2014 +0200 +++ b/OrthancServer/ServerEnumerations.cpp Mon Sep 22 13:44:08 2014 +0200 @@ -324,7 +324,6 @@ return "Store"; break; - default: throw OrthancException(ErrorCode_ParameterOutOfRange); } @@ -359,4 +358,36 @@ throw OrthancException(ErrorCode_ParameterOutOfRange); } } + + + const char* EnumerationToString(TransferSyntax syntax) + { + switch (syntax) + { + case TransferSyntax_Deflated: + return "Deflated"; + + case TransferSyntax_Jpeg: + return "JPEG"; + + case TransferSyntax_Jpeg2000: + return "JPEG2000"; + + case TransferSyntax_JpegLossless: + return "JPEG Lossless"; + + case TransferSyntax_Jpip: + return "JPIP"; + + case TransferSyntax_Mpeg2: + return "MPEG2"; + + case TransferSyntax_Rle: + return "RLE"; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + } + } diff -r 1ea4094d077c -r 059391d3f8df OrthancServer/ServerEnumerations.h --- a/OrthancServer/ServerEnumerations.h Wed Sep 17 13:25:37 2014 +0200 +++ b/OrthancServer/ServerEnumerations.h Mon Sep 22 13:44:08 2014 +0200 @@ -79,6 +79,17 @@ DicomReplaceMode_IgnoreIfAbsent }; + enum TransferSyntax + { + TransferSyntax_Deflated, + TransferSyntax_Jpeg, + TransferSyntax_Jpeg2000, + TransferSyntax_JpegLossless, + TransferSyntax_Jpip, + TransferSyntax_Mpeg2, + TransferSyntax_Rle + }; + /** * WARNING: Do not change the explicit values in the enumerations @@ -157,6 +168,8 @@ const char* EnumerationToString(DicomRequestType type); + const char* EnumerationToString(TransferSyntax syntax); + ModalityManufacturer StringToModalityManufacturer(const std::string& manufacturer); ResourceType GetParentResourceType(ResourceType type); diff -r 1ea4094d077c -r 059391d3f8df OrthancServer/main.cpp --- a/OrthancServer/main.cpp Wed Sep 17 13:25:37 2014 +0200 +++ b/OrthancServer/main.cpp Mon Sep 22 13:44:08 2014 +0200 @@ -150,7 +150,14 @@ class OrthancApplicationEntityFilter : public IApplicationEntityFilter { +private: + ServerContext& context_; + public: + OrthancApplicationEntityFilter(ServerContext& context) : context_(context) + { + } + virtual bool IsAllowedConnection(const std::string& /*callingIp*/, const std::string& /*callingAet*/) { @@ -177,6 +184,63 @@ return true; } } + + virtual bool IsAllowedTransferSyntax(const std::string& callingIp, + const std::string& callingAet, + TransferSyntax syntax) + { + std::string configuration; + + switch (syntax) + { + case TransferSyntax_Deflated: + configuration = "DeflatedTransferSyntaxAccepted"; + break; + + case TransferSyntax_Jpeg: + configuration = "JpegTransferSyntaxAccepted"; + break; + + case TransferSyntax_Jpeg2000: + configuration = "Jpeg2000TransferSyntaxAccepted"; + break; + + case TransferSyntax_JpegLossless: + configuration = "JpegLosslessTransferSyntaxAccepted"; + break; + + case TransferSyntax_Jpip: + configuration = "JpipTransferSyntaxAccepted"; + break; + + case TransferSyntax_Mpeg2: + configuration = "Mpeg2TransferSyntaxAccepted"; + break; + + case TransferSyntax_Rle: + configuration = "RleTransferSyntaxAccepted"; + break; + + default: + throw OrthancException(ErrorCode_ParameterOutOfRange); + } + + { + std::string lua = "Is" + configuration; + + ServerContext::LuaContextLocker locker(context_); + + if (locker.GetLua().IsExistingFunction(lua.c_str())) + { + LuaFunctionCall call(locker.GetLua(), lua.c_str()); + call.PushString(callingAet); + call.PushString(callingIp); + return call.ExecutePredicate(); + } + } + + return Configuration::GetGlobalBoolParameter(configuration, true); + } }; @@ -405,7 +469,7 @@ { // DICOM server DicomServer dicomServer; - OrthancApplicationEntityFilter dicomFilter; + OrthancApplicationEntityFilter dicomFilter(context); dicomServer.SetCalledApplicationEntityTitleCheck(Configuration::GetGlobalBoolParameter("DicomCheckCalledAet", false)); dicomServer.SetStoreRequestHandlerFactory(serverFactory); dicomServer.SetMoveRequestHandlerFactory(serverFactory); @@ -594,6 +658,7 @@ } } + LOG(WARNING) << "Orthanc version: " << ORTHANC_VERSION; int status = 0; try diff -r 1ea4094d077c -r 059391d3f8df Resources/Configuration.json --- a/Resources/Configuration.json Wed Sep 17 13:25:37 2014 +0200 +++ b/Resources/Configuration.json Mon Sep 22 13:44:08 2014 +0200 @@ -69,6 +69,14 @@ // and "Chinese". "DefaultEncoding" : "Latin1", + // The transfer syntaxes that are accepted by Orthanc C-Store SCP + "DeflatedTransferSyntaxAccepted" : true, + "JpegTransferSyntaxAccepted" : true, + "Jpeg2000TransferSyntaxAccepted" : true, + "JpegLosslessTransferSyntaxAccepted" : true, + "JpipTransferSyntaxAccepted" : true, + "Mpeg2TransferSyntaxAccepted" : true, + "RleTransferSyntaxAccepted" : true, /** diff -r 1ea4094d077c -r 059391d3f8df Resources/DicomConformanceStatement.txt --- a/Resources/DicomConformanceStatement.txt Wed Sep 17 13:25:37 2014 +0200 +++ b/Resources/DicomConformanceStatement.txt Mon Sep 22 13:44:08 2014 +0200 @@ -231,6 +231,9 @@ MPEG2MainProfileAtHighLevelTransferSyntax | 1.2.840.10008.1.2.4.101 RLELosslessTransferSyntax | 1.2.840.10008.1.2.5 +It is possible to disable a subset of these transfer syntaxes thanks to the +"*TransferSyntaxAccepted" options in the Orthanc configuration file. + When possible, Orthanc will prefer the LittleEndianImplicitTransferSyntax transfer syntax (1.2.840.10008.1.2). diff -r 1ea4094d077c -r 059391d3f8df Resources/Samples/Lua/TransferSyntaxDisable.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/Lua/TransferSyntaxDisable.lua Mon Sep 22 13:44:08 2014 +0200 @@ -0,0 +1,29 @@ +function IsDeflatedTransferSyntaxAccepted(aet, ip) + return false +end + +function IsJpegTransferSyntaxAccepted(aet, ip) + return false +end + +function IsJpeg2000TransferSyntaxAccepted(aet, ip) + return false +end + +function IsJpegLosslessTransferSyntaxAccepted(aet, ip) + return false +end + +function IsJpipTransferSyntaxAccepted(aet, ip) + return false +end + +function IsMpeg2TransferSyntaxAccepted(aet, ip) + return false +end + +function IsRleTransferSyntaxAccepted(aet, ip) + return false +end + +print('All special transfer syntaxes are now disallowed') diff -r 1ea4094d077c -r 059391d3f8df Resources/Samples/Lua/TransferSyntaxEnable.lua --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Resources/Samples/Lua/TransferSyntaxEnable.lua Mon Sep 22 13:44:08 2014 +0200 @@ -0,0 +1,29 @@ +function IsDeflatedTransferSyntaxAccepted(aet, ip) + return true +end + +function IsJpegTransferSyntaxAccepted(aet, ip) + return true +end + +function IsJpeg2000TransferSyntaxAccepted(aet, ip) + return true +end + +function IsJpegLosslessTransferSyntaxAccepted(aet, ip) + return true +end + +function IsJpipTransferSyntaxAccepted(aet, ip) + return true +end + +function IsMpeg2TransferSyntaxAccepted(aet, ip) + return true +end + +function IsRleTransferSyntaxAccepted(aet, ip) + return true +end + +print('All special transfer syntaxes are now accepted')