changeset 6036:e282d55e043c attach-custom-data

merge default -> attach-custom-data
author Alain Mazy <am@orthanc.team>
date Tue, 11 Mar 2025 10:46:53 +0100
parents 83deef099c75 (current diff) cba3e8ca3a87 (diff)
children e83414b2b98d 55f171e6ea4a
files NEWS OrthancServer/Resources/Configuration.json TODO
diffstat 8 files changed, 167 insertions(+), 61 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Mon Feb 24 17:54:27 2025 +0100
+++ b/NEWS	Tue Mar 11 10:46:53 2025 +0100
@@ -39,6 +39,9 @@
 * Recovered compatibility with Windows XP that was broken because of DCMTK 3.6.9
 * Enabled support of the 1.2.840.10008.1.2.1.99 transfer syntax
   (Deflated Explicit VR Little Endian) in static builds
+* Housekeeper plugin:
+  - When encountering an error, the housekeeper now skips the resource and continues processing.
+
 
 
 Version 1.12.6 (2025-01-22)
--- a/OrthancFramework/UnitTestsSources/GithubCACertificates.h	Mon Feb 24 17:54:27 2025 +0100
+++ b/OrthancFramework/UnitTestsSources/GithubCACertificates.h	Tue Mar 11 10:46:53 2025 +0100
@@ -1,30 +1,37 @@
 #define GITHUB_CERTIFICATES  \
 "-----BEGIN CERTIFICATE-----\n"  \
