changeset 4473:68f52897c119

new URIs: /tools/accepted-transfer-syntaxes and /tools/unknown-sop-class-accepted to replace Lua callbacks for transfer syntaxes
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 26 Jan 2021 14:48:10 +0100
parents 28a4baadde17
children f8c1d94363b6
files NEWS OrthancFramework/Resources/CodeGeneration/GenerateTransferSyntaxesEnumerations.mustache OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp OrthancFramework/Sources/Enumerations.h OrthancFramework/Sources/Enumerations_TransferSyntaxes.impl.h OrthancFramework/Sources/RestApi/RestApiCall.cpp OrthancFramework/Sources/RestApi/RestApiCall.h OrthancFramework/Sources/RestApi/RestApiPostCall.h OrthancFramework/Sources/RestApi/RestApiPutCall.h OrthancServer/Resources/DicomConformanceStatement.txt OrthancServer/Resources/Samples/Lua/TransferSyntaxDisable.lua OrthancServer/Resources/Samples/Lua/TransferSyntaxEnable.lua OrthancServer/Sources/OrthancConfiguration.cpp OrthancServer/Sources/OrthancConfiguration.h OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp OrthancServer/Sources/ServerContext.cpp OrthancServer/Sources/ServerContext.h OrthancServer/Sources/ServerEnumerations.cpp OrthancServer/Sources/ServerEnumerations.h OrthancServer/Sources/main.cpp
diffstat 21 files changed, 356 insertions(+), 313 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Tue Jan 26 10:18:50 2021 +0100
+++ b/NEWS	Tue Jan 26 14:48:10 2021 +0100
@@ -25,8 +25,16 @@
 REST API
 --------
 
+* "/tools/accepted-transfer-syntaxes": Get/set transfer syntaxes accepted by Orthanc C-STORE SCP
+* "/tools/unknown-sop-class-accepted": Get/set whether C-STORE SCP accepts unknown SOP class UID
 * "/modalities/{...}/query": New string argument "LocalAet"
 
+Lua
+---
+
+* BREAKING CHANGE: All the Lua callbacks "IsXXXTransferSyntaxAccepted()" and
+  "IsUnknownSopClassAccepted()" have been removed
+
 Plugins
 -------
 
--- a/OrthancFramework/Resources/CodeGeneration/GenerateTransferSyntaxesEnumerations.mustache	Tue Jan 26 10:18:50 2021 +0100
+++ b/OrthancFramework/Resources/CodeGeneration/GenerateTransferSyntaxesEnumerations.mustache	Tue Jan 26 14:48:10 2021 +0100
@@ -73,7 +73,7 @@
   }
 
 
-  void GetAllTransferSyntaxes(std::set<DicomTransferSyntax>& target)
+  void GetAllDicomTransferSyntaxes(std::set<DicomTransferSyntax>& target)
   {
     target.clear();
     {{#Syntaxes}}
--- a/OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp	Tue Jan 26 10:18:50 2021 +0100
+++ b/OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp	Tue Jan 26 14:48:10 2021 +0100
@@ -76,14 +76,15 @@
 #  error The macro DCMTK_VERSION_NUMBER must be defined
 #endif
 
-#include "FindScp.h"
-#include "StoreScp.h"
-#include "MoveScp.h"
-#include "GetScp.h"
 #include "../../Compatibility.h"
-#include "../../Toolbox.h"
+#include "../../DicomParsing/FromDcmtkBridge.h"
 #include "../../Logging.h"
 #include "../../OrthancException.h"
+#include "../../Toolbox.h"
+#include "FindScp.h"
+#include "GetScp.h"
+#include "MoveScp.h"
+#include "StoreScp.h"
 
 #include <dcmtk/dcmdata/dcdeftag.h>     /* for storage commitment */
 #include <dcmtk/dcmdata/dcsequen.h>     /* for class DcmSequenceOfItems */
@@ -425,7 +426,7 @@
            * framework <= 1.8.2, where only the uncompressed transfer
            * syntaxes are accepted by default.
            **/
-          GetAllTransferSyntaxes(storageTransferSyntaxes);
+          GetAllDicomTransferSyntaxes(storageTransferSyntaxes);
         }
 
         if (storageTransferSyntaxes.empty())
@@ -446,7 +447,10 @@
            * class "DicomServer"?
            **/
           const DicomTransferSyntax PREFERRED_TRANSFER_SYNTAX = DicomTransferSyntax_LittleEndianExplicit;
-          
+
+          E_TransferSyntax dummy;
+          assert(FromDcmtkBridge::LookupDcmtkTransferSyntax(dummy, PREFERRED_TRANSFER_SYNTAX));
+
           std::vector<const char*> storageTransferSyntaxesC;
           storageTransferSyntaxesC.reserve(storageTransferSyntaxes.size());
 
@@ -456,11 +460,13 @@
           }
           
           for (std::set<DicomTransferSyntax>::const_iterator
-                 it = storageTransferSyntaxes.begin(); it != storageTransferSyntaxes.end(); ++it)
+                 syntax = storageTransferSyntaxes.begin(); syntax != storageTransferSyntaxes.end(); ++syntax)
           {
-            if (*it != PREFERRED_TRANSFER_SYNTAX)  // Don't add the preferred transfer syntax twice
+            if (*syntax != PREFERRED_TRANSFER_SYNTAX &&  // Don't add the preferred transfer syntax twice
+                // Make sure that the current version of DCMTK knows this transfer syntax
+                FromDcmtkBridge::LookupDcmtkTransferSyntax(dummy, *syntax))
             {
-              storageTransferSyntaxesC.push_back(GetTransferSyntaxUid(*it));
+              storageTransferSyntaxesC.push_back(GetTransferSyntaxUid(*syntax));
             }
           }
 
