# HG changeset patch # User Alain Mazy # Date 1717578277 -7200 # Node ID c4e33e0f907b23fd4f883ff444a4eb5ecab30cb2 # Parent c2817a9eb032d9c759589e5e8469354428bbb3df# Parent a8e9113dc8f1c8b722c9d0853db5d998704c253c merge diff -r c2817a9eb032 -r c4e33e0f907b NEWS --- a/NEWS Wed Jun 05 11:03:45 2024 +0200 +++ b/NEWS Wed Jun 05 11:04:37 2024 +0200 @@ -12,7 +12,8 @@ e.g. after you have updated the 'ExtraMainDicomTags' for this level. * The "requestedTags" GET argument was deprecated in favor of "requested-tags". * Fixed broken /instances/../tags route after the calling - /studies/../reconstruct when changing the "IngestTranscoding". + /studies/../reconstruct when changing the "IngestTranscoding". +* Added "?whole" option to "/instances/{id}/tags" to access tags stored after pixel data. Plugins ------- diff -r c2817a9eb032 -r c4e33e0f907b OrthancFramework/Sources/DicomNetworking/DicomServer.cpp --- a/OrthancFramework/Sources/DicomNetworking/DicomServer.cpp Wed Jun 05 11:03:45 2024 +0200 +++ b/OrthancFramework/Sources/DicomNetworking/DicomServer.cpp Wed Jun 05 11:04:37 2024 +0200 @@ -107,7 +107,8 @@ applicationEntityFilter_(NULL), useDicomTls_(false), maximumPduLength_(ASC_DEFAULTMAXPDU), - remoteCertificateRequired_(true) + remoteCertificateRequired_(true), + minimumTlsVersion_(0) { } diff -r c2817a9eb032 -r c4e33e0f907b OrthancFramework/Sources/DicomNetworking/Internals/DicomTls.cpp --- a/OrthancFramework/Sources/DicomNetworking/Internals/DicomTls.cpp Wed Jun 05 11:03:45 2024 +0200 +++ b/OrthancFramework/Sources/DicomNetworking/Internals/DicomTls.cpp Wed Jun 05 11:04:37 2024 +0200 @@ -61,6 +61,39 @@ #endif +#if DCMTK_VERSION_NUMBER >= 367 + static OFCondition MyConvertOpenSSLError(unsigned long errorCode, OFBool logAsError) + { + return DcmTLSTransportLayer::convertOpenSSLError(errorCode, logAsError); + } +#else + static OFCondition MyConvertOpenSSLError(unsigned long errorCode, OFBool logAsError) + { + if (errorCode == 0) + { + return EC_Normal; + } + else + { + const char *err = ERR_reason_error_string(errorCode); + if (err == NULL) + { + err = "OpenSSL error"; + } + + if (logAsError) + { + DCMTLS_ERROR("OpenSSL error " << STD_NAMESPACE hex << STD_NAMESPACE setfill('0') + << STD_NAMESPACE setw(8) << errorCode << ": " << err); + } + + // The "2" below corresponds to the same error code as "DCMTLS_EC_FailedToSetCiphersuites" + return OFCondition(OFM_dcmtls, 2, OF_error, err); + } + } +#endif + + DcmTLSTransportLayer* InitializeDicomTls(T_ASC_Network *network, T_ASC_NetworkRole role, const std::string& ownPrivateKeyPath, @@ -239,13 +272,13 @@ if (joinedCiphersTls.size() > 0 && SSL_CTX_set_cipher_list(sslNativeHandle, joinedCiphersTls.c_str()) != 1) { - OFCondition cond = DcmTLSTransportLayer::convertOpenSSLError(ERR_get_error(), OFTrue); + OFCondition cond = MyConvertOpenSSLError(ERR_get_error(), OFTrue); throw OrthancException(ErrorCode_InternalError, "Unable to configure cipher suite. OpenSSL error: " + boost::lexical_cast(cond.code()) + " - " + cond.text()); } if (joinedCiphersTls13.size() > 0 && SSL_CTX_set_ciphersuites(sslNativeHandle, joinedCiphersTls13.c_str()) != 1) { - OFCondition cond = DcmTLSTransportLayer::convertOpenSSLError(ERR_get_error(), OFTrue); + OFCondition cond = MyConvertOpenSSLError(ERR_get_error(), OFTrue); throw OrthancException(ErrorCode_InternalError, "Unable to configure cipher suite for TLS 1.3. OpenSSL error: " + boost::lexical_cast(cond.code()) + " - " + cond.text()); } diff -r c2817a9eb032 -r c4e33e0f907b OrthancServer/Resources/RunCppCheck.sh --- a/OrthancServer/Resources/RunCppCheck.sh Wed Jun 05 11:03:45 2024 +0200 +++ b/OrthancServer/Resources/RunCppCheck.sh Wed Jun 05 11:04:37 2024 +0200 @@ -12,33 +12,33 @@ constParameter:../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp knownArgument:../../OrthancFramework/UnitTestsSources/ImageTests.cpp knownConditionTrueFalse:../../OrthancServer/Plugins/Engine/OrthancPlugins.cpp -nullPointer:../../OrthancFramework/UnitTestsSources/RestApiTests.cpp:315 -stlFindInsert:../../OrthancFramework/Sources/DicomFormat/DicomMap.cpp:1476 -stlFindInsert:../../OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp:165 -stlFindInsert:../../OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp:73 -stlFindInsert:../../OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp:373 -stlFindInsert:../../OrthancServer/Sources/OrthancWebDav.cpp:377 -stlFindInsert:../../OrthancServer/Sources/ServerJobs/MergeStudyJob.cpp:40 -stlFindInsert:../../OrthancServer/Sources/ServerJobs/SplitStudyJob.cpp:190 -stlFindInsert:../../OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp:360 -syntaxError:../../OrthancFramework/Sources/SQLite/FunctionContext.h:52 -syntaxError:../../OrthancFramework/UnitTestsSources/DicomMapTests.cpp:73 -syntaxError:../../OrthancFramework/UnitTestsSources/ZipTests.cpp:132 -syntaxError:../../OrthancServer/UnitTestsSources/UnitTestsMain.cpp:310 -uninitMemberVar:../../OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.cpp:416 +nullPointer:../../OrthancFramework/UnitTestsSources/RestApiTests.cpp:316 +stlFindInsert:../../OrthancFramework/Sources/DicomFormat/DicomMap.cpp:1477 +stlFindInsert:../../OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp:166 +stlFindInsert:../../OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp:74 +stlFindInsert:../../OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp:374 +stlFindInsert:../../OrthancServer/Sources/OrthancWebDav.cpp:378 +stlFindInsert:../../OrthancServer/Sources/ServerJobs/MergeStudyJob.cpp:41 +stlFindInsert:../../OrthancServer/Sources/ServerJobs/SplitStudyJob.cpp:191 +stlFindInsert:../../OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp:361 +syntaxError:../../OrthancFramework/Sources/SQLite/FunctionContext.h:53 +syntaxError:../../OrthancFramework/UnitTestsSources/DicomMapTests.cpp:74 +syntaxError:../../OrthancFramework/UnitTestsSources/ZipTests.cpp:133 +syntaxError:../../OrthancServer/UnitTestsSources/UnitTestsMain.cpp:311 +uninitMemberVar:../../OrthancServer/Sources/ServerJobs/StorageCommitmentScpJob.cpp:417 unreadVariable:../../OrthancFramework/Sources/FileStorage/StorageAccessor.cpp -unreadVariable:../../OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp:1118 +unreadVariable:../../OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp:1123 unusedFunction -useInitializationList:../../OrthancFramework/Sources/Images/PngReader.cpp:90 -useInitializationList:../../OrthancFramework/Sources/Images/PngWriter.cpp:98 -useInitializationList:../../OrthancServer/Sources/ServerJobs/DicomModalityStoreJob.cpp:274 -assertWithSideEffect:../../OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp:276 -assertWithSideEffect:../../OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp:1025 -assertWithSideEffect:../../OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp:289 -assertWithSideEffect:../../OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp:388 -assertWithSideEffect:../../OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp:3630 -assertWithSideEffect:../../OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp:285 -assertWithSideEffect:../../OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp:453 +useInitializationList:../../OrthancFramework/Sources/Images/PngReader.cpp:91 +useInitializationList:../../OrthancFramework/Sources/Images/PngWriter.cpp:99 +useInitializationList:../../OrthancServer/Sources/ServerJobs/DicomModalityStoreJob.cpp:275 +assertWithSideEffect:../../OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp:277 +assertWithSideEffect:../../OrthancServer/Plugins/Engine/OrthancPluginDatabase.cpp:1026 +assertWithSideEffect:../../OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp:290 +assertWithSideEffect:../../OrthancServer/Sources/Database/Compatibility/DatabaseLookup.cpp:389 +assertWithSideEffect:../../OrthancServer/Sources/Database/StatelessDatabaseOperations.cpp:3663 +assertWithSideEffect:../../OrthancServer/Sources/ServerJobs/ResourceModificationJob.cpp:286 +assertWithSideEffect:../../OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp:454 EOF ${CPPCHECK} --enable=all --quiet --std=c++11 \ diff -r c2817a9eb032 -r c4e33e0f907b OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp Wed Jun 05 11:03:45 2024 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp Wed Jun 05 11:04:37 2024 +0200 @@ -200,8 +200,6 @@ static void DocumentEchoShared(RestApiPostCall& call) { call.GetDocumentation() - .SetRequestField(KEY_TIMEOUT, RestApiCallDocumentation::Type_Number, - "Timeout for the C-ECHO command, in seconds", false) .SetRequestField(KEY_CHECK_FIND, RestApiCallDocumentation::Type_Boolean, "Issue a dummy C-FIND command after the C-GET SCU, in order to check whether the remote " "modality knows about Orthanc. This field defaults to the value of the `DicomEchoChecksFind` " @@ -219,6 +217,8 @@ .SetSummary("Trigger C-ECHO SCU") .SetDescription("Trigger C-ECHO SCU command against the DICOM modality whose identifier is provided in URL: " "https://orthanc.uclouvain.be/book/users/rest.html#performing-c-echo") + .SetRequestField(KEY_TIMEOUT, RestApiCallDocumentation::Type_Number, + "Timeout for the C-ECHO command, in seconds", false) .SetUriArgument("id", "Identifier of the modality of interest"); return; } diff -r c2817a9eb032 -r c4e33e0f907b OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Wed Jun 05 11:03:45 2024 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Wed Jun 05 11:04:37 2024 +0200 @@ -60,6 +60,7 @@ static const char* const IGNORE_LENGTH = "ignore-length"; static const char* const RECONSTRUCT_FILES = "ReconstructFiles"; static const char* const LIMIT_TO_THIS_LEVEL_MAIN_DICOM_TAGS = "LimitToThisLevelMainDicomTags"; +static const char* const ARG_WHOLE = "whole"; namespace Orthanc @@ -486,7 +487,8 @@ template - static void GetInstanceTagsInternal(RestApiGetCall& call) + static void GetInstanceTagsInternal(RestApiGetCall& call, + bool whole) { ServerContext& context = OrthancRestApi::GetContext(call); @@ -494,56 +496,90 @@ std::set ignoreTagLength; ParseSetOfTags(ignoreTagLength, call, IGNORE_LENGTH); - - if (format != DicomToJsonFormat_Full || - !ignoreTagLength.empty()) + + if (whole) { - Json::Value full; - context.ReadDicomAsJson(full, publicId, ignoreTagLength); - AnswerDicomAsJson(call, full, format); + // This is new in Orthanc 1.12.4. Reference: + // https://discourse.orthanc-server.org/t/private-tags-with-group-7fe0-are-not-provided-via-rest-api/4744 + const DicomToJsonFlags flags = static_cast(DicomToJsonFlags_Default & ~DicomToJsonFlags_StopAfterPixelData); + + Json::Value answer; + + { + ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), publicId); + locker.GetDicom().DatasetToJson(answer, format, flags, + ORTHANC_MAXIMUM_TAG_LENGTH, ignoreTagLength); + } + + call.GetOutput().AnswerJson(answer); } else { - // This path allows one to avoid the JSON decoding if no - // simplification is asked, and if no "ignore-length" argument - // is present - Json::Value full; - context.ReadDicomAsJson(full, publicId); - call.GetOutput().AnswerJson(full); + if (format != DicomToJsonFormat_Full || + !ignoreTagLength.empty()) + { + Json::Value full; + context.ReadDicomAsJson(full, publicId, ignoreTagLength); + AnswerDicomAsJson(call, full, format); + } + else + { + // This path allows one to avoid the JSON decoding if no + // simplification is asked, and if no "ignore-length" argument + // is present + Json::Value full; + context.ReadDicomAsJson(full, publicId); + call.GetOutput().AnswerJson(full); + } } } + static void DocumentGetInstanceTags(RestApiGetCall& call) + { + call.GetDocumentation() + .SetTag("Instances") + .SetUriArgument("id", "Orthanc identifier of the DICOM instance of interest") + .SetHttpGetArgument( + IGNORE_LENGTH, RestApiCallDocumentation::Type_JsonListOfStrings, + "Also include the DICOM tags that are provided in this list, even if their associated value is long", false) + .SetHttpGetArgument( + ARG_WHOLE, RestApiCallDocumentation::Type_Boolean, "Whether to read the whole DICOM file from the " + "storage area (new in Orthanc 1.12.4). If set to \"false\" (default value), the DICOM file is read " + "until the pixel data tag (7fe0,0010) to optimize access to storage. Setting the option " + "to \"true\" provides access to the DICOM tags stored after the pixel data tag.", false) + .AddAnswerType(MimeType_Json, "JSON object containing the DICOM tags and their associated value"); + } + + static void GetInstanceTags(RestApiGetCall& call) { if (call.IsDocumentation()) { OrthancRestApi::DocumentDicomFormat(call, DicomToJsonFormat_Full); + DocumentGetInstanceTags(call); call.GetDocumentation() - .SetTag("Instances") .SetSummary("Get DICOM tags") .SetDescription("Get the DICOM tags in the specified format. By default, the `full` format is used, which " "combines hexadecimal tags with human-readable description.") - .SetUriArgument("id", "Orthanc identifier of the DICOM instance of interest") - .SetHttpGetArgument(IGNORE_LENGTH, RestApiCallDocumentation::Type_JsonListOfStrings, - "Also include the DICOM tags that are provided in this list, even if their associated value is long", false) - .AddAnswerType(MimeType_Json, "JSON object containing the DICOM tags and their associated value") .SetTruncatedJsonHttpGetSample("https://orthanc.uclouvain.be/demo/instances/7c92ce8e-bbf67ed2-ffa3b8c1-a3b35d94-7ff3ae26/tags", 10); return; } + const bool whole = call.GetBooleanArgument(ARG_WHOLE, false); + switch (OrthancRestApi::GetDicomFormat(call, DicomToJsonFormat_Full)) { case DicomToJsonFormat_Human: - GetInstanceTagsInternal(call); + GetInstanceTagsInternal(call, whole); break; case DicomToJsonFormat_Short: - GetInstanceTagsInternal(call); + GetInstanceTagsInternal(call, whole); break; case DicomToJsonFormat_Full: - GetInstanceTagsInternal(call); + GetInstanceTagsInternal(call, whole); break; default: @@ -556,20 +592,16 @@ { if (call.IsDocumentation()) { + DocumentGetInstanceTags(call); call.GetDocumentation() - .SetTag("Instances") .SetSummary("Get human-readable tags") .SetDescription("Get the DICOM tags in human-readable format (same as the `/instances/{id}/tags?simplify` route)") - .SetUriArgument("id", "Orthanc identifier of the DICOM instance of interest") - .SetHttpGetArgument(IGNORE_LENGTH, RestApiCallDocumentation::Type_JsonListOfStrings, - "Also include the DICOM tags that are provided in this list, even if their associated value is long", false) - .AddAnswerType(MimeType_Json, "JSON object containing the DICOM tags and their associated value") .SetTruncatedJsonHttpGetSample("https://orthanc.uclouvain.be/demo/instances/7c92ce8e-bbf67ed2-ffa3b8c1-a3b35d94-7ff3ae26/simplified-tags", 10); return; } else { - GetInstanceTagsInternal(call); + GetInstanceTagsInternal(call, call.GetBooleanArgument(ARG_WHOLE, false)); } } diff -r c2817a9eb032 -r c4e33e0f907b OrthancServer/UnitTestsSources/VersionsTests.cpp --- a/OrthancServer/UnitTestsSources/VersionsTests.cpp Wed Jun 05 11:03:45 2024 +0200 +++ b/OrthancServer/UnitTestsSources/VersionsTests.cpp Wed Jun 05 11:04:37 2024 +0200 @@ -113,7 +113,7 @@ TEST(Versions, BoostStatic) { - ASSERT_TRUE(std::string(BOOST_LIB_VERSION) == "1_84" || + ASSERT_TRUE(std::string(BOOST_LIB_VERSION) == "1_85" || std::string(BOOST_LIB_VERSION) == "1_69" /* if USE_LEGACY_BOOST */); }