-"MIIEyDCCA7CgAwIBAgIQDPW9BitWAvR6uFAsI8zwZjANBgkqhkiG9w0BAQsFADBh\n"  \
-"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"  \
-"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\n"  \
-"MjAeFw0yMTAzMzAwMDAwMDBaFw0zMTAzMjkyMzU5NTlaMFkxCzAJBgNVBAYTAlVT\n"  \
-"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMzAxBgNVBAMTKkRpZ2lDZXJ0IEdsb2Jh\n"  \
-"bCBHMiBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTCCASIwDQYJKoZIhvcNAQEBBQAD\n"  \
-"ggEPADCCAQoCggEBAMz3EGJPprtjb+2QUlbFbSd7ehJWivH0+dbn4Y+9lavyYEEV\n"  \
-"cNsSAPonCrVXOFt9slGTcZUOakGUWzUb+nv6u8W+JDD+Vu/E832X4xT1FE3LpxDy\n"  \
-"FuqrIvAxIhFhaZAmunjZlx/jfWardUSVc8is/+9dCopZQ+GssjoP80j812s3wWPc\n"  \
-"3kbW20X+fSP9kOhRBx5Ro1/tSUZUfyyIxfQTnJcVPAPooTncaQwywa8WV0yUR0J8\n"  \
-"osicfebUTVSvQpmowQTCd5zWSOTOEeAqgJnwQ3DPP3Zr0UxJqyRewg2C/Uaoq2yT\n"  \
-"zGJSQnWS+Jr6Xl6ysGHlHx+5fwmY6D36g39HaaECAwEAAaOCAYIwggF+MBIGA1Ud\n"  \
-"EwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFHSFgMBmx9833s+9KTeqAx2+7c0XMB8G\n"  \
-"A1UdIwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485MA4GA1UdDwEB/wQEAwIBhjAd\n"  \
-"BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdgYIKwYBBQUHAQEEajBoMCQG\n"  \
-"CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQAYIKwYBBQUHMAKG\n"  \
-"NGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RH\n"  \
-"Mi5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQuY29t\n"  \
-"L0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDA9BgNVHSAENjA0MAsGCWCGSAGG/WwC\n"  \
-"ATAHBgVngQwBATAIBgZngQwBAgEwCAYGZ4EMAQICMAgGBmeBDAECAzANBgkqhkiG\n"  \
-"9w0BAQsFAAOCAQEAkPFwyyiXaZd8dP3A+iZ7U6utzWX9upwGnIrXWkOH7U1MVl+t\n"  \
-"wcW1BSAuWdH/SvWgKtiwla3JLko716f2b4gp/DA/JIS7w7d7kwcsr4drdjPtAFVS\n"  \
-"slme5LnQ89/nD/7d+MS5EHKBCQRfz5eeLjJ1js+aWNJXMX43AYGyZm0pGrFmCW3R\n"  \
-"bpD0ufovARTFXFZkAdl9h6g4U5+LXUZtXMYnhIHUfoyMo5tS58aI7Dd8KvvwVVo4\n"  \
-"chDYABPPTHPbqjc1qCmBaZx2vN4Ye5DUys/vZwP9BFohFrH/6j/f3IL16/RZkiMN\n"  \
-"JCqVJUzKoZHm1Lesh3Sz8W2jmdv51b2EQJ8HmA==\n"  \
+"MIIGEzCCA/ugAwIBAgIQfVtRJrR2uhHbdBYLvFMNpzANBgkqhkiG9w0BAQwFADCB\n"  \
+"iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl\n"  \
+"cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV\n"  \
+"BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTgx\n"  \
+"MTAyMDAwMDAwWhcNMzAxMjMxMjM1OTU5WjCBjzELMAkGA1UEBhMCR0IxGzAZBgNV\n"  \
+"BAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEYMBYGA1UE\n"  \
+"ChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQDEy5TZWN0aWdvIFJTQSBEb21haW4g\n"  \
+"VmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC\n"  \
+"AQ8AMIIBCgKCAQEA1nMz1tc8INAA0hdFuNY+B6I/x0HuMjDJsGz99J/LEpgPLT+N\n"  \
+"TQEMgg8Xf2Iu6bhIefsWg06t1zIlk7cHv7lQP6lMw0Aq6Tn/2YHKHxYyQdqAJrkj\n"  \
+"eocgHuP/IJo8lURvh3UGkEC0MpMWCRAIIz7S3YcPb11RFGoKacVPAXJpz9OTTG0E\n"  \
+"oKMbgn6xmrntxZ7FN3ifmgg0+1YuWMQJDgZkW7w33PGfKGioVrCSo1yfu4iYCBsk\n"  \
+"Haswha6vsC6eep3BwEIc4gLw6uBK0u+QDrTBQBbwb4VCSmT3pDCg/r8uoydajotY\n"  \
+"uK3DGReEY+1vVv2Dy2A0xHS+5p3b4eTlygxfFQIDAQABo4IBbjCCAWowHwYDVR0j\n"  \
+"BBgwFoAUU3m/WqorSs9UgOHYm8Cd8rIDZsswHQYDVR0OBBYEFI2MXsRUrYrhd+mb\n"  \
+"+ZsF4bgBjWHhMA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMB0G\n"  \
+"A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAbBgNVHSAEFDASMAYGBFUdIAAw\n"  \
+"CAYGZ4EMAQIBMFAGA1UdHwRJMEcwRaBDoEGGP2h0dHA6Ly9jcmwudXNlcnRydXN0\n"  \
+"LmNvbS9VU0VSVHJ1c3RSU0FDZXJ0aWZpY2F0aW9uQXV0aG9yaXR5LmNybDB2Bggr\n"  \
+"BgEFBQcBAQRqMGgwPwYIKwYBBQUHMAKGM2h0dHA6Ly9jcnQudXNlcnRydXN0LmNv\n"  \
+"bS9VU0VSVHJ1c3RSU0FBZGRUcnVzdENBLmNydDAlBggrBgEFBQcwAYYZaHR0cDov\n"  \
+"L29jc3AudXNlcnRydXN0LmNvbTANBgkqhkiG9w0BAQwFAAOCAgEAMr9hvQ5Iw0/H\n"  \
+"ukdN+Jx4GQHcEx2Ab/zDcLRSmjEzmldS+zGea6TvVKqJjUAXaPgREHzSyrHxVYbH\n"  \
+"7rM2kYb2OVG/Rr8PoLq0935JxCo2F57kaDl6r5ROVm+yezu/Coa9zcV3HAO4OLGi\n"  \
+"H19+24rcRki2aArPsrW04jTkZ6k4Zgle0rj8nSg6F0AnwnJOKf0hPHzPE/uWLMUx\n"  \
+"RP0T7dWbqWlod3zu4f+k+TY4CFM5ooQ0nBnzvg6s1SQ36yOoeNDT5++SR2RiOSLv\n"  \
+"xvcRviKFxmZEJCaOEDKNyJOuB56DPi/Z+fVGjmO+wea03KbNIaiGCpXZLoUmGv38\n"  \
+"sbZXQm2V0TP2ORQGgkE49Y9Y3IBbpNV9lXj9p5v//cWoaasm56ekBYdbqbe4oyAL\n"  \
+"l6lFhd2zi+WJN44pDfwGF/Y4QA5C5BIG+3vzxhFoYt/jmPQT2BVPi7Fp2RBgvGQq\n"  \
+"6jG35LWjOhSbJuMLe/0CjraZwTiXWTb2qHSihrZe68Zk6s+go/lunrotEbaGmAhY\n"  \
+"LcmsJWTyXnW0OMGuf1pGg+pRyrbxmRE1a6Vqe8YAsOf4vmSyrcjC8azjUeqkk+B5\n"  \
+"yOGBQMkKW+ESPMFgKuOXwIlCypTPRpgSabuY0MLTDXJLR27lk8QyKGOHQ+SwMj4K\n"  \
+"00u/I5sUKUErmgQfky3xxzlIPK1aEn8=\n"  \
 "-----END CERTIFICATE-----\n"  \
 "\n" 