--- a/OrthancFramework/Sources/Enumerations.h	Tue Jan 26 10:18:50 2021 +0100
+++ b/OrthancFramework/Sources/Enumerations.h	Tue Jan 26 14:48:10 2021 +0100
@@ -882,5 +882,5 @@
                                   bool isLowerCase);
 
   ORTHANC_PUBLIC
-  void GetAllTransferSyntaxes(std::set<DicomTransferSyntax>& target);
+  void GetAllDicomTransferSyntaxes(std::set<DicomTransferSyntax>& target);
 }
--- a/OrthancFramework/Sources/Enumerations_TransferSyntaxes.impl.h	Tue Jan 26 10:18:50 2021 +0100
+++ b/OrthancFramework/Sources/Enumerations_TransferSyntaxes.impl.h	Tue Jan 26 14:48:10 2021 +0100
@@ -554,7 +554,7 @@
   }
 
 
-  void GetAllTransferSyntaxes(std::set<DicomTransferSyntax>& target)
+  void GetAllDicomTransferSyntaxes(std::set<DicomTransferSyntax>& target)
   {
     target.clear();
     target.insert(DicomTransferSyntax_LittleEndianImplicit);
--- a/OrthancFramework/Sources/RestApi/RestApiCall.cpp	Tue Jan 26 10:18:50 2021 +0100
+++ b/OrthancFramework/Sources/RestApi/RestApiCall.cpp	Tue Jan 26 14:48:10 2021 +0100
@@ -23,6 +23,9 @@
 #include "../PrecompiledHeaders.h"
 #include "RestApiCall.h"
 
+#include "../OrthancException.h"
+
+
 namespace Orthanc
 {
   void RestApiCall::GetUriComponentsNames(std::set<std::string>& components) const
@@ -59,4 +62,25 @@
     
     return *documentation_;
   }
+
+
+  bool RestApiCall::ParseBoolean(const std::string& value)
+  {
+    std::string stripped = Toolbox::StripSpaces(value);
+
+    if (stripped == "0" ||
+        stripped == "false")
+    {
+      return false;
+    }
+    else if (stripped == "1" ||
+             stripped == "true")
+    {
+      return true;
+    }
+    else
+    {
+      throw OrthancException(ErrorCode_BadFileFormat, "Boolean value expected");
+    }
+  }
 }
--- a/OrthancFramework/Sources/RestApi/RestApiCall.h	Tue Jan 26 10:18:50 2021 +0100
+++ b/OrthancFramework/Sources/RestApi/RestApiCall.h	Tue Jan 26 14:48:10 2021 +0100
@@ -152,5 +152,7 @@
     {
       return (origin_ == RequestOrigin_Documentation);
     }
+
+    static bool ParseBoolean(const std::string& value);
   };
 }
--- a/OrthancFramework/Sources/RestApi/RestApiPostCall.h	Tue Jan 26 10:18:50 2021 +0100
+++ b/OrthancFramework/Sources/RestApi/RestApiPostCall.h	Tue Jan 26 14:48:10 2021 +0100
@@ -72,5 +72,12 @@
     {
       return Toolbox::ReadJson(result, bodyData_, bodySize_);
     }
+
+    bool ParseBooleanBody() const
+    {
+      std::string s;
+      BodyToString(s);
+      return RestApiCall::ParseBoolean(s);
+    }
   };
 }
--- a/OrthancFramework/Sources/RestApi/RestApiPutCall.h	Tue Jan 26 10:18:50 2021 +0100
+++ b/OrthancFramework/Sources/RestApi/RestApiPutCall.h	Tue Jan 26 14:48:10 2021 +0100
@@ -71,6 +71,13 @@
     virtual bool ParseJsonRequest(Json::Value& result) const ORTHANC_OVERRIDE
     {
       return Toolbox::ReadJson(result, bodyData_, bodySize_);
-    }      
+    }
+
+    bool ParseBooleanBody() const
+    {
+      std::string s;
+      BodyToString(s);
+      return RestApiCall::ParseBoolean(s);
+    }
   };
 }
