1 /**
2  * Orthanc - A Lightweight, RESTful DICOM Store
3  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4  * Department, University Hospital of Liege, Belgium
5  * Copyright (C) 2017-2020 Osimis S.A., Belgium
6  *
7  * This program is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * In addition, as a special exception, the copyright holders of this
13  * program give permission to link the code of its release with the
14  * OpenSSL project's "OpenSSL" library (or with modified versions of it
15  * that use the same license as the "OpenSSL" library), and distribute
16  * the linked executables. You must obey the GNU General Public License
17  * in all respects for all of the code used other than "OpenSSL". If you
18  * modify file(s) with this exception, you may extend this exception to
19  * your version of the file(s), but you are not obligated to do so. If
20  * you do not wish to do so, delete this exception statement from your
21  * version. If you delete this exception statement from all source files
22  * in the program, then also delete it here.
23  *
24  * This program is distributed in the hope that it will be useful, but
25  * WITHOUT ANY WARRANTY; without even the implied warranty of
26  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27  * General Public License for more details.
28  *
29  * You should have received a copy of the GNU General Public License
30  * along with this program. If not, see <http://www.gnu.org/licenses/>.
31  **/
32 
33 
34 #include "PrecompiledHeadersServer.h"
35 #include "OrthancConfiguration.h"
36 
37 #include "../../OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h"
38 #include "../../OrthancFramework/Sources/HttpServer/HttpServer.h"
39 #include "../../OrthancFramework/Sources/Logging.h"
40 #include "../../OrthancFramework/Sources/OrthancException.h"
41 #include "../../OrthancFramework/Sources/SystemToolbox.h"
42 #include "../../OrthancFramework/Sources/TemporaryFile.h"
43 #include "../../OrthancFramework/Sources/Toolbox.h"
44 
45 #include "ServerIndex.h"
46 
47 
48 static const char* const DICOM_MODALITIES = "DicomModalities";
49 static const char* const DICOM_MODALITIES_IN_DB = "DicomModalitiesInDatabase";
50 static const char* const ORTHANC_PEERS = "OrthancPeers";
51 static const char* const ORTHANC_PEERS_IN_DB = "OrthancPeersInDatabase";
52 static const char* const TEMPORARY_DIRECTORY = "TemporaryDirectory";
53 
54 namespace Orthanc
55 {
AddFileToConfiguration(Json::Value & target,const boost::filesystem::path & path)56   static void AddFileToConfiguration(Json::Value& target,
57                                      const boost::filesystem::path& path)
58   {
59     std::map<std::string, std::string> env;
60     SystemToolbox::GetEnvironmentVariables(env);
61 
62     LOG(WARNING) << "Reading the configuration from: " << path;
63 
64     Json::Value config;
65 
66     {
67       std::string content;
68       SystemToolbox::ReadFile(content, path.string());
69 
70       content = Toolbox::SubstituteVariables(content, env);
71 
72       Json::Value tmp;
73       Json::Reader reader;
74       if (!reader.parse(content, tmp) ||
75           tmp.type() != Json::objectValue)
76       {
77         throw OrthancException(ErrorCode_BadJson,
78                                "The configuration file does not follow the JSON syntax: " + path.string());
79       }
80 
81       Toolbox::CopyJsonWithoutComments(config, tmp);
82     }
83 
84     if (target.size() == 0)
85     {
86       target = config;
87     }
88     else
89     {
90       // Merge the newly-added file with the previous content of "target"
91       Json::Value::Members members = config.getMemberNames();
92       for (Json::Value::ArrayIndex i = 0; i < members.size(); i++)
93       {
94         if (target.isMember(members[i]))
95         {
96           throw OrthancException(ErrorCode_BadFileFormat,
97                                  "The configuration section \"" + members[i] +
98                                  "\" is defined in 2 different configuration files");
99         }
100         else
101         {
102           target[members[i]] = config[members[i]];
103         }
104       }
105     }
106   }
107 
108 
ScanFolderForConfiguration(Json::Value & target,const char * folder)109   static void ScanFolderForConfiguration(Json::Value& target,
110                                          const char* folder)
111   {
112     using namespace boost::filesystem;
113 
114     LOG(WARNING) << "Scanning folder \"" << folder << "\" for configuration files";
115 
116     directory_iterator end_it; // default construction yields past-the-end
117     for (directory_iterator it(folder);
118          it != end_it;
119          ++it)
120     {
121       if (!is_directory(it->status()))
122       {
123         std::string extension = boost::filesystem::extension(it->path());
124         Toolbox::ToLowerCase(extension);
125 
126         if (extension == ".json")
127         {
128           AddFileToConfiguration(target, it->path().string());
129         }
130       }
131     }
132   }
133 
134 
ReadConfiguration(Json::Value & target,const char * configurationFile)135   static void ReadConfiguration(Json::Value& target,
136                                 const char* configurationFile)
137   {
138     target = Json::objectValue;
139 
140     if (configurationFile != NULL)
141     {
142       if (!boost::filesystem::exists(configurationFile))
143       {
144         throw OrthancException(ErrorCode_InexistentFile,
145                                "Inexistent path to configuration: " +
146                                std::string(configurationFile));
147       }
148 
149       if (boost::filesystem::is_directory(configurationFile))
150       {
151         ScanFolderForConfiguration(target, configurationFile);
152       }
153       else
154       {
155         AddFileToConfiguration(target, configurationFile);
156       }
157     }
158     else
159     {
160 #if ORTHANC_STANDALONE == 1
161       // No default path for the standalone configuration
162       LOG(WARNING) << "Using the default Orthanc configuration";
163       return;
164 
165 #else
166       // In a non-standalone build, we use the
167       // "Resources/Configuration.json" from the Orthanc source code
168 
169       boost::filesystem::path p = ORTHANC_PATH;
170       p /= "Resources";
171       p /= "Configuration.json";
172 
173       AddFileToConfiguration(target, p);
174 #endif
175     }
176   }
177 
178 
CheckAlphanumeric(const std::string & s)179   static void CheckAlphanumeric(const std::string& s)
180   {
181     for (size_t j = 0; j < s.size(); j++)
182     {
183       if (!isalnum(s[j]) &&
184           s[j] != '-' && s[j] != '_')
185       {
186         throw OrthancException(ErrorCode_BadFileFormat,
187                                "Only alphanumeric, dash characters and underscores are allowed "
188                                "in the names of modalities/peers, but found: " + s);
189       }
190     }
191   }
192 
193 
LoadModalitiesFromJson(const Json::Value & source)194   void OrthancConfiguration::LoadModalitiesFromJson(const Json::Value& source)
195   {
196     modalities_.clear();
197 
198     if (source.type() != Json::objectValue)
199     {
200       throw OrthancException(ErrorCode_BadFileFormat,
201                              "Bad format of the \"" + std::string(DICOM_MODALITIES) +
202                              "\" configuration section");
203     }
204 
205     Json::Value::Members members = source.getMemberNames();
206 
207     for (size_t i = 0; i < members.size(); i++)
208     {
209       const std::string& name = members[i];
210       CheckAlphanumeric(name);
211 
212       RemoteModalityParameters modality;
213       modality.Unserialize(source[name]);
214       modalities_[name] = modality;
215     }
216   }
217 
218 
LoadPeersFromJson(const Json::Value & source)219   void OrthancConfiguration::LoadPeersFromJson(const Json::Value& source)
220   {
221     peers_.clear();
222 
223     if (source.type() != Json::objectValue)
224     {
225       throw OrthancException(ErrorCode_BadFileFormat,
226                              "Bad format of the \"" + std::string(ORTHANC_PEERS) +
227                              "\" configuration section");
228     }
229 
230     Json::Value::Members members = source.getMemberNames();
231 
232     for (size_t i = 0; i < members.size(); i++)
233     {
234       const std::string& name = members[i];
235       CheckAlphanumeric(name);
236 
237       WebServiceParameters peer;
238       peer.Unserialize(source[name]);
239       peers_[name] = peer;
240     }
241   }
242 
243 
LoadModalities()244   void OrthancConfiguration::LoadModalities()
245   {
246     if (GetBooleanParameter(DICOM_MODALITIES_IN_DB, false))
247     {
248       // Modalities are stored in the database
249       if (serverIndex_ == NULL)
250       {
251         throw Orthanc::OrthancException(ErrorCode_BadSequenceOfCalls);
252       }
253       else
254       {
255         std::string property = serverIndex_->GetGlobalProperty(GlobalProperty_Modalities, "{}");
256 
257         Json::Reader reader;
258         Json::Value modalities;
259         if (reader.parse(property, modalities))
260         {
261           LoadModalitiesFromJson(modalities);
262         }
263         else
264         {
265           throw OrthancException(ErrorCode_InternalError,
266                                  "Cannot unserialize the list of modalities from the Orthanc database");
267         }
268       }
269     }
270     else
271     {
272       // Modalities are stored in the configuration files
273       if (json_.isMember(DICOM_MODALITIES))
274       {
275         LoadModalitiesFromJson(json_[DICOM_MODALITIES]);
276       }
277       else
278       {
279         modalities_.clear();
280       }
281     }
282   }
283 
LoadPeers()284   void OrthancConfiguration::LoadPeers()
285   {
286     if (GetBooleanParameter(ORTHANC_PEERS_IN_DB, false))
287     {
288       // Peers are stored in the database
289       if (serverIndex_ == NULL)
290       {
291         throw Orthanc::OrthancException(ErrorCode_BadSequenceOfCalls);
292       }
293       else
294       {
295         std::string property = serverIndex_->GetGlobalProperty(GlobalProperty_Peers, "{}");
296 
297         Json::Reader reader;
298         Json::Value peers;
299         if (reader.parse(property, peers))
300         {
301           LoadPeersFromJson(peers);
302         }
303         else
304         {
305           throw OrthancException(ErrorCode_InternalError,
306                                  "Cannot unserialize the list of peers from the Orthanc database");
307         }
308       }
309     }
310     else
311     {
312       // Peers are stored in the configuration files
313       if (json_.isMember(ORTHANC_PEERS))
314       {
315         LoadPeersFromJson(json_[ORTHANC_PEERS]);
316       }
317       else
318       {
319         peers_.clear();
320       }
321     }
322   }
323 
324 
SaveModalitiesToJson(Json::Value & target)325   void OrthancConfiguration::SaveModalitiesToJson(Json::Value& target)
326   {
327     target = Json::objectValue;
328 
329     for (Modalities::const_iterator it = modalities_.begin(); it != modalities_.end(); ++it)
330     {
331       Json::Value modality;
332       it->second.Serialize(modality, true /* force advanced format */);
333 
334       target[it->first] = modality;
335     }
336   }
337 
338 
SavePeersToJson(Json::Value & target)339   void OrthancConfiguration::SavePeersToJson(Json::Value& target)
340   {
341     target = Json::objectValue;
342 
343     for (Peers::const_iterator it = peers_.begin(); it != peers_.end(); ++it)
344     {
345       Json::Value peer;
346       it->second.Serialize(peer,
347                            false /* use simple format if possible */,
348                            true  /* include passwords */);
349 
350       target[it->first] = peer;
351     }
352   }
353 
354 
SaveModalities()355   void OrthancConfiguration::SaveModalities()
356   {
357     if (GetBooleanParameter(DICOM_MODALITIES_IN_DB, false))
358     {
359       // Modalities are stored in the database
360       if (serverIndex_ == NULL)
361       {
362         throw Orthanc::OrthancException(ErrorCode_BadSequenceOfCalls);
363       }
364       else
365       {
366         Json::Value modalities;
367         SaveModalitiesToJson(modalities);
368 
369         Json::FastWriter writer;
370         std::string s = writer.write(modalities);
371 
372         serverIndex_->SetGlobalProperty(GlobalProperty_Modalities, s);
373       }
374     }
375     else
376     {
377       // Modalities are stored in the configuration files
378       if (!modalities_.empty() ||
379           json_.isMember(DICOM_MODALITIES))
380       {
381         SaveModalitiesToJson(json_[DICOM_MODALITIES]);
382       }
383     }
384   }
385 
386 
SavePeers()387   void OrthancConfiguration::SavePeers()
388   {
389     if (GetBooleanParameter(ORTHANC_PEERS_IN_DB, false))
390     {
391       // Peers are stored in the database
392       if (serverIndex_ == NULL)
393       {
394         throw Orthanc::OrthancException(ErrorCode_BadSequenceOfCalls);
395       }
396       else
397       {
398         Json::Value peers;
399         SavePeersToJson(peers);
400 
401         Json::FastWriter writer;
402         std::string s = writer.write(peers);
403 
404         serverIndex_->SetGlobalProperty(GlobalProperty_Peers, s);
405       }
406     }
407     else
408     {
409       // Peers are stored in the configuration files
410       if (!peers_.empty() ||
411           json_.isMember(ORTHANC_PEERS))
412       {
413         SavePeersToJson(json_[ORTHANC_PEERS]);
414       }
415     }
416   }
417 
418 
GetInstance()419   OrthancConfiguration& OrthancConfiguration::GetInstance()
420   {
421     static OrthancConfiguration configuration;
422     return configuration;
423   }
424 
425 
LookupStringParameter(std::string & target,const std::string & parameter) const426   bool OrthancConfiguration::LookupStringParameter(std::string& target,
427                                                    const std::string& parameter) const
428   {
429     if (json_.isMember(parameter))
430     {
431       if (json_[parameter].type() != Json::stringValue)
432       {
433         throw OrthancException(ErrorCode_BadParameterType,
434                                "The configuration option \"" + parameter + "\" must be a string");
435       }
436       else
437       {
438         target = json_[parameter].asString();
439         return true;
440       }
441     }
442     else
443     {
444       return false;
445     }
446   }
447 
448 
GetStringParameter(const std::string & parameter,const std::string & defaultValue) const449   std::string OrthancConfiguration::GetStringParameter(const std::string& parameter,
450                                                        const std::string& defaultValue) const
451   {
452     std::string value;
453     if (LookupStringParameter(value, parameter))
454     {
455       return value;
456     }
457     else
458     {
459       return defaultValue;
460     }
461   }
462 
463 
GetIntegerParameter(const std::string & parameter,int defaultValue) const464   int OrthancConfiguration::GetIntegerParameter(const std::string& parameter,
465                                                 int defaultValue) const
466   {
467     if (json_.isMember(parameter))
468     {
469       if (json_[parameter].type() != Json::intValue)
470       {
471         throw OrthancException(ErrorCode_BadParameterType,
472                                "The configuration option \"" + parameter + "\" must be an integer");
473       }
474       else
475       {
476         return json_[parameter].asInt();
477       }
478     }
479     else
480     {
481       return defaultValue;
482     }
483   }
484 
485 
GetUnsignedIntegerParameter(const std::string & parameter,unsigned int defaultValue) const486   unsigned int OrthancConfiguration::GetUnsignedIntegerParameter(
487     const std::string& parameter,
488     unsigned int defaultValue) const
489   {
490     int v = GetIntegerParameter(parameter, defaultValue);
491 
492     if (v < 0)
493     {
494       throw OrthancException(ErrorCode_ParameterOutOfRange,
495                              "The configuration option \"" + parameter + "\" must be a positive integer");
496     }
497     else
498     {
499       return static_cast<unsigned int>(v);
500     }
501   }
502 
503 
LookupBooleanParameter(bool & target,const std::string & parameter) const504   bool OrthancConfiguration::LookupBooleanParameter(bool& target,
505                                                     const std::string& parameter) const
506   {
507     if (json_.isMember(parameter))
508     {
509       if (json_[parameter].type() != Json::booleanValue)
510       {
511         throw OrthancException(ErrorCode_BadParameterType,
512                                "The configuration option \"" + parameter +
513                                "\" must be a Boolean (true or false)");
514       }
515       else
516       {
517         target = json_[parameter].asBool();
518         return true;
519       }
520     }
521     else
522     {
523       return false;
524     }
525   }
526 
527 
GetBooleanParameter(const std::string & parameter,bool defaultValue) const528   bool OrthancConfiguration::GetBooleanParameter(const std::string& parameter,
529                                                  bool defaultValue) const
530   {
531     bool value;
532     if (LookupBooleanParameter(value, parameter))
533     {
534       return value;
535     }
536     else
537     {
538       return defaultValue;
539     }
540   }
541 
542 
Read(const char * configurationFile)543   void OrthancConfiguration::Read(const char* configurationFile)
544   {
545     // Read the content of the configuration
546     configurationFileArg_ = configurationFile;
547     ReadConfiguration(json_, configurationFile);
548 
549     // Adapt the paths to the configurations
550     defaultDirectory_ = boost::filesystem::current_path();
551     configurationAbsolutePath_ = "";
552 
553     if (configurationFile)
554     {
555       if (boost::filesystem::is_directory(configurationFile))
556       {
557         defaultDirectory_ = boost::filesystem::path(configurationFile);
558         configurationAbsolutePath_ = boost::filesystem::absolute(configurationFile).parent_path().string();
559       }
560       else
561       {
562         defaultDirectory_ = boost::filesystem::path(configurationFile).parent_path();
563         configurationAbsolutePath_ = boost::filesystem::absolute(configurationFile).string();
564       }
565     }
566     else
567     {
568 #if ORTHANC_STANDALONE != 1
569       // In a non-standalone build, we use the
570       // "Resources/Configuration.json" from the Orthanc source code
571 
572       boost::filesystem::path p = ORTHANC_PATH;
573       p /= "Resources";
574       p /= "Configuration.json";
575       configurationAbsolutePath_ = boost::filesystem::absolute(p).string();
576 #endif
577     }
578   }
579 
580 
LoadModalitiesAndPeers()581   void OrthancConfiguration::LoadModalitiesAndPeers()
582   {
583     LoadModalities();
584     LoadPeers();
585   }
586 
587 
RegisterFont(ServerResources::FileResourceId resource)588   void OrthancConfiguration::RegisterFont(ServerResources::FileResourceId resource)
589   {
590     std::string content;
591     ServerResources::GetFileResource(content, resource);
592     fontRegistry_.AddFromMemory(content);
593   }
594 
595 
GetDicomModalityUsingSymbolicName(RemoteModalityParameters & modality,const std::string & name) const596   void OrthancConfiguration::GetDicomModalityUsingSymbolicName(
597     RemoteModalityParameters& modality,
598     const std::string& name) const
599   {
600     Modalities::const_iterator found = modalities_.find(name);
601 
602     if (found == modalities_.end())
603     {
604       throw OrthancException(ErrorCode_InexistentItem,
605                              "No modality with symbolic name: " + name);
606     }
607     else
608     {
609       modality = found->second;
610     }
611   }
612 
613 
LookupOrthancPeer(WebServiceParameters & peer,const std::string & name) const614   bool OrthancConfiguration::LookupOrthancPeer(WebServiceParameters& peer,
615                                                const std::string& name) const
616   {
617     Peers::const_iterator found = peers_.find(name);
618 
619     if (found == peers_.end())
620     {
621       LOG(ERROR) << "No peer with symbolic name: " << name;
622       return false;
623     }
624     else
625     {
626       peer = found->second;
627       return true;
628     }
629   }
630 
631 
GetListOfDicomModalities(std::set<std::string> & target) const632   void OrthancConfiguration::GetListOfDicomModalities(std::set<std::string>& target) const
633   {
634     target.clear();
635 
636     for (Modalities::const_iterator
637            it = modalities_.begin(); it != modalities_.end(); ++it)
638     {
639       target.insert(it->first);
640     }
641   }
642 
643 
GetListOfOrthancPeers(std::set<std::string> & target) const644   void OrthancConfiguration::GetListOfOrthancPeers(std::set<std::string>& target) const
645   {
646     target.clear();
647 
648     for (Peers::const_iterator it = peers_.begin(); it != peers_.end(); ++it)
649     {
650       target.insert(it->first);
651     }
652   }
653 
654 
SetupRegisteredUsers(HttpServer & httpServer) const655   bool OrthancConfiguration::SetupRegisteredUsers(HttpServer& httpServer) const
656   {
657     httpServer.ClearUsers();
658 
659     if (!json_.isMember("RegisteredUsers"))
660     {
661       return false;
662     }
663 
664     const Json::Value& users = json_["RegisteredUsers"];
665     if (users.type() != Json::objectValue)
666     {
667       throw OrthancException(ErrorCode_BadFileFormat, "Badly formatted list of users");
668     }
669 
670     bool hasUser = false;
671     Json::Value::Members usernames = users.getMemberNames();
672     for (size_t i = 0; i < usernames.size(); i++)
673     {
674       const std::string& username = usernames[i];
675       std::string password = users[username].asString();
676       httpServer.RegisterUser(username.c_str(), password.c_str());
677       hasUser = true;
678     }
679 
680     return hasUser;
681   }
682 
683 
InterpretStringParameterAsPath(const std::string & parameter) const684   std::string OrthancConfiguration::InterpretStringParameterAsPath(
685     const std::string& parameter) const
686   {
687     return SystemToolbox::InterpretRelativePath(defaultDirectory_.string(), parameter);
688   }
689 
690 
GetListOfStringsParameter(std::list<std::string> & target,const std::string & key) const691   void OrthancConfiguration::GetListOfStringsParameter(std::list<std::string>& target,
692                                                        const std::string& key) const
693   {
694     target.clear();
695 
696     if (!json_.isMember(key))
697     {
698       return;
699     }
700 
701     const Json::Value& lst = json_[key];
702 
703     if (lst.type() != Json::arrayValue)
704     {
705       throw OrthancException(ErrorCode_BadFileFormat, "Badly formatted list of strings");
706     }
707 
708     for (Json::Value::ArrayIndex i = 0; i < lst.size(); i++)
709     {
710       target.push_back(lst[i].asString());
711     }
712   }
713 
714 
IsSameAETitle(const std::string & aet1,const std::string & aet2) const715   bool OrthancConfiguration::IsSameAETitle(const std::string& aet1,
716                                            const std::string& aet2) const
717   {
718     if (GetBooleanParameter("StrictAetComparison", false))
719     {
720       // Case-sensitive matching
721       return aet1 == aet2;
722     }
723     else
724     {
725       // Case-insensitive matching (default)
726       std::string tmp1, tmp2;
727       Toolbox::ToLowerCase(tmp1, aet1);
728       Toolbox::ToLowerCase(tmp2, aet2);
729       return tmp1 == tmp2;
730     }
731   }
732 
733 
LookupDicomModalityUsingAETitle(RemoteModalityParameters & modality,const std::string & aet) const734   bool OrthancConfiguration::LookupDicomModalityUsingAETitle(RemoteModalityParameters& modality,
735                                                              const std::string& aet) const
736   {
737     for (Modalities::const_iterator it = modalities_.begin(); it != modalities_.end(); ++it)
738     {
739       if (IsSameAETitle(aet, it->second.GetApplicationEntityTitle()))
740       {
741         modality = it->second;
742         return true;
743       }
744     }
745 
746     return false;
747   }
748 
LookupDicomModalitiesUsingAETitle(std::list<RemoteModalityParameters> & modalities,const std::string & aet) const749   bool OrthancConfiguration::LookupDicomModalitiesUsingAETitle(std::list<RemoteModalityParameters>& modalities,
750                                                                const std::string& aet) const
751   {
752     modalities.clear();
753 
754     for (Modalities::const_iterator it = modalities_.begin(); it != modalities_.end(); ++it)
755     {
756       if (IsSameAETitle(aet, it->second.GetApplicationEntityTitle()))
757       {
758         modalities.push_back(it->second);
759       }
760     }
761 
762     return modalities.size() > 0;
763   }
764 
765 
766 
IsKnownAETitle(const std::string & aet,const std::string & ip) const767   bool OrthancConfiguration::IsKnownAETitle(const std::string& aet,
768                                             const std::string& ip) const
769   {
770     RemoteModalityParameters modality;
771 
772     if (!LookupDicomModalityUsingAETitle(modality, aet))
773     {
774       LOG(WARNING) << "Modality \"" << aet
775                    << "\" is not listed in the \"DicomModalities\" configuration option";
776       return false;
777     }
778     else if (!GetBooleanParameter("DicomCheckModalityHost", false) ||
779              ip == modality.GetHost())
780     {
781       return true;
782     }
783     else
784     {
785       LOG(WARNING) << "Forbidding access from AET \"" << aet
786                    << "\" given its hostname (" << ip << ") does not match "
787                    << "the \"DicomModalities\" configuration option ("
788                    << modality.GetHost() << " was expected)";
789       return false;
790     }
791   }
792 
793 
794   RemoteModalityParameters
GetModalityUsingSymbolicName(const std::string & name) const795   OrthancConfiguration::GetModalityUsingSymbolicName(const std::string& name) const
796   {
797     RemoteModalityParameters modality;
798     GetDicomModalityUsingSymbolicName(modality, name);
799 
800     return modality;
801   }
802 
803 
804   RemoteModalityParameters
GetModalityUsingAet(const std::string & aet) const805   OrthancConfiguration::GetModalityUsingAet(const std::string& aet) const
806   {
807     RemoteModalityParameters modality;
808 
809     if (LookupDicomModalityUsingAETitle(modality, aet))
810     {
811       return modality;
812     }
813     else
814     {
815       throw OrthancException(ErrorCode_InexistentItem,
816                              "Unknown modality for AET: " + aet);
817     }
818   }
819 
820 
UpdateModality(const std::string & symbolicName,const RemoteModalityParameters & modality)821   void OrthancConfiguration::UpdateModality(const std::string& symbolicName,
822                                             const RemoteModalityParameters& modality)
823   {
824     CheckAlphanumeric(symbolicName);
825 
826     modalities_[symbolicName] = modality;
827     SaveModalities();
828   }
829 
830 
RemoveModality(const std::string & symbolicName)831   void OrthancConfiguration::RemoveModality(const std::string& symbolicName)
832   {
833     if (modalities_.find(symbolicName) == modalities_.end())
834     {
835       throw OrthancException(ErrorCode_InexistentItem,
836                              "Unknown DICOM modality with symbolic name: " + symbolicName);
837     }
838     else
839     {
840       modalities_.erase(symbolicName);
841       SaveModalities();
842     }
843   }
844 
845 
UpdatePeer(const std::string & symbolicName,const WebServiceParameters & peer)846   void OrthancConfiguration::UpdatePeer(const std::string& symbolicName,
847                                         const WebServiceParameters& peer)
848   {
849     CheckAlphanumeric(symbolicName);
850 
851     peer.CheckClientCertificate();
852 
853     peers_[symbolicName] = peer;
854     SavePeers();
855   }
856 
857 
RemovePeer(const std::string & symbolicName)858   void OrthancConfiguration::RemovePeer(const std::string& symbolicName)
859   {
860     if (peers_.find(symbolicName) == peers_.end())
861     {
862       throw OrthancException(ErrorCode_InexistentItem,
863                              "Unknown Orthanc peer: " + symbolicName);
864     }
865     else
866     {
867       peers_.erase(symbolicName);
868       SavePeers();
869     }
870   }
871 
872 
Format(std::string & result) const873   void OrthancConfiguration::Format(std::string& result) const
874   {
875     Json::StyledWriter w;
876     result = w.write(json_);
877   }
878 
879 
SetDefaultEncoding(Encoding encoding)880   void OrthancConfiguration::SetDefaultEncoding(Encoding encoding)
881   {
882     SetDefaultDicomEncoding(encoding);
883 
884     // Propagate the encoding to the configuration file that is
885     // stored in memory
886     json_["DefaultEncoding"] = EnumerationToString(encoding);
887   }
888 
889 
HasConfigurationChanged() const890   bool OrthancConfiguration::HasConfigurationChanged() const
891   {
892     Json::Value current;
893     ReadConfiguration(current, configurationFileArg_);
894 
895     Json::FastWriter writer;
896     std::string a = writer.write(json_);
897     std::string b = writer.write(current);
898 
899     return a != b;
900   }
901 
902 
SetServerIndex(ServerIndex & index)903   void OrthancConfiguration::SetServerIndex(ServerIndex& index)
904   {
905     serverIndex_ = &index;
906   }
907 
908 
ResetServerIndex()909   void OrthancConfiguration::ResetServerIndex()
910   {
911     serverIndex_ = NULL;
912   }
913 
914 
CreateTemporaryFile() const915   TemporaryFile* OrthancConfiguration::CreateTemporaryFile() const
916   {
917     if (json_.isMember(TEMPORARY_DIRECTORY))
918     {
919       return new TemporaryFile(InterpretStringParameterAsPath(GetStringParameter(TEMPORARY_DIRECTORY, ".")), "");
920     }
921     else
922     {
923       return new TemporaryFile;
924     }
925   }
926 
927 
GetDefaultPrivateCreator() const928   std::string OrthancConfiguration::GetDefaultPrivateCreator() const
929   {
930     // New configuration option in Orthanc 1.6.0
931     return GetStringParameter("DefaultPrivateCreator", "");
932   }
933 
934 
DefaultExtractDicomSummary(DicomMap & target,const ParsedDicomFile & dicom)935   void OrthancConfiguration::DefaultExtractDicomSummary(DicomMap& target,
936                                                         const ParsedDicomFile& dicom)
937   {
938     std::set<DicomTag> ignoreTagLength;
939     dicom.ExtractDicomSummary(target, ORTHANC_MAXIMUM_TAG_LENGTH, ignoreTagLength);
940   }
941 
942 
DefaultDicomDatasetToJson(Json::Value & target,const ParsedDicomFile & dicom)943   void OrthancConfiguration::DefaultDicomDatasetToJson(Json::Value& target,
944                                                        const ParsedDicomFile& dicom)
945   {
946     std::set<DicomTag> ignoreTagLength;
947     DefaultDicomDatasetToJson(target, dicom, ignoreTagLength);
948   }
949 
950 
DefaultDicomDatasetToJson(Json::Value & target,const ParsedDicomFile & dicom,const std::set<DicomTag> & ignoreTagLength)951   void OrthancConfiguration::DefaultDicomDatasetToJson(Json::Value& target,
952                                                        const ParsedDicomFile& dicom,
953                                                        const std::set<DicomTag>& ignoreTagLength)
954   {
955     dicom.DatasetToJson(target, DicomToJsonFormat_Full, DicomToJsonFlags_Default,
956                         ORTHANC_MAXIMUM_TAG_LENGTH, ignoreTagLength);
957   }
958 
959 
DefaultDicomHeaderToJson(Json::Value & target,const ParsedDicomFile & dicom)960   void OrthancConfiguration::DefaultDicomHeaderToJson(Json::Value& target,
961                                                       const ParsedDicomFile& dicom)
962   {
963     dicom.HeaderToJson(target, DicomToJsonFormat_Full);
964   }
965 }
966