--- a/OrthancFramework/UnitTestsSources/RestApiTests.cpp	Mon Feb 24 17:54:27 2025 +0100
+++ b/OrthancFramework/UnitTestsSources/RestApiTests.cpp	Tue Mar 11 10:46:53 2025 +0100
@@ -99,9 +99,9 @@
 #if (UNIT_TESTS_WITH_HTTP_CONNEXIONS == 1) && (ORTHANC_ENABLE_SSL == 1) && (ORTHANC_SANDBOXED != 1)
 
 /**
-   The HTTPS CA certificates for BitBucket were extracted as follows:
+   The HTTPS CA certificates for Github were extracted as follows:
    
-   (1) We retrieve the URI of the root CA of BitBucket:
+   (1) We retrieve the URI of the root CA of Github:
 
    # echo | openssl s_client -servername raw.githubusercontent.com -connect raw.githubusercontent.com:443 2>/dev/null | openssl x509 -text | grep "CA Issuers"
 
@@ -109,7 +109,7 @@
    macro that can be used by libcurl:
 
    # cd UnitTestsSources
-   # python2 ../Resources/RetrieveCACertificates.py GITHUB_CERTIFICATES http://cacerts.digicert.com/DigiCertGlobalG2TLSRSASHA2562020CA1-1.crt > GithubCACertificates.h
+   # python2 ../Resources/RetrieveCACertificates.py GITHUB_CERTIFICATES http://crt.sectigo.com/SectigoRSADomainValidationSecureServerCA.crt > GithubCACertificates.h
 **/
 
 #include "GithubCACertificates.h"
--- a/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Mon Feb 24 17:54:27 2025 +0100
+++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.cpp	Tue Mar 11 10:46:53 2025 +0100
@@ -26,6 +26,7 @@
 #include <boost/algorithm/string/predicate.hpp>
 #include <boost/move/unique_ptr.hpp>
 #include <boost/thread.hpp>
+#include <boost/algorithm/string/join.hpp>
 
 
 #include <json/reader.h>
@@ -4077,6 +4078,26 @@
     }    
   }
 
+  void SerializeGetArguments(std::string& output, const OrthancPluginHttpRequest* request)
+  {
+    output.clear();
+    std::vector<std::string> arguments;
+    for (uint32_t i = 0; i < request->getCount; ++i)
+    {
+      if (request->getValues[i] && strlen(request->getValues[i]) > 0)
+      {
+        arguments.push_back(std::string(request->getKeys[i]) + "=" + std::string(request->getValues[i]));
+      }
+      else
+      {
+        arguments.push_back(std::string(request->getKeys[i]));
+      }
+    }
+
+    output = boost::algorithm::join(arguments, "&");
+  }
+
+
 #if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 4)
   static void SetPluginProperty(const std::string& pluginIdentifier,
                                 _OrthancPluginProperty property,
@@ -4130,6 +4151,24 @@
     httpStatus_(0)
   {
   }
+
+  RestApiClient::RestApiClient(const char* url,
+                               const OrthancPluginHttpRequest* request) :
+    method_(request->method),
+    path_(url),
+    afterPlugins_(false),
+    httpStatus_(0)
+  {
+    OrthancPlugins::GetHttpHeaders(requestHeaders_, request);
+
+    std::string getArguments;
+    OrthancPlugins::SerializeGetArguments(getArguments, request);
+
+    if (!getArguments.empty())
+    {
+      path_ += "?" + getArguments;
+    }
+  }
 #endif
 
 
@@ -4195,6 +4234,32 @@
       }
     }
   }