--- a/OrthancServer/Resources/DicomConformanceStatement.txt	Tue Jan 26 10:18:50 2021 +0100
+++ b/OrthancServer/Resources/DicomConformanceStatement.txt	Tue Jan 26 14:48:10 2021 +0100
@@ -251,12 +251,13 @@
   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.
+It is possible to disable a subset of these transfer syntaxes thanks
+to the "AcceptedTransferSyntaxes" and "*TransferSyntaxAccepted"
+options in the Orthanc configuration file.
 
 When possible, Orthanc will prefer the
-LittleEndianImplicitTransferSyntax transfer syntax
-(1.2.840.10008.1.2).
+LittleEndianExplicitTransferSyntax transfer syntax
+(1.2.840.10008.1.2.1).
 
 Orthanc does not support extended negotiation.
 
@@ -267,10 +268,10 @@
 
 The information above about the SCP support is readily extracted from
 the function "Orthanc::Internals::AcceptAssociation()" from file
-"Core/DicomNetworking/Internals/CommandDispatcher.cpp".
+"OrthancFramework/Sources/DicomNetworking/Internals/CommandDispatcher.cpp".
 
 The information above about the SCU support is derived from the
 classes "Orthanc::DicomControlUserConnection" and
 "Orthanc::DicomStoreUserConnection" from file
-"Core/DicomNetworking/DicomControlUserConnection.cpp" and
-"Core/DicomNetworking/DicomStoreUserConnection.cpp".
+"OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp" and
+"OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp".
--- a/OrthancServer/Resources/Samples/Lua/TransferSyntaxDisable.lua	Tue Jan 26 10:18:50 2021 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-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 IsMpeg4TransferSyntaxAccepted(aet, ip)
-   return false
-end
-
-function IsRleTransferSyntaxAccepted(aet, ip)
-   return false
-end
-
-function IsUnknownSopClassAccepted(aet, ip)
-   return false
-end
-
-print('All special transfer syntaxes are now disallowed')
--- a/OrthancServer/Resources/Samples/Lua/TransferSyntaxEnable.lua	Tue Jan 26 10:18:50 2021 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-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 IsMpeg4TransferSyntaxAccepted(aet, ip)
-   return true
-end
-
-function IsRleTransferSyntaxAccepted(aet, ip)
-   return true
-end
-
-function IsUnknownSopClassAccepted(aet, ip)
-   return true
-end
-
-print('All special transfer syntaxes are now accepted')
--- a/OrthancServer/Sources/OrthancConfiguration.cpp	Tue Jan 26 10:18:50 2021 +0100
+++ b/OrthancServer/Sources/OrthancConfiguration.cpp	Tue Jan 26 14:48:10 2021 +0100
@@ -44,6 +44,8 @@
 
 #include "ServerIndex.h"
 
+#include <boost/regex.hpp>
+
 
 static const char* const DICOM_MODALITIES = "DicomModalities";
 static const char* const DICOM_MODALITIES_IN_DB = "DicomModalitiesInDatabase";
@@ -577,8 +579,15 @@
 
   void OrthancConfiguration::LoadModalitiesAndPeers()
   {
-    LoadModalities();
-    LoadPeers();
+    if (serverIndex_ == NULL)
+    {
+      throw OrthancException(ErrorCode_BadSequenceOfCalls);
+    }
+    else
+    {
+      LoadModalities();
+      LoadPeers();
+    }
   }
 
 
@@ -928,6 +937,54 @@
   }
 
 
