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-2021 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 "ServerEnumerations.h"
36 
37 #include "../../OrthancFramework/Sources/OrthancException.h"
38 #include "../../OrthancFramework/Sources/EnumerationDictionary.h"
39 #include "../../OrthancFramework/Sources/Logging.h"
40 #include "../../OrthancFramework/Sources/Toolbox.h"
41 
42 #include <boost/thread.hpp>
43 
44 namespace Orthanc
45 {
46   typedef std::map<FileContentType, std::string>  MimeTypes;
47 
48   static boost::mutex enumerationsMutex_;
49   static EnumerationDictionary<MetadataType> dictMetadataType_;
50   static EnumerationDictionary<FileContentType> dictContentType_;
51   static MimeTypes  mimeTypes_;
52 
InitializeServerEnumerations()53   void InitializeServerEnumerations()
54   {
55     boost::mutex::scoped_lock lock(enumerationsMutex_);
56 
57     dictMetadataType_.Clear();
58     dictContentType_.Clear();
59 
60     dictMetadataType_.Add(MetadataType_Instance_IndexInSeries, "IndexInSeries");
61     dictMetadataType_.Add(MetadataType_Instance_ReceptionDate, "ReceptionDate");
62     dictMetadataType_.Add(MetadataType_RemoteAet, "RemoteAET");
63     dictMetadataType_.Add(MetadataType_Series_ExpectedNumberOfInstances, "ExpectedNumberOfInstances");
64     dictMetadataType_.Add(MetadataType_ModifiedFrom, "ModifiedFrom");
65     dictMetadataType_.Add(MetadataType_AnonymizedFrom, "AnonymizedFrom");
66     dictMetadataType_.Add(MetadataType_LastUpdate, "LastUpdate");
67     dictMetadataType_.Add(MetadataType_Instance_Origin, "Origin");
68     dictMetadataType_.Add(MetadataType_Instance_TransferSyntax, "TransferSyntax");
69     dictMetadataType_.Add(MetadataType_Instance_SopClassUid, "SopClassUid");
70     dictMetadataType_.Add(MetadataType_Instance_RemoteIp, "RemoteIP");
71     dictMetadataType_.Add(MetadataType_Instance_CalledAet, "CalledAET");
72     dictMetadataType_.Add(MetadataType_Instance_HttpUsername, "HttpUsername");
73     dictMetadataType_.Add(MetadataType_Instance_PixelDataOffset, "PixelDataOffset");
74 
75     dictContentType_.Add(FileContentType_Dicom, "dicom");
76     dictContentType_.Add(FileContentType_DicomAsJson, "dicom-as-json");
77     dictContentType_.Add(FileContentType_DicomUntilPixelData, "dicom-until-pixel-data");
78   }
79 
RegisterUserMetadata(int metadata,const std::string & name)80   void RegisterUserMetadata(int metadata,
81                             const std::string& name)
82   {
83     boost::mutex::scoped_lock lock(enumerationsMutex_);
84 
85     MetadataType type = static_cast<MetadataType>(metadata);
86 
87     if (metadata < 0 ||
88         !IsUserMetadata(type))
89     {
90       LOG(ERROR) << "A user content type must have index between "
91                  << static_cast<int>(MetadataType_StartUser) << " and "
92                  << static_cast<int>(MetadataType_EndUser) << ", but \""
93                  << name << "\" has index " << metadata;
94 
95       throw OrthancException(ErrorCode_ParameterOutOfRange);
96     }
97 
98     if (dictMetadataType_.Contains(type))
99     {
100       LOG(ERROR) << "Cannot associate user content type \""
101                  << name << "\" with index " << metadata
102                  << ", as this index is already used";
103 
104       throw OrthancException(ErrorCode_ParameterOutOfRange);
105     }
106 
107     dictMetadataType_.Add(type, name);
108   }
109 
EnumerationToString(MetadataType type)110   std::string EnumerationToString(MetadataType type)
111   {
112     // This function MUST return a "std::string" and not "const
113     // char*", as the result is not a static string
114     boost::mutex::scoped_lock lock(enumerationsMutex_);
115     return dictMetadataType_.Translate(type);
116   }
117 
StringToMetadata(const std::string & str)118   MetadataType StringToMetadata(const std::string& str)
119   {
120     boost::mutex::scoped_lock lock(enumerationsMutex_);
121     return dictMetadataType_.Translate(str);
122   }
123 
RegisterUserContentType(int contentType,const std::string & name,const std::string & mime)124   void RegisterUserContentType(int contentType,
125                                const std::string& name,
126                                const std::string& mime)
127   {
128     boost::mutex::scoped_lock lock(enumerationsMutex_);
129 
130     FileContentType type = static_cast<FileContentType>(contentType);
131 
132     if (contentType < 0 ||
133         !IsUserContentType(type))
134     {
135       LOG(ERROR) << "A user content type must have index between "
136                  << static_cast<int>(FileContentType_StartUser) << " and "
137                  << static_cast<int>(FileContentType_EndUser) << ", but \""
138                  << name << "\" has index " << contentType;
139 
140       throw OrthancException(ErrorCode_ParameterOutOfRange);
141     }
142 
143     if (dictContentType_.Contains(type))
144     {
145       LOG(ERROR) << "Cannot associate user content type \""
146                  << name << "\" with index " << contentType
147                  << ", as this index is already used";
148 
149       throw OrthancException(ErrorCode_ParameterOutOfRange);
150     }
151 
152     dictContentType_.Add(type, name);
153     mimeTypes_[type] = mime;
154   }
155 
EnumerationToString(FileContentType type)156   std::string EnumerationToString(FileContentType type)
157   {
158     // This function MUST return a "std::string" and not "const
159     // char*", as the result is not a static string
160     boost::mutex::scoped_lock lock(enumerationsMutex_);
161     return dictContentType_.Translate(type);
162   }
163 
GetFileContentMime(FileContentType type)164   std::string GetFileContentMime(FileContentType type)
165   {
166     if (type >= FileContentType_StartUser &&
167         type <= FileContentType_EndUser)
168     {
169       boost::mutex::scoped_lock lock(enumerationsMutex_);
170 
171       MimeTypes::const_iterator it = mimeTypes_.find(type);
172       if (it != mimeTypes_.end())
173       {
174         return it->second;
175       }
176     }
177 
178     switch (type)
179     {
180       case FileContentType_Dicom:
181         return EnumerationToString(MimeType_Dicom);
182 
183       case FileContentType_DicomAsJson:
184         return MIME_JSON_UTF8;
185 
186       default:
187         return EnumerationToString(MimeType_Binary);
188     }
189   }
190 
StringToContentType(const std::string & str)191   FileContentType StringToContentType(const std::string& str)
192   {
193     boost::mutex::scoped_lock lock(enumerationsMutex_);
194     return dictContentType_.Translate(str);
195   }
196 
197 
StringToFindStorageAccessMode(const std::string & value)198   FindStorageAccessMode StringToFindStorageAccessMode(const std::string& value)
199   {
200     if (value == "Always")
201     {
202       return FindStorageAccessMode_DiskOnLookupAndAnswer;
203     }
204     else if (value == "Never")
205     {
206       return FindStorageAccessMode_DatabaseOnly;
207     }
208     else if (value == "Answers")
209     {
210       return FindStorageAccessMode_DiskOnAnswer;
211     }
212     else
213     {
214       throw OrthancException(ErrorCode_ParameterOutOfRange,
215                              "Configuration option \"StorageAccessOnFind\" "
216                              "should be \"Always\", \"Never\" or \"Answers\": " + value);
217     }
218   }
219 
220 
StringToBuiltinDecoderTranscoderOrder(const std::string & value)221   BuiltinDecoderTranscoderOrder StringToBuiltinDecoderTranscoderOrder(const std::string& value)
222   {
223     if (value == "Before")
224     {
225       return BuiltinDecoderTranscoderOrder_Before;
226     }
227     else if (value == "After")
228     {
229       return BuiltinDecoderTranscoderOrder_After;
230     }
231     else if (value == "Disabled")
232     {
233       return BuiltinDecoderTranscoderOrder_Disabled;
234     }
235     else
236     {
237       throw OrthancException(ErrorCode_ParameterOutOfRange,
238                              "Configuration option \"BuiltinDecoderTranscoderOrder\" "
239                              "should be \"After\", \"Before\" or \"Disabled\": " + value);
240     }
241   }
242 
243 
StringToVerbosity(const std::string & str)244   Verbosity StringToVerbosity(const std::string& str)
245   {
246     if (str == "default")
247     {
248       return Verbosity_Default;
249     }
250     else if (str == "verbose")
251     {
252       return Verbosity_Verbose;
253     }
254     else if (str == "trace")
255     {
256       return Verbosity_Trace;
257     }
258     else
259     {
260       throw OrthancException(ErrorCode_ParameterOutOfRange,
261                              "Verbosity can be \"default\", \"verbose\" or \"trace\": " + str);
262     }
263   }
264 
265 
GetBasePath(ResourceType type,const std::string & publicId)266   std::string GetBasePath(ResourceType type,
267                           const std::string& publicId)
268   {
269     switch (type)
270     {
271       case ResourceType_Patient:
272         return "/patients/" + publicId;
273 
274       case ResourceType_Study:
275         return "/studies/" + publicId;
276 
277       case ResourceType_Series:
278         return "/series/" + publicId;
279 
280       case ResourceType_Instance:
281         return "/instances/" + publicId;
282 
283       default:
284         throw OrthancException(ErrorCode_ParameterOutOfRange);
285     }
286   }
287 
EnumerationToString(SeriesStatus status)288   const char* EnumerationToString(SeriesStatus status)
289   {
290     switch (status)
291     {
292       case SeriesStatus_Complete:
293         return "Complete";
294 
295       case SeriesStatus_Missing:
296         return "Missing";
297 
298       case SeriesStatus_Inconsistent:
299         return "Inconsistent";
300 
301       case SeriesStatus_Unknown:
302         return "Unknown";
303 
304       default:
305         throw OrthancException(ErrorCode_ParameterOutOfRange);
306     }
307   }
308 
EnumerationToString(StoreStatus status)309   const char* EnumerationToString(StoreStatus status)
310   {
311     switch (status)
312     {
313       case StoreStatus_Success:
314         return "Success";
315 
316       case StoreStatus_AlreadyStored:
317         return "AlreadyStored";
318 
319       case StoreStatus_Failure:
320         return "Failure";
321 
322       case StoreStatus_FilteredOut:
323         return "FilteredOut";
324 
325       default:
326         throw OrthancException(ErrorCode_ParameterOutOfRange);
327     }
328   }
329 
330 
EnumerationToString(ChangeType type)331   const char* EnumerationToString(ChangeType type)
332   {
333     switch (type)
334     {
335       case ChangeType_CompletedSeries:
336         return "CompletedSeries";
337 
338       case ChangeType_NewInstance:
339         return "NewInstance";
340 
341       case ChangeType_NewPatient:
342         return "NewPatient";
343 
344       case ChangeType_NewSeries:
345         return "NewSeries";
346 
347       case ChangeType_NewStudy:
348         return "NewStudy";
349 
350       case ChangeType_AnonymizedStudy:
351         return "AnonymizedStudy";
352 
353       case ChangeType_AnonymizedSeries:
354         return "AnonymizedSeries";
355 
356       case ChangeType_ModifiedStudy:
357         return "ModifiedStudy";
358 
359       case ChangeType_ModifiedSeries:
360         return "ModifiedSeries";
361 
362       case ChangeType_AnonymizedPatient:
363         return "AnonymizedPatient";
364 
365       case ChangeType_ModifiedPatient:
366         return "ModifiedPatient";
367 
368       case ChangeType_StablePatient:
369         return "StablePatient";
370 
371       case ChangeType_StableStudy:
372         return "StableStudy";
373 
374       case ChangeType_StableSeries:
375         return "StableSeries";
376 
377       case ChangeType_Deleted:
378         return "Deleted";
379 
380       case ChangeType_NewChildInstance:
381         return "NewChildInstance";
382 
383       case ChangeType_UpdatedAttachment:
384         return "UpdatedAttachment";
385 
386       case ChangeType_UpdatedMetadata:
387         return "UpdatedMetadata";
388 
389       default:
390         throw OrthancException(ErrorCode_ParameterOutOfRange);
391     }
392   }
393 
394 
EnumerationToString(Verbosity verbosity)395   const char* EnumerationToString(Verbosity verbosity)
396   {
397     switch (verbosity)
398     {
399       case Verbosity_Default:
400         return "default";
401 
402       case Verbosity_Verbose:
403         return "verbose";
404 
405       case Verbosity_Trace:
406         return "trace";
407 
408       default:
409         throw OrthancException(ErrorCode_ParameterOutOfRange);
410     }
411   }
412 
413 
IsUserMetadata(MetadataType metadata)414   bool IsUserMetadata(MetadataType metadata)
415   {
416     return (metadata >= MetadataType_StartUser &&
417             metadata <= MetadataType_EndUser);
418   }
419 
420 
GetTransferSyntaxGroup(std::set<DicomTransferSyntax> & target,TransferSyntaxGroup source)421   void GetTransferSyntaxGroup(std::set<DicomTransferSyntax>& target,
422                               TransferSyntaxGroup source)
423   {
424     target.clear();
425 
426     switch (source)
427     {
428       // Transfer syntaxes supported since Orthanc 0.7.2
429       case TransferSyntaxGroup_Deflated:
430         target.insert(DicomTransferSyntax_DeflatedLittleEndianExplicit);
431         break;
432 
433       case TransferSyntaxGroup_Jpeg:
434         target.insert(DicomTransferSyntax_JPEGProcess1);
435         target.insert(DicomTransferSyntax_JPEGProcess2_4);
436         target.insert(DicomTransferSyntax_JPEGProcess3_5);
437         target.insert(DicomTransferSyntax_JPEGProcess6_8);
438         target.insert(DicomTransferSyntax_JPEGProcess7_9);
439         target.insert(DicomTransferSyntax_JPEGProcess10_12);
440         target.insert(DicomTransferSyntax_JPEGProcess11_13);
441         target.insert(DicomTransferSyntax_JPEGProcess14);
442         target.insert(DicomTransferSyntax_JPEGProcess15);
443         target.insert(DicomTransferSyntax_JPEGProcess16_18);
444         target.insert(DicomTransferSyntax_JPEGProcess17_19);
445         target.insert(DicomTransferSyntax_JPEGProcess20_22);
446         target.insert(DicomTransferSyntax_JPEGProcess21_23);
447         target.insert(DicomTransferSyntax_JPEGProcess24_26);
448         target.insert(DicomTransferSyntax_JPEGProcess25_27);
449         target.insert(DicomTransferSyntax_JPEGProcess28);
450         target.insert(DicomTransferSyntax_JPEGProcess29);
451         target.insert(DicomTransferSyntax_JPEGProcess14SV1);
452         break;
453 
454       case TransferSyntaxGroup_Jpeg2000:
455         target.insert(DicomTransferSyntax_JPEG2000);
456         target.insert(DicomTransferSyntax_JPEG2000LosslessOnly);
457         target.insert(DicomTransferSyntax_JPEG2000Multicomponent);
458         target.insert(DicomTransferSyntax_JPEG2000MulticomponentLosslessOnly);
459         break;
460 
461       case TransferSyntaxGroup_JpegLossless:
462         target.insert(DicomTransferSyntax_JPEGLSLossless);
463         target.insert(DicomTransferSyntax_JPEGLSLossy);
464         break;
465 
466       case TransferSyntaxGroup_Jpip:
467         target.insert(DicomTransferSyntax_JPIPReferenced);
468         target.insert(DicomTransferSyntax_JPIPReferencedDeflate);
469         break;
470 
471       case TransferSyntaxGroup_Mpeg2:
472         target.insert(DicomTransferSyntax_MPEG2MainProfileAtMainLevel);
473         target.insert(DicomTransferSyntax_MPEG2MainProfileAtHighLevel);
474         break;
475 
476       case TransferSyntaxGroup_Rle:
477         target.insert(DicomTransferSyntax_RLELossless);
478         break;
479 
480       case TransferSyntaxGroup_Mpeg4:
481         // New in Orthanc 1.6.0
482         target.insert(DicomTransferSyntax_MPEG4BDcompatibleHighProfileLevel4_1);
483         target.insert(DicomTransferSyntax_MPEG4HighProfileLevel4_1);
484         target.insert(DicomTransferSyntax_MPEG4HighProfileLevel4_2_For2DVideo);
485         target.insert(DicomTransferSyntax_MPEG4HighProfileLevel4_2_For3DVideo);
486         target.insert(DicomTransferSyntax_MPEG4StereoHighProfileLevel4_2);
487         break;
488 
489       case TransferSyntaxGroup_H265:
490         // New in Orthanc 1.9.0
491         target.insert(DicomTransferSyntax_HEVCMainProfileLevel5_1);
492         target.insert(DicomTransferSyntax_HEVCMain10ProfileLevel5_1);
493         break;
494 
495       default:
496         throw OrthancException(ErrorCode_ParameterOutOfRange);
497     }
498   }
499 }
500