+
+  void RestApiClient::Forward(OrthancPluginContext* context, OrthancPluginRestOutput* output)
+  {
+    if (Execute() && httpStatus_ == 200)
+    {
+      const char* mimeType = NULL;
+      for (HttpHeaders::const_iterator h = answerHeaders_.begin(); h != answerHeaders_.end(); ++h)
+      {
+        if (h->first == "content-type")
+        {
+          mimeType = h->second.c_str();
+        }
+      }
+      
+      AnswerString(answerBody_, mimeType, output);
+    }
+    else
+    {
+      AnswerHttpError(httpStatus_, output);
+    }
+  }
+
+  bool RestApiClient::GetAnswerJson(Json::Value& output) const
+  {
+    return ReadJson(output, answerBody_);
+  }
 #endif
 
 
--- a/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Mon Feb 24 17:54:27 2025 +0100
+++ b/OrthancServer/Plugins/Samples/Common/OrthancPluginCppWrapper.h	Tue Mar 11 10:46:53 2025 +0100
@@ -1399,6 +1399,9 @@
 // helper method to convert Http headers from the plugin SDK to a std::map
 void GetHttpHeaders(HttpHeaders& result, const OrthancPluginHttpRequest* request);
 
+// helper method to re-serialize the get arguments from the SDK into a string
+void SerializeGetArguments(std::string& output, const OrthancPluginHttpRequest* request);
+
 #if HAS_ORTHANC_PLUGIN_WEBDAV == 1
   class IWebDavCollection : public boost::noncopyable
   {
@@ -1528,6 +1531,10 @@
 
   public:
     RestApiClient();
+    
+    // used to forward a call from the plugin to the core
+    RestApiClient(const char* url,
+                  const OrthancPluginHttpRequest* request);
 
     void SetMethod(OrthancPluginHttpMethod method)
     {
@@ -1584,12 +1591,17 @@
 
     bool Execute();
 
+    // Execute and forward the response as is
+    void Forward(OrthancPluginContext* context, OrthancPluginRestOutput* output);
+
     uint16_t GetHttpStatus() const;
 
     bool LookupAnswerHeader(std::string& value,
                             const std::string& key) const;
 
     const std::string& GetAnswerBody() const;
+
+    bool GetAnswerJson(Json::Value& output) const;
   };
 #endif
 }
--- a/OrthancServer/Plugins/Samples/Housekeeper/Plugin.cpp	Mon Feb 24 17:54:27 2025 +0100
+++ b/OrthancServer/Plugins/Samples/Housekeeper/Plugin.cpp	Tue Mar 11 10:46:53 2025 +0100
@@ -549,39 +549,46 @@
       const Json::Value& change = changes["Changes"][i];
       int64_t seq = change["Seq"].asInt64();
 
-      if (!limitToChange_.empty()) // if updating only maindicomtags for a single level 
+      try
       {
-        if (change["ChangeType"] == limitToChange_)
+        if (!limitToChange_.empty()) // if updating only maindicomtags for a single level 
+        {
+          if (change["ChangeType"] == limitToChange_)
+          {
+            Json::Value result;
+            Json::Value request;
+            request["ReconstructFiles"] = false;
+            request["LimitToThisLevelMainDicomTags"] = true;
+            OrthancPlugins::RestApiPost(result, "/" + limitToUrl_ + "/" + change["ID"].asString() + "/reconstruct", request, false);
+          }
+        }
+        else
         {
-          Json::Value result;
-          Json::Value request;
-          request["ReconstructFiles"] = false;
-          request["LimitToThisLevelMainDicomTags"] = true;
-          OrthancPlugins::RestApiPost(result, "/" + limitToUrl_ + "/" + change["ID"].asString() + "/reconstruct", request, false);
+          if (change["ChangeType"] == "NewStudy") // some StableStudy might be missing if orthanc was shutdown during a StableAge -> consider only the NewStudy events that can not be missed
+          {
+            Json::Value result;
+
+            if (needsReconstruct || needsReingest ||force_)
+            {
+              Json::Value request;
+              if (needsReingest)
+              {
+                request["ReconstructFiles"] = true;
+              }
+              OrthancPlugins::RestApiPost(result, "/studies/" + change["ID"].asString() + "/reconstruct", request, false);
+            }
+
+            if (needsDicomWebCaching)
+            {
+              Json::Value request;
+              OrthancPlugins::RestApiPost(result, "/studies/" + change["ID"].asString() + "/update-dicomweb-cache", request, true);
+            }
+          }
         }
       }