+  static void GetAcceptOption(std::set<DicomTransferSyntax>& target,
+                              const OrthancConfiguration& configuration,
+                              const std::string& optionName,
+                              TransferSyntaxGroup optionGroup)
+  {
+    bool accept;
+    if (configuration.LookupBooleanParameter(accept, optionName))
+    {
+      std::set<DicomTransferSyntax> group;
+      GetTransferSyntaxGroup(group, optionGroup);
+
+      for (std::set<DicomTransferSyntax>::const_iterator
+             syntax = group.begin(); syntax != group.end(); ++syntax)
+      {
+        if (accept)
+        {
+          target.insert(*syntax);
+        }
+        else
+        {
+          target.insert(*syntax);
+        }
+      }
+    }
+  }
+
+
+  void OrthancConfiguration::GetAcceptedTransferSyntaxes(std::set<DicomTransferSyntax>& target) const
+  {
+    target.clear();
+    
+    // This is the list of the transfer syntaxes that were supported up to Orthanc 0.7.1
+    target.insert(DicomTransferSyntax_LittleEndianExplicit);
+    target.insert(DicomTransferSyntax_BigEndianExplicit);
+    target.insert(DicomTransferSyntax_LittleEndianImplicit);
+
+    // Groups of transfer syntaxes, supported since Orthanc 0.7.2
+    GetAcceptOption(target, *this, "DeflatedTransferSyntaxAccepted", TransferSyntaxGroup_Deflated);
+    GetAcceptOption(target, *this, "JpegTransferSyntaxAccepted", TransferSyntaxGroup_Jpeg);
+    GetAcceptOption(target, *this, "Jpeg2000TransferSyntaxAccepted", TransferSyntaxGroup_Jpeg2000);
+    GetAcceptOption(target, *this, "JpegLosslessTransferSyntaxAccepted", TransferSyntaxGroup_JpegLossless);
+    GetAcceptOption(target, *this, "JpipTransferSyntaxAccepted", TransferSyntaxGroup_Jpip);
+    GetAcceptOption(target, *this, "Mpeg2TransferSyntaxAccepted", TransferSyntaxGroup_Mpeg2);
+    GetAcceptOption(target, *this, "Mpeg4TransferSyntaxAccepted", TransferSyntaxGroup_Mpeg4);
+    GetAcceptOption(target, *this, "RleTransferSyntaxAccepted", TransferSyntaxGroup_Rle);
+  }
+
+
   void OrthancConfiguration::DefaultExtractDicomSummary(DicomMap& target,
                                                         const ParsedDicomFile& dicom)
   {
@@ -958,4 +1015,60 @@
   {
     dicom.HeaderToJson(target, DicomToJsonFormat_Full);
   }
+
+
+  static void AddTransferSyntaxes(std::set<DicomTransferSyntax>& target,
+                                  const std::string& source)
+  {
+    boost::regex pattern(Toolbox::WildcardToRegularExpression(source));
+
+    std::set<DicomTransferSyntax> allSyntaxes;
+    GetAllDicomTransferSyntaxes(allSyntaxes);
+
+    for (std::set<DicomTransferSyntax>::const_iterator
+           syntax = allSyntaxes.begin(); syntax != allSyntaxes.end(); ++syntax)
+    {
+      if (regex_match(GetTransferSyntaxUid(*syntax), pattern))
+      {
+        target.insert(*syntax);
+      }
+    }
+  }
+
+
+  void OrthancConfiguration::ParseAcceptedTransferSyntaxes(std::set<DicomTransferSyntax>& target,
+                                                           const std::string& source)
+  {
+    Json::Value json;
+    
+    if (Toolbox::ReadJson(json, source))
+    {
+      if (json.type() == Json::stringValue)
+      {
+        AddTransferSyntaxes(target, json.asString());
+      }
+      else if (json.type() == Json::arrayValue)
+      {
+        for (Json::Value::ArrayIndex i = 0; i < json.size(); i++)
+        {
+          if (json[i].type() == Json::stringValue)
+          {
+            AddTransferSyntaxes(target, json[i].asString());
+          }
+          else
+          {
+            throw OrthancException(ErrorCode_BadFileFormat);
+          }
+        }
+      }
+      else
+      {
+        throw OrthancException(ErrorCode_BadFileFormat);
+      }
+    }
+    else
+    {
+      AddTransferSyntaxes(target, source);
+    }
+  }
 }
--- a/OrthancServer/Sources/OrthancConfiguration.h	Tue Jan 26 10:18:50 2021 +0100
+++ b/OrthancServer/Sources/OrthancConfiguration.h	Tue Jan 26 14:48:10 2021 +0100
@@ -160,6 +160,7 @@
 
     void Read(const char* configurationFile);
 
+    // "SetServerIndex()" must have been called
     void LoadModalitiesAndPeers();
     
     void RegisterFont(ServerResources::FileResourceId resource);
@@ -246,6 +247,8 @@
       return GetStringParameter("DicomAet", "ORTHANC");
     }
 
+    void GetAcceptedTransferSyntaxes(std::set<DicomTransferSyntax>& target) const;
+
     static void DefaultExtractDicomSummary(DicomMap& target,
                                            const ParsedDicomFile& dicom);
     
@@ -258,5 +261,8 @@
     
     static void DefaultDicomHeaderToJson(Json::Value& target,
                                          const ParsedDicomFile& dicom);
+
+    static void ParseAcceptedTransferSyntaxes(std::set<DicomTransferSyntax>& target,
+                                              const std::string& source);
   };
 }
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Tue Jan 26 10:18:50 2021 +0100
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Tue Jan 26 14:48:10 2021 +0100
@@ -321,24 +321,8 @@
 
     std::string publicId = call.GetUriComponent("id", "");
 
