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 "OrthancFindRequestHandler.h"
36 
37 #include "../../OrthancFramework/Sources/DicomFormat/DicomArray.h"
38 #include "../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h"
39 #include "../../OrthancFramework/Sources/Logging.h"
40 #include "../../OrthancFramework/Sources/Lua/LuaFunctionCall.h"
41 #include "../../OrthancFramework/Sources/MetricsRegistry.h"
42 #include "OrthancConfiguration.h"
43 #include "Search/DatabaseLookup.h"
44 #include "ServerContext.h"
45 #include "ServerToolbox.h"
46 
47 #include <boost/regex.hpp>
48 
49 
50 namespace Orthanc
51 {
GetChildren(std::list<std::string> & target,ServerIndex & index,const std::list<std::string> & source)52   static void GetChildren(std::list<std::string>& target,
53                           ServerIndex& index,
54                           const std::list<std::string>& source)
55   {
56     target.clear();
57 
58     for (std::list<std::string>::const_iterator
59            it = source.begin(); it != source.end(); ++it)
60     {
61       std::list<std::string> tmp;
62       index.GetChildren(tmp, *it);
63       target.splice(target.end(), tmp);
64     }
65   }
66 
67 
StoreSetOfStrings(DicomMap & result,const DicomTag & tag,const std::set<std::string> & values)68   static void StoreSetOfStrings(DicomMap& result,
69                                 const DicomTag& tag,
70                                 const std::set<std::string>& values)
71   {
72     bool isFirst = true;
73 
74     std::string s;
75     for (std::set<std::string>::const_iterator
76            it = values.begin(); it != values.end(); ++it)
77     {
78       if (isFirst)
79       {
80         isFirst = false;
81       }
82       else
83       {
84         s += "\\";
85       }
86 
87       s += *it;
88     }
89 
90     result.SetValue(tag, s, false);
91   }
92 
93 
ComputePatientCounters(DicomMap & result,ServerIndex & index,const std::string & patient,const DicomMap & query)94   static void ComputePatientCounters(DicomMap& result,
95                                      ServerIndex& index,
96                                      const std::string& patient,
97                                      const DicomMap& query)
98   {
99     std::list<std::string> studies;
100     index.GetChildren(studies, patient);
101 
102     if (query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES))
103     {
104       result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES,
105                       boost::lexical_cast<std::string>(studies.size()), false);
106     }
107 
108     if (!query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES) &&
109         !query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES))
110     {
111       return;
112     }
113 
114     std::list<std::string> series;
115     GetChildren(series, index, studies);
116     studies.clear();  // This information is useless below
117 
118     if (query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES))
119     {
120       result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES,
121                       boost::lexical_cast<std::string>(series.size()), false);
122     }
123 
124     if (!query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES))
125     {
126       return;
127     }
128 
129     std::list<std::string> instances;
130     GetChildren(instances, index, series);
131 
132     if (query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES))
133     {
134       result.SetValue(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES,
135                       boost::lexical_cast<std::string>(instances.size()), false);
136     }
137   }
138 
139 
ComputeStudyCounters(DicomMap & result,ServerContext & context,const std::string & study,const DicomMap & query)140   static void ComputeStudyCounters(DicomMap& result,
141                                    ServerContext& context,
142                                    const std::string& study,
143                                    const DicomMap& query)
144   {
145     ServerIndex& index = context.GetIndex();
146 
147     std::list<std::string> series;
148     index.GetChildren(series, study);
149 
150     if (query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES))
151     {
152       result.SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES,
153                       boost::lexical_cast<std::string>(series.size()), false);
154     }
155 
156     if (query.HasTag(DICOM_TAG_MODALITIES_IN_STUDY))
157     {
158       std::set<std::string> values;
159 
160       for (std::list<std::string>::const_iterator
161              it = series.begin(); it != series.end(); ++it)
162       {
163         DicomMap tags;
164         if (index.GetMainDicomTags(tags, *it, ResourceType_Series, ResourceType_Series))
165         {
166           const DicomValue* value = tags.TestAndGetValue(DICOM_TAG_MODALITY);
167 
168           if (value != NULL &&
169               !value->IsNull() &&
170               !value->IsBinary())
171           {
172             values.insert(value->GetContent());
173           }
174         }
175       }
176 
177       StoreSetOfStrings(result, DICOM_TAG_MODALITIES_IN_STUDY, values);
178     }
179 
180     if (!query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES) &&
181         !query.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY))
182     {
183       return;
184     }
185 
186     std::list<std::string> instances;
187     GetChildren(instances, index, series);
188 
189     if (query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES))
190     {
191       result.SetValue(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES,
192                       boost::lexical_cast<std::string>(instances.size()), false);
193     }
194 
195     if (query.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY))
196     {
197       std::set<std::string> values;
198 
199       for (std::list<std::string>::const_iterator
200              it = instances.begin(); it != instances.end(); ++it)
201       {
202         std::string value;
203         if (context.LookupOrReconstructMetadata(value, *it, ResourceType_Instance, MetadataType_Instance_SopClassUid))
204         {
205           values.insert(value);
206         }
207       }
208 
209       StoreSetOfStrings(result, DICOM_TAG_SOP_CLASSES_IN_STUDY, values);
210     }
211   }
212 
213 
ComputeSeriesCounters(DicomMap & result,ServerIndex & index,const std::string & series,const DicomMap & query)214   static void ComputeSeriesCounters(DicomMap& result,
215                                     ServerIndex& index,
216                                     const std::string& series,
217                                     const DicomMap& query)
218   {
219     std::list<std::string> instances;
220     index.GetChildren(instances, series);
221 
222     if (query.HasTag(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES))
223     {
224       result.SetValue(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES,
225                       boost::lexical_cast<std::string>(instances.size()), false);
226     }
227   }
228 
229 
ComputeCounters(ServerContext & context,const std::string & instanceId,ResourceType level,const DicomMap & query)230   static DicomMap* ComputeCounters(ServerContext& context,
231                                    const std::string& instanceId,
232                                    ResourceType level,
233                                    const DicomMap& query)
234   {
235     switch (level)
236     {
237       case ResourceType_Patient:
238         if (!query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES) &&
239             !query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES) &&
240             !query.HasTag(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES))
241         {
242           return NULL;
243         }
244 
245         break;
246 
247       case ResourceType_Study:
248         if (!query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES) &&
249             !query.HasTag(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES) &&
250             !query.HasTag(DICOM_TAG_SOP_CLASSES_IN_STUDY) &&
251             !query.HasTag(DICOM_TAG_MODALITIES_IN_STUDY))
252         {
253           return NULL;
254         }
255 
256         break;
257 
258       case ResourceType_Series:
259         if (!query.HasTag(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES))
260         {
261           return NULL;
262         }
263 
264         break;
265 
266       default:
267         return NULL;
268     }
269 
270     std::string parent;
271     if (!context.GetIndex().LookupParent(parent, instanceId, level))
272     {
273       throw OrthancException(ErrorCode_UnknownResource);  // The resource was deleted in between
274     }
275 
276     std::unique_ptr<DicomMap> result(new DicomMap);
277 
278     switch (level)
279     {
280       case ResourceType_Patient:
281         ComputePatientCounters(*result, context.GetIndex(), parent, query);
282         break;
283 
284       case ResourceType_Study:
285         ComputeStudyCounters(*result, context, parent, query);
286         break;
287 
288       case ResourceType_Series:
289         ComputeSeriesCounters(*result, context.GetIndex(), parent, query);
290         break;
291 
292       default:
293         throw OrthancException(ErrorCode_InternalError);
294     }
295 
296     return result.release();
297   }
298 
299 
AddAnswer(DicomFindAnswers & answers,const DicomMap & mainDicomTags,const Json::Value * dicomAsJson,const DicomArray & query,const std::list<DicomTag> & sequencesToReturn,const DicomMap * counters,const std::string & defaultPrivateCreator,const std::map<uint16_t,std::string> & privateCreators,const std::string & retrieveAet)300   static void AddAnswer(DicomFindAnswers& answers,
301                         const DicomMap& mainDicomTags,
302                         const Json::Value* dicomAsJson,
303                         const DicomArray& query,
304                         const std::list<DicomTag>& sequencesToReturn,
305                         const DicomMap* counters,
306                         const std::string& defaultPrivateCreator,
307                         const std::map<uint16_t, std::string>& privateCreators,
308                         const std::string& retrieveAet)
309   {
310     DicomMap match;
311 
312     if (dicomAsJson != NULL)
313     {
314       match.FromDicomAsJson(*dicomAsJson);
315     }
316     else
317     {
318       match.Assign(mainDicomTags);
319     }
320 
321     DicomMap result;
322 
323     /**
324      * Add the mandatory "Retrieve AE Title (0008,0054)" tag, which was missing in Orthanc <= 1.7.2.
325      * http://dicom.nema.org/medical/dicom/current/output/html/part04.html#sect_C.4.1.1.3.2
326      * https://groups.google.com/g/orthanc-users/c/-7zNTKR_PMU/m/kfjwzEVNAgAJ
327      **/
328     result.SetValue(DICOM_TAG_RETRIEVE_AE_TITLE, retrieveAet, false /* not binary */);
329 
330     for (size_t i = 0; i < query.GetSize(); i++)
331     {
332       if (query.GetElement(i).GetTag() == DICOM_TAG_QUERY_RETRIEVE_LEVEL)
333       {
334         // Fix issue 30 on Google Code (QR response missing "Query/Retrieve Level" (008,0052))
335         result.SetValue(query.GetElement(i).GetTag(), query.GetElement(i).GetValue());
336       }
337       else if (query.GetElement(i).GetTag() == DICOM_TAG_SPECIFIC_CHARACTER_SET)
338       {
339         // Do not include the encoding, this is handled by class ParsedDicomFile
340       }
341       else
342       {
343         const DicomTag& tag = query.GetElement(i).GetTag();
344         const DicomValue* value = match.TestAndGetValue(tag);
345 
346         if (value != NULL &&
347             !value->IsNull() &&
348             !value->IsBinary())
349         {
350           result.SetValue(tag, value->GetContent(), false);
351         }
352         else
353         {
354           result.SetValue(tag, "", false);
355         }
356       }
357     }
358 
359     if (counters != NULL)
360     {
361       DicomArray tmp(*counters);
362       for (size_t i = 0; i < tmp.GetSize(); i++)
363       {
364         result.SetValue(tmp.GetElement(i).GetTag(), tmp.GetElement(i).GetValue().GetContent(), false);
365       }
366     }
367 
368     if (result.GetSize() == 0 &&
369         sequencesToReturn.empty())
370     {
371       CLOG(WARNING, DICOM) << "The C-FIND request does not return any DICOM tag";
372     }
373     else if (sequencesToReturn.empty())
374     {
375       answers.Add(result);
376     }
377     else if (dicomAsJson == NULL)
378     {
379       CLOG(WARNING, DICOM) << "C-FIND query requesting a sequence, but reading JSON from disk is disabled";
380       answers.Add(result);
381     }
382     else
383     {
384       ParsedDicomFile dicom(result, GetDefaultDicomEncoding(),
385                             true /* be permissive, cf. issue #136 */, defaultPrivateCreator, privateCreators);
386 
387       for (std::list<DicomTag>::const_iterator tag = sequencesToReturn.begin();
388            tag != sequencesToReturn.end(); ++tag)
389       {
390         assert(dicomAsJson != NULL);
391         const Json::Value& source = (*dicomAsJson) [tag->Format()];
392 
393         if (source.type() == Json::objectValue &&
394             source.isMember("Type") &&
395             source.isMember("Value") &&
396             source["Type"].asString() == "Sequence" &&
397             source["Value"].type() == Json::arrayValue)
398         {
399           Json::Value content = Json::arrayValue;
400 
401           for (Json::Value::ArrayIndex i = 0; i < source["Value"].size(); i++)
402           {
403             Json::Value item;
404             Toolbox::SimplifyDicomAsJson(item, source["Value"][i], DicomToJsonFormat_Short);
405             content.append(item);
406           }
407 
408           if (tag->IsPrivate())
409           {
410             std::map<uint16_t, std::string>::const_iterator found = privateCreators.find(tag->GetGroup());
411 
412             if (found != privateCreators.end())
413             {
414               dicom.Replace(*tag, content, false, DicomReplaceMode_InsertIfAbsent, found->second.c_str());
415             }
416             else
417             {
418               dicom.Replace(*tag, content, false, DicomReplaceMode_InsertIfAbsent, defaultPrivateCreator);
419             }
420           }
421           else
422           {
423             dicom.Replace(*tag, content, false, DicomReplaceMode_InsertIfAbsent, "" /* no private creator */);
424           }
425         }
426       }
427 
428       answers.Add(dicom);
429     }
430   }
431 
432 
433 
FilterQueryTag(std::string & value,ResourceType level,const DicomTag & tag,ModalityManufacturer manufacturer)434   bool OrthancFindRequestHandler::FilterQueryTag(std::string& value /* can be modified */,
435                                                  ResourceType level,
436                                                  const DicomTag& tag,
437                                                  ModalityManufacturer manufacturer)
438   {
439     // Whatever the manufacturer, remove the GenericGroupLength tags
440     // http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_7.2.html
441     // https://bugs.orthanc-server.com/show_bug.cgi?id=31
442     if (tag.GetElement() == 0x0000)
443     {
444       return false;
445     }
446 
447     switch (manufacturer)
448     {
449       case ModalityManufacturer_Vitrea:
450         // Following Denis Nesterov's mail on 2015-11-30
451         if (tag == DicomTag(0x5653, 0x0010))  // "PrivateCreator = Vital Images SW 3.4"
452         {
453           return false;
454         }
455 
456         break;
457 
458       default:
459         break;
460     }
461 
462     return true;
463   }
464 
465 
ApplyLuaFilter(DicomMap & target,const DicomMap & source,const std::string & remoteIp,const std::string & remoteAet,const std::string & calledAet,ModalityManufacturer manufacturer)466   bool OrthancFindRequestHandler::ApplyLuaFilter(DicomMap& target,
467                                                  const DicomMap& source,
468                                                  const std::string& remoteIp,
469                                                  const std::string& remoteAet,
470                                                  const std::string& calledAet,
471                                                  ModalityManufacturer manufacturer)
472   {
473     static const char* LUA_CALLBACK = "IncomingFindRequestFilter";
474 
475     LuaScripting::Lock lock(context_.GetLuaScripting());
476 
477     if (!lock.GetLua().IsExistingFunction(LUA_CALLBACK))
478     {
479       return false;
480     }
481     else
482     {
483       Json::Value origin;
484       FormatOrigin(origin, remoteIp, remoteAet, calledAet, manufacturer);
485 
486       LuaFunctionCall call(lock.GetLua(), LUA_CALLBACK);
487       call.PushDicom(source);
488       call.PushJson(origin);
489       call.ExecuteToDicom(target);
490 
491       return true;
492     }
493   }
494 
495 
OrthancFindRequestHandler(ServerContext & context)496   OrthancFindRequestHandler::OrthancFindRequestHandler(ServerContext& context) :
497     context_(context),
498     maxResults_(0),
499     maxInstances_(0)
500   {
501   }
502 
503 
504   class OrthancFindRequestHandler::LookupVisitor : public ServerContext::ILookupVisitor
505   {
506   private:
507     DicomFindAnswers&           answers_;
508     ServerContext&              context_;
509     ResourceType                level_;
510     const DicomMap&             query_;
511     DicomArray                  queryAsArray_;
512     const std::list<DicomTag>&  sequencesToReturn_;
513     std::string                 defaultPrivateCreator_;       // the private creator to use if the group is not defined in the query itself
514     const std::map<uint16_t, std::string>& privateCreators_;  // the private creators defined in the query itself
515     std::string                 retrieveAet_;
516 
517   public:
LookupVisitor(DicomFindAnswers & answers,ServerContext & context,ResourceType level,const DicomMap & query,const std::list<DicomTag> & sequencesToReturn,const std::map<uint16_t,std::string> & privateCreators)518     LookupVisitor(DicomFindAnswers&  answers,
519                   ServerContext& context,
520                   ResourceType level,
521                   const DicomMap& query,
522                   const std::list<DicomTag>& sequencesToReturn,
523                   const std::map<uint16_t, std::string>& privateCreators) :
524       answers_(answers),
525       context_(context),
526       level_(level),
527       query_(query),
528       queryAsArray_(query),
529       sequencesToReturn_(sequencesToReturn),
530       privateCreators_(privateCreators)
531     {
532       answers_.SetComplete(false);
533 
534       {
535         OrthancConfiguration::ReaderLock lock;
536         defaultPrivateCreator_ = lock.GetConfiguration().GetDefaultPrivateCreator();
537         retrieveAet_ = lock.GetConfiguration().GetOrthancAET();
538       }
539     }
540 
IsDicomAsJsonNeeded() const541     virtual bool IsDicomAsJsonNeeded() const ORTHANC_OVERRIDE
542     {
543       // Ask the "DICOM-as-JSON" attachment only if sequences are to
544       // be returned OR if "query_" contains non-main DICOM tags!
545 
546       DicomMap withoutSpecialTags;
547       withoutSpecialTags.Assign(query_);
548 
549       // Check out "ComputeCounters()"
550       withoutSpecialTags.Remove(DICOM_TAG_MODALITIES_IN_STUDY);
551       withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_INSTANCES);
552       withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_SERIES);
553       withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_PATIENT_RELATED_STUDIES);
554       withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_SERIES_RELATED_INSTANCES);
555       withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_STUDY_RELATED_INSTANCES);
556       withoutSpecialTags.Remove(DICOM_TAG_NUMBER_OF_STUDY_RELATED_SERIES);
557       withoutSpecialTags.Remove(DICOM_TAG_SOP_CLASSES_IN_STUDY);
558 
559       // Check out "AddAnswer()"
560       withoutSpecialTags.Remove(DICOM_TAG_SPECIFIC_CHARACTER_SET);
561       withoutSpecialTags.Remove(DICOM_TAG_QUERY_RETRIEVE_LEVEL);
562 
563       return (!sequencesToReturn_.empty() ||
564               !withoutSpecialTags.HasOnlyMainDicomTags());
565     }
566 
MarkAsComplete()567     virtual void MarkAsComplete() ORTHANC_OVERRIDE
568     {
569       answers_.SetComplete(true);
570     }
571 
Visit(const std::string & publicId,const std::string & instanceId,const DicomMap & mainDicomTags,const Json::Value * dicomAsJson)572     virtual void Visit(const std::string& publicId,
573                        const std::string& instanceId,
574                        const DicomMap& mainDicomTags,
575                        const Json::Value* dicomAsJson) ORTHANC_OVERRIDE
576     {
577       std::unique_ptr<DicomMap> counters(ComputeCounters(context_, instanceId, level_, query_));
578 
579       AddAnswer(answers_, mainDicomTags, dicomAsJson, queryAsArray_, sequencesToReturn_,
580                 counters.get(), defaultPrivateCreator_, privateCreators_, retrieveAet_);
581     }
582   };
583 
584 
Handle(DicomFindAnswers & answers,const DicomMap & input,const std::list<DicomTag> & sequencesToReturn,const std::string & remoteIp,const std::string & remoteAet,const std::string & calledAet,ModalityManufacturer manufacturer)585   void OrthancFindRequestHandler::Handle(DicomFindAnswers& answers,
586                                          const DicomMap& input,
587                                          const std::list<DicomTag>& sequencesToReturn,
588                                          const std::string& remoteIp,
589                                          const std::string& remoteAet,
590                                          const std::string& calledAet,
591                                          ModalityManufacturer manufacturer)
592   {
593     MetricsRegistry::Timer timer(context_.GetMetricsRegistry(), "orthanc_find_scp_duration_ms");
594 
595 
596     /**
597      * Deal with global configuration
598      **/
599 
600     bool caseSensitivePN;
601 
602     {
603       OrthancConfiguration::ReaderLock lock;
604       caseSensitivePN = lock.GetConfiguration().GetBooleanParameter("CaseSensitivePN", false);
605 
606       RemoteModalityParameters remote;
607       if (!lock.GetConfiguration().LookupDicomModalityUsingAETitle(remote, remoteAet))
608       {
609         if (lock.GetConfiguration().GetBooleanParameter("DicomAlwaysAllowFind", false))
610         {
611           CLOG(INFO, DICOM) << "C-FIND: Allowing SCU request from unknown modality with AET: " << remoteAet;
612         }
613         else
614         {
615           // This should never happen, given the test at bottom of
616           // "OrthancApplicationEntityFilter::IsAllowedRequest()"
617           throw OrthancException(ErrorCode_InexistentItem,
618                                  "C-FIND: Rejecting SCU request from unknown modality with AET: " + remoteAet);
619         }
620       }
621     }
622 
623 
624 
625     /**
626      * Possibly apply the user-supplied Lua filter.
627      **/
628 
629     DicomMap lua;
630     const DicomMap* filteredInput = &input;
631 
632     if (ApplyLuaFilter(lua, input, remoteIp, remoteAet, calledAet, manufacturer))
633     {
634       filteredInput = &lua;
635     }
636 
637 
638     /**
639      * Retrieve the query level.
640      **/
641 
642     assert(filteredInput != NULL);
643     const DicomValue* levelTmp = filteredInput->TestAndGetValue(DICOM_TAG_QUERY_RETRIEVE_LEVEL);
644     if (levelTmp == NULL ||
645         levelTmp->IsNull() ||
646         levelTmp->IsBinary())
647     {
648       throw OrthancException(ErrorCode_BadRequest,
649                              "C-FIND request without the tag 0008,0052 (QueryRetrieveLevel)");
650     }
651 
652     ResourceType level = StringToResourceType(levelTmp->GetContent().c_str());
653 
654     if (level != ResourceType_Patient &&
655         level != ResourceType_Study &&
656         level != ResourceType_Series &&
657         level != ResourceType_Instance)
658     {
659       throw OrthancException(ErrorCode_NotImplemented);
660     }
661 
662 
663     DicomArray query(*filteredInput);
664     CLOG(INFO, DICOM) << "DICOM C-Find request at level: " << EnumerationToString(level);
665 
666     for (size_t i = 0; i < query.GetSize(); i++)
667     {
668       if (!query.GetElement(i).GetValue().IsNull())
669       {
670         CLOG(INFO, DICOM) << "  (" << query.GetElement(i).GetTag().Format()
671                           << ")  " << FromDcmtkBridge::GetTagName(query.GetElement(i))
672                           << " = " << context_.GetDeidentifiedContent(query.GetElement(i));
673       }
674     }
675 
676     for (std::list<DicomTag>::const_iterator it = sequencesToReturn.begin();
677          it != sequencesToReturn.end(); ++it)
678     {
679       CLOG(INFO, DICOM) << "  (" << it->Format()
680                         << ")  " << FromDcmtkBridge::GetTagName(*it, "")
681                         << " : sequence tag whose content will be copied";
682     }
683 
684     // collect the private creators from the query itself
685     std::map<uint16_t, std::string> privateCreators;
686     for (size_t i = 0; i < query.GetSize(); i++)
687     {
688       const DicomElement& element = query.GetElement(i);
689       if (element.GetTag().IsPrivate() && element.GetTag().GetElement() == 0x10)
690       {
691         privateCreators[element.GetTag().GetGroup()] = element.GetValue().GetContent();
692       }
693     }
694 
695     /**
696      * Build up the query object.
697      **/
698 
699     DatabaseLookup lookup;
700 
701     for (size_t i = 0; i < query.GetSize(); i++)
702     {
703       const DicomElement& element = query.GetElement(i);
704       const DicomTag tag = element.GetTag();
705 
706       if (element.GetValue().IsNull() ||
707           tag == DICOM_TAG_QUERY_RETRIEVE_LEVEL ||
708           tag == DICOM_TAG_SPECIFIC_CHARACTER_SET)
709       {
710         continue;
711       }
712 
713       std::string value = element.GetValue().GetContent();
714       if (value.size() == 0)
715       {
716         // An empty string corresponds to an universal constraint, so we ignore it
717         continue;
718       }
719 
720       if (FilterQueryTag(value, level, tag, manufacturer))
721       {
722         ValueRepresentation vr = FromDcmtkBridge::LookupValueRepresentation(tag);
723 
724         // DICOM specifies that searches must be case sensitive, except
725         // for tags with a PN value representation
726         bool sensitive = true;
727         if (vr == ValueRepresentation_PersonName)
728         {
729           sensitive = caseSensitivePN;
730         }
731 
732         lookup.AddDicomConstraint(tag, value, sensitive, true /* mandatory */);
733       }
734       else
735       {
736         CLOG(INFO, DICOM) << "Because of a patch for the manufacturer of the remote modality, "
737                           << "ignoring constraint on tag (" << tag.Format() << ") "
738                           << FromDcmtkBridge::GetTagName(element);
739       }
740     }
741 
742 
743     /**
744      * Run the query.
745      **/
746 
747     size_t limit = (level == ResourceType_Instance) ? maxInstances_ : maxResults_;
748 
749 
750     LookupVisitor visitor(answers, context_, level, *filteredInput, sequencesToReturn, privateCreators);
751     context_.Apply(visitor, lookup, level, 0 /* "since" is not relevant to C-FIND */, limit);
752   }
753 
754 
FormatOrigin(Json::Value & origin,const std::string & remoteIp,const std::string & remoteAet,const std::string & calledAet,ModalityManufacturer manufacturer)755   void OrthancFindRequestHandler::FormatOrigin(Json::Value& origin,
756                                                const std::string& remoteIp,
757                                                const std::string& remoteAet,
758                                                const std::string& calledAet,
759                                                ModalityManufacturer manufacturer)
760   {
761     origin = Json::objectValue;
762     origin["RemoteIp"] = remoteIp;
763     origin["RemoteAet"] = remoteAet;
764     origin["CalledAet"] = calledAet;
765     origin["Manufacturer"] = EnumerationToString(manufacturer);
766   }
767 }
768