-      else
+      catch (...)
       {
-        if (change["ChangeType"] == "NewStudy") // some StableStudy might be missing if orthanc was shutdown during a StableAge -> consider only the NewStudy events that can not be missed
-        {
-          Json::Value result;
-
-          if (needsReconstruct || needsReingest ||force_)
-          {
-            Json::Value request;
-            if (needsReingest)
-            {
-              request["ReconstructFiles"] = true;
-            }
-            OrthancPlugins::RestApiPost(result, "/studies/" + change["ID"].asString() + "/reconstruct", request, false);
-          }
-
-          if (needsDicomWebCaching)
-          {
-            Json::Value request;
-            OrthancPlugins::RestApiPost(result, "/studies/" + change["ID"].asString() + "/update-dicomweb-cache", request, true);
-          }
-        }
+        ORTHANC_PLUGINS_LOG_ERROR("Housekeeper: unhandled error while processing change " + boost::lexical_cast<std::string>(seq) + ", skipping resource.");
       }
 
       {
@@ -703,9 +710,17 @@
   {
     if (runningPeriods_.isInPeriod())
     {
-      completed = ProcessChanges(needsReconstruct, needsReingest, needsDicomWebCaching, currentDbConfiguration);
-      SaveStatusInDb();
-      
+      try
+      {
+        completed = ProcessChanges(needsReconstruct, needsReingest, needsDicomWebCaching, currentDbConfiguration);
+        SaveStatusInDb();
+      }
+      catch (...)
+      {
+        ORTHANC_PLUGINS_LOG_ERROR("Housekeeper: unhandled error while processing change " + boost::lexical_cast<std::string>(pluginStatus_.lastProcessedChange) +
+                                 " / " + boost::lexical_cast<std::string>(pluginStatus_.lastChangeToProcess));
+      }
+
       if (!completed)
       {
         boost::recursive_mutex::scoped_lock lock(pluginStatusMutex_);
--- a/OrthancServer/Resources/Configuration.json	Mon Feb 24 17:54:27 2025 +0100
+++ b/OrthancServer/Resources/Configuration.json	Tue Mar 11 10:46:53 2025 +0100
@@ -947,7 +947,8 @@
 
   // Deidentify/anonymize the contents of the logs (notably C-FIND,
   // C-GET, and C-MOVE queries submitted to Orthanc) according to
-  // Table E.1-1 of the DICOM standard (new in Orthanc 1.8.2)
+  // Table E.1-1 of the DICOM standard (new in Orthanc 1.8.2).
+  // Note that, the DICOM logs at TRACE level are not deidentified !
   "DeidentifyLogs" : true,
 
   // If "DeidentifyLogs" is true, this sets the DICOM standard to
--- a/TODO	Mon Feb 24 17:54:27 2025 +0100
+++ b/TODO	Tue Mar 11 10:46:53 2025 +0100
@@ -198,6 +198,9 @@
 * Log outgoing C-Find queries
 * Support other Transfer Syntaxes in the Worklist plugin:
   https://discourse.orthanc-server.org/t/could-you-please-create-an-option-to-set-the-transfer-syntax-in-the-worklist-plugin-currently-little-endian-explicit-is-fixed/4871
+* Deidentify Trace level logs: https://discourse.orthanc-server.org/t/dicom-log-deidentification-at-trace-log-level/5563
+  We should implement something like GetDeidentifiedContent(const DcmDataSet& dataset) that would 
+  reproduce DcmDataSet::print but hide individual elements that contain PHI.
 
 
 ---------