-    std::string body;
-    call.BodyToString(body);
-    body = Toolbox::StripSpaces(body);
-
-    if (body == "0")
-    {
-      context.GetIndex().SetProtectedPatient(publicId, false);
-      call.GetOutput().AnswerBuffer("", MimeType_PlainText);
-    }
-    else if (body == "1")
-    {
-      context.GetIndex().SetProtectedPatient(publicId, true);
-      call.GetOutput().AnswerBuffer("", MimeType_PlainText);
-    }
-    else
-    {
-      // Bad request
-    }
+    context.GetIndex().SetProtectedPatient(publicId, call.ParseBooleanBody());
+    call.GetOutput().AnswerBuffer("", MimeType_PlainText);
   }
 
 
@@ -1039,22 +1023,7 @@
 
         if (call.HasArgument(ARG_SMOOTH))
         {
-          std::string value = call.GetArgument(ARG_SMOOTH, "");
-          if (value == "0" ||
-              value == "false")
-          {
-            smooth = false;
-          }
-          else if (value == "1" ||
-                   value == "true")
-          {
-            smooth = true;
-          }
-          else
-          {
-            throw OrthancException(ErrorCode_ParameterOutOfRange,
-                                   "Argument must be Boolean: " + std::string(ARG_SMOOTH));
-          }
+          smooth = RestApiCall::ParseBoolean(call.GetArgument(ARG_SMOOTH, ""));
         }        
       }
                                 
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp	Tue Jan 26 10:18:50 2021 +0100
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp	Tue Jan 26 14:48:10 2021 +0100
@@ -322,6 +322,101 @@
   }
 
 
+  static void AnswerAcceptedTransferSyntaxes(RestApiCall& call)
+  {
+    std::set<DicomTransferSyntax> syntaxes;
+    OrthancRestApi::GetContext(call).GetAcceptedTransferSyntaxes(syntaxes);
+    
+    Json::Value json = Json::arrayValue;
+    for (std::set<DicomTransferSyntax>::const_iterator
+           syntax = syntaxes.begin(); syntax != syntaxes.end(); ++syntax)
+    {
+      json.append(GetTransferSyntaxUid(*syntax));
+    }
+    
+    call.GetOutput().AnswerJson(json);
+  }
+  
+
+  static void GetAcceptedTransferSyntaxes(RestApiGetCall& call)
+  {
+    if (call.IsDocumentation())
+    {
+      call.GetDocumentation()
+        .SetTag("System")
+        .SetSummary("Get accepted transfer syntaxes")
+        .SetDescription("Get the list of UIDs of the DICOM transfer syntaxes that are accepted "
+                        "by Orthanc C-STORE SCP. This corresponds to the configuration options "
+                        "`AcceptedTransferSyntaxes` and `XXXTransferSyntaxAccepted`.")
+        .AddAnswerType(MimeType_Json, "JSON array containing the transfer syntax UIDs");
+      return;
+    }
+
+    AnswerAcceptedTransferSyntaxes(call);
+  }
+
+
+  static void SetAcceptedTransferSyntaxes(RestApiPutCall& call)
+  {
+    if (call.IsDocumentation())
+    {
+      call.GetDocumentation()
+        .SetTag("System")
+        .SetSummary("Set accepted transfer syntaxes")
+        .SetDescription("Set the DICOM transfer syntaxes that accepted by Orthanc C-STORE SCP")
+        .AddRequestType(MimeType_PlainText, "UID of the transfer syntax to be accepted. "
+                        "Wildcards `?` and `*` are accepted.")
+        .AddRequestType(MimeType_Json, "JSON array containing a list of transfer syntax "
+                        "UIDs to be accepted. Wildcards `?` and `*` are accepted.")
+        .AddAnswerType(MimeType_Json, "JSON array containing the now-accepted transfer syntax UIDs");
+      return;
+    }
+
+    std::string body;
+    call.BodyToString(body);
+
+    std::set<DicomTransferSyntax> syntaxes;
+    OrthancConfiguration::ParseAcceptedTransferSyntaxes(syntaxes, body);
+    OrthancRestApi::GetContext(call).SetAcceptedTransferSyntaxes(syntaxes);
+    
+    AnswerAcceptedTransferSyntaxes(call);
+  }
+
+
+  static void GetUnknownSopClassAccepted(RestApiGetCall& call)
+  {
+    if (call.IsDocumentation())
+    {
+      call.GetDocumentation()
+        .SetTag("System")
+        .SetSummary("Is unknown SOP class accepted?")
+        .SetDescription("Shall Orthanc C-STORE SCP accept DICOM instances with an unknown SOP class UID?")
+        .AddAnswerType(MimeType_PlainText, "`1` if accepted, `0` if not accepted");
+      return;
+    }
+
+    const bool accepted = OrthancRestApi::GetContext(call).IsUnknownSopClassAccepted();
+    call.GetOutput().AnswerBuffer(accepted ? "1" : "0", MimeType_PlainText);
+  }
+
+
+  static void SetUnknownSopClassAccepted(RestApiPutCall& call)
+  {
+    if (call.IsDocumentation())
+    {
+      call.GetDocumentation()
+        .SetTag("System")
+        .SetSummary("Set unknown SOP class accepted")
+        .SetDescription("Set whether Orthanc C-STORE SCP should accept DICOM instances with an unknown SOP class UID")
+        .AddRequestType(MimeType_PlainText, "`1` if accepted, `0` if not accepted");
+      return;
+    }
+
+    OrthancRestApi::GetContext(call).SetUnknownSopClassAccepted(call.ParseBooleanBody());
+    call.GetOutput().AnswerBuffer("", MimeType_PlainText);
+  }
+
+
   
   // Plugins information ------------------------------------------------------
 
@@ -748,24 +843,7 @@
       return;
     }
 
-    bool enabled;
-
-    std::string body;
-    call.BodyToString(body);
-
-    if (body == "1")
-    {
-      enabled = true;
-    }
-    else if (body == "0")
-    {
-      enabled = false;
-    }
-    else
-    {
-      throw OrthancException(ErrorCode_ParameterOutOfRange,
-                             "The HTTP body must be 0 or 1, but found: " + body);
-    }
+    const bool enabled = call.ParseBooleanBody();
 
     // Success
     OrthancRestApi::GetContext(call).GetMetricsRegistry().SetEnabled(enabled);
@@ -919,5 +997,11 @@
     Register("/jobs/{id}/resubmit", ApplyJobAction<JobAction_Resubmit>);
     Register("/jobs/{id}/resume", ApplyJobAction<JobAction_Resume>);
     Register("/jobs/{id}/{key}", GetJobOutput);
+
+    // New in Orthanc 1.9.0
+    Register("/tools/accepted-transfer-syntaxes", GetAcceptedTransferSyntaxes);
+    Register("/tools/accepted-transfer-syntaxes", SetAcceptedTransferSyntaxes);
+    Register("/tools/unknown-sop-class-accepted", GetUnknownSopClassAccepted);
+    Register("/tools/unknown-sop-class-accepted", SetUnknownSopClassAccepted);
   }
 }
--- a/OrthancServer/Sources/ServerContext.cpp	Tue Jan 26 10:18:50 2021 +0100
+++ b/OrthancServer/Sources/ServerContext.cpp	Tue Jan 26 14:48:10 2021 +0100
@@ -391,7 +391,7 @@
           CLOG(INFO, DICOM) << "Deidentification of log contents (notably for DIMSE queries) is disabled";
         }
 
-        // New option in Orthanc 1.9.0
+        // New options in Orthanc 1.9.0
         if (lock.GetConfiguration().LookupStringParameter(s, "DicomScuPreferredTransferSyntax") &&
             !LookupTransferSyntax(preferredTransferSyntax_, s))
         {
@@ -401,6 +401,10 @@
         
         CLOG(INFO, DICOM) << "Preferred transfer syntax for Orthanc C-STORE SCU: "
                           << GetTransferSyntaxUid(preferredTransferSyntax_);
+
+        lock.GetConfiguration().GetAcceptedTransferSyntaxes(acceptedTransferSyntaxes_);
+
+        isUnknownSopClassAccepted_ = lock.GetConfiguration().GetBooleanParameter("UnknownSopClassAccepted", false);
       }
 
       jobsEngine_.SetThreadSleep(unitTesting ? 20 : 200);
@@ -1794,4 +1798,32 @@
       return element.GetValue().GetContent();
     }
   }
+
+
+  void ServerContext::GetAcceptedTransferSyntaxes(std::set<DicomTransferSyntax>& syntaxes)
+  {
+    boost::mutex::scoped_lock lock(dynamicOptionsMutex_);
+    syntaxes = acceptedTransferSyntaxes_;
+  }
+  
+
+  void ServerContext::SetAcceptedTransferSyntaxes(const std::set<DicomTransferSyntax>& syntaxes)
+  {
+    boost::mutex::scoped_lock lock(dynamicOptionsMutex_);
+    acceptedTransferSyntaxes_ = syntaxes;
+  }
+
+
+  bool ServerContext::IsUnknownSopClassAccepted()
+  {
+    boost::mutex::scoped_lock lock(dynamicOptionsMutex_);
+    return isUnknownSopClassAccepted_;
+  }
+
+  
+  void ServerContext::SetUnknownSopClassAccepted(bool accepted)
+  {
+    boost::mutex::scoped_lock lock(dynamicOptionsMutex_);
+    isUnknownSopClassAccepted_ = accepted;
+  }
 }
--- a/OrthancServer/Sources/ServerContext.h	Tue Jan 26 10:18:50 2021 +0100
+++ b/OrthancServer/Sources/ServerContext.h	Tue Jan 26 14:48:10 2021 +0100
@@ -225,7 +225,12 @@
     DicomTransferSyntax ingestTransferSyntax_;
     bool ingestTranscodingOfUncompressed_;
     bool ingestTranscodingOfCompressed_;
-    DicomTransferSyntax preferredTransferSyntax_;   // New in Orthanc 1.9.0
+
+    // New in Orthanc 1.9.0
+    DicomTransferSyntax preferredTransferSyntax_;
+    boost::mutex dynamicOptionsMutex_;
+    bool isUnknownSopClassAccepted_;
+    std::set<DicomTransferSyntax>  acceptedTransferSyntaxes_;
 
     StoreStatus StoreAfterTranscoding(std::string& resultPublicId,
                                       DicomInstanceToStore& dicom,
@@ -503,5 +508,13 @@
     }
 
     const std::string& GetDeidentifiedContent(const DicomElement& element) const;
+
+    void GetAcceptedTransferSyntaxes(std::set<DicomTransferSyntax>& syntaxes);
+
+    void SetAcceptedTransferSyntaxes(const std::set<DicomTransferSyntax>& syntaxes);
+
+    bool IsUnknownSopClassAccepted();
+
+    void SetUnknownSopClassAccepted(bool accepted);
   };
 }
--- a/OrthancServer/Sources/ServerEnumerations.cpp	Tue Jan 26 10:18:50 2021 +0100
+++ b/OrthancServer/Sources/ServerEnumerations.cpp	Tue Jan 26 14:48:10 2021 +0100
@@ -416,48 +416,10 @@
   }
 
 
-  const char* EnumerationToString(TransferSyntaxGroup syntax)
+  void GetTransferSyntaxGroup(std::set<DicomTransferSyntax>& target,
+                              TransferSyntaxGroup source)
   {
-    switch (syntax)
-    {
-      case TransferSyntaxGroup_Deflated:
-        return "Deflated";
-
-      case TransferSyntaxGroup_Jpeg:
-        return "JPEG";
-
-      case TransferSyntaxGroup_Jpeg2000:
-        return "JPEG2000";
-
-      case TransferSyntaxGroup_JpegLossless:
-        return "JPEG Lossless";
-
-      case TransferSyntaxGroup_Jpip:
-        return "JPIP";
-
-      case TransferSyntaxGroup_Mpeg2:
-        return "MPEG2";
-
-      case TransferSyntaxGroup_Mpeg4:
-        return "MPEG4";
-
-      case TransferSyntaxGroup_Rle:
-        return "RLE";
-
-      default: 
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-  }
-
-
-  void GetTransferSyntaxGroup(std::set<DicomTransferSyntax>& target,
-                              TransferSyntaxGroup source,
-                              bool clearTarget)
-  {
-    if (clearTarget)
-    {
-      target.clear();
-    }
+    target.clear();
 
     switch (source)
     {    
@@ -510,14 +472,12 @@
         break;
 
       case TransferSyntaxGroup_Mpeg4:
-#if DCMTK_VERSION_NUMBER >= 361
         // New in Orthanc 1.6.0
         target.insert(DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1);
         target.insert(DicomTransferSyntax_MPEG4HighProfileLevel4_1);
         target.insert(DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo);
         target.insert(DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo);
         target.insert(DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2);
-#endif
         break;
         
       case TransferSyntaxGroup_Rle:
--- a/OrthancServer/Sources/ServerEnumerations.h	Tue Jan 26 10:18:50 2021 +0100
+++ b/OrthancServer/Sources/ServerEnumerations.h	Tue Jan 26 14:48:10 2021 +0100
@@ -233,9 +233,6 @@
 
   bool IsUserMetadata(MetadataType type);
 
-  const char* EnumerationToString(TransferSyntaxGroup syntax);
-
   void GetTransferSyntaxGroup(std::set<DicomTransferSyntax>& target,
-                              TransferSyntaxGroup source,
-                              bool clearTarget);
+                              TransferSyntaxGroup source);
 }
--- a/OrthancServer/Sources/main.cpp	Tue Jan 26 10:18:50 2021 +0100
+++ b/OrthancServer/Sources/main.cpp	Tue Jan 26 14:48:10 2021 +0100
@@ -282,72 +282,6 @@
   bool            alwaysAllowEcho_;
   bool            alwaysAllowStore_;
 
-  bool IsAllowedTransferSyntax(const std::string& remoteIp,
-                               const std::string& remoteAet,
-                               const std::string& calledAet,
-                               TransferSyntaxGroup syntax)
-  {
-    std::string configuration;
-
-    switch (syntax)
-    {
-      case TransferSyntaxGroup_Deflated:
-        configuration = "DeflatedTransferSyntaxAccepted";
-        break;
-
-      case TransferSyntaxGroup_Jpeg:
-        configuration = "JpegTransferSyntaxAccepted";
-        break;
-
-      case TransferSyntaxGroup_Jpeg2000:
-        configuration = "Jpeg2000TransferSyntaxAccepted";
-        break;
-
-      case TransferSyntaxGroup_JpegLossless:
-        configuration = "JpegLosslessTransferSyntaxAccepted";
-        break;
-
-      case TransferSyntaxGroup_Jpip:
-        configuration = "JpipTransferSyntaxAccepted";
-        break;
-
-      case TransferSyntaxGroup_Mpeg2:
-        configuration = "Mpeg2TransferSyntaxAccepted";
-        break;
-
-      case TransferSyntaxGroup_Mpeg4:
-        configuration = "Mpeg4TransferSyntaxAccepted";
-        break;
-
-      case TransferSyntaxGroup_Rle:
-        configuration = "RleTransferSyntaxAccepted";
-        break;
-
-      default: 
-        throw OrthancException(ErrorCode_ParameterOutOfRange);
-    }
-
-    {
-      std::string name = "Is" + configuration;
-
-      LuaScripting::Lock lock(context_.GetLuaScripting());
-      
-      if (lock.GetLua().IsExistingFunction(name.c_str()))
-      {
-        LuaFunctionCall call(lock.GetLua(), name.c_str());
-        call.PushString(remoteAet);
-        call.PushString(remoteIp);
-        call.PushString(calledAet);
-        return call.ExecutePredicate();
-      }
-    }
-
-    {
-      OrthancConfiguration::ReaderLock lock;
-      return lock.GetConfiguration().GetBooleanParameter(configuration, true);
-    }
-  }
-
 public:
   explicit OrthancApplicationEntityFilter(ServerContext& context) :
     context_(context)
@@ -436,33 +370,7 @@
                                            const std::string& remoteAet,
                                            const std::string& calledAet) ORTHANC_OVERRIDE
   {
-    target.clear();
-    
-    // This is the list of the transfer syntaxes that were supported up to Orthanc 0.7.1
-    target.insert(DicomTransferSyntax_LittleEndianExplicit);
-    target.insert(DicomTransferSyntax_BigEndianExplicit);
-    target.insert(DicomTransferSyntax_LittleEndianImplicit);
-
-    // Group of transfer syntaxes, supported since Orthanc 0.7.2
-    std::set<TransferSyntaxGroup> groups;
-    groups.insert(TransferSyntaxGroup_Deflated);
-    groups.insert(TransferSyntaxGroup_Jpeg);
-    groups.insert(TransferSyntaxGroup_Jpeg2000);
-    groups.insert(TransferSyntaxGroup_JpegLossless);
-    groups.insert(TransferSyntaxGroup_Jpip);
-    groups.insert(TransferSyntaxGroup_Mpeg2);
-    groups.insert(TransferSyntaxGroup_Mpeg4);   // New in Orthanc 1.6.0
-    groups.insert(TransferSyntaxGroup_Rle);
-    assert(groups.size() == 8u);  // Number of items in enum, cf. "ServerEnumerations.h"
-
-    for (std::set<TransferSyntaxGroup>::const_iterator
-           group = groups.begin(); group != groups.end(); ++group)
-    {
-      if (IsAllowedTransferSyntax(remoteIp, remoteAet, calledAet, *group))
-      {
-        GetTransferSyntaxGroup(target, *group, false /* don't clear target */);
-      }
-    }
+    context_.GetAcceptedTransferSyntaxes(target);
   }
 
   
@@ -470,27 +378,7 @@
                                          const std::string& remoteAet,
                                          const std::string& calledAet) ORTHANC_OVERRIDE
   {
-    static const char* configuration = "UnknownSopClassAccepted";
-
-    {
-      std::string lua = "Is" + std::string(configuration);
-
-      LuaScripting::Lock lock(context_.GetLuaScripting());
-      
-      if (lock.GetLua().IsExistingFunction(lua.c_str()))
-      {
-        LuaFunctionCall call(lock.GetLua(), lua.c_str());
-        call.PushString(remoteAet);
-        call.PushString(remoteIp);
-        call.PushString(calledAet);
-        return call.ExecutePredicate();
-      }
-    }
-
-    {
-      OrthancConfiguration::ReaderLock lock;
-      return lock.GetConfiguration().GetBooleanParameter(configuration, false);
-    }
+    return context_.IsUnknownSopClassAccepted();
   }
 };
 
@@ -1536,7 +1424,7 @@
   }
 
   {
-    ServerContextConfigurator configurator(context, plugins);
+    ServerContextConfigurator configurator(context, plugins);  // This calls "OrthancConfiguration::SetServerIndex()"
 
     {
       OrthancConfiguration::WriterLock lock;