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 Affero General Public License
9  * as published by the Free Software Foundation, either version 3 of
10  * the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program. If not, see <http://www.gnu.org/licenses/>.
19  **/
20 
21 
22 #include "WadoUri.h"
23 
24 #include "../Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h"
25 #include "Configuration.h"
26 
27 #include <string>
28 
29 
MapWadoToOrthancIdentifier(std::string & orthanc,char * (* func)(OrthancPluginContext *,const char *),const std::string & dicom)30 static bool MapWadoToOrthancIdentifier(std::string& orthanc,
31                                        char* (*func) (OrthancPluginContext*, const char*),
32                                        const std::string& dicom)
33 {
34   OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
35 
36   char* tmp = func(context, dicom.c_str());
37 
38   if (tmp)
39   {
40     orthanc = tmp;
41     OrthancPluginFreeString(context, tmp);
42     return true;
43   }
44   else
45   {
46     return false;
47   }
48 }
49 
50 
LocateInstanceWadoUri(std::string & instance,std::string & contentType,const OrthancPluginHttpRequest * request)51 static bool LocateInstanceWadoUri(std::string& instance,
52                                   std::string& contentType,
53                                   const OrthancPluginHttpRequest* request)
54 {
55   std::string requestType, studyUid, seriesUid, objectUid;
56 
57   for (uint32_t i = 0; i < request->getCount; i++)
58   {
59     std::string key(request->getKeys[i]);
60     std::string value(request->getValues[i]);
61 
62     if (key == "studyUID")
63     {
64       studyUid = value;
65     }
66     else if (key == "seriesUID")
67     {
68       seriesUid = value;
69     }
70     else if (key == "objectUID")  // In WADO-URI, "objectUID" corresponds to "SOPInstanceUID"
71     {
72       objectUid = value;
73     }
74     else if (key == "requestType")
75     {
76       requestType = value;
77     }
78     else if (key == "contentType")
79     {
80       contentType = value;
81     }
82   }
83 
84   if (requestType != "WADO")
85   {
86     OrthancPlugins::LogError("WADO-URI: Invalid requestType: \"" + requestType + "\"");
87     return false;
88   }
89 
90   if (objectUid.empty())
91   {
92     OrthancPlugins::LogError("WADO-URI: No SOPInstanceUID provided");
93     return false;
94   }
95 
96   if (!MapWadoToOrthancIdentifier(instance, OrthancPluginLookupInstance, objectUid))
97   {
98     OrthancPlugins::LogError("WADO-URI: No such SOPInstanceUID in Orthanc: \"" + objectUid + "\"");
99     return false;
100   }
101 
102   /**
103    * Below are only sanity checks to ensure that the possibly provided
104    * "seriesUID" and "studyUID" match that of the provided instance.
105    **/
106 
107   if (!seriesUid.empty())
108   {
109     std::string series;
110     if (!MapWadoToOrthancIdentifier(series, OrthancPluginLookupSeries, seriesUid))
111     {
112       OrthancPlugins::LogError("WADO-URI: No such SeriesInstanceUID in Orthanc: \"" + seriesUid + "\"");
113       return false;
114     }
115     else
116     {
117       Json::Value info;
118       if (!OrthancPlugins::RestApiGet(info, "/instances/" + instance + "/series", false) ||
119           info["MainDicomTags"]["SeriesInstanceUID"] != seriesUid)
120       {
121         OrthancPlugins::LogError("WADO-URI: Instance " + objectUid + " does not belong to series " + seriesUid);
122         return false;
123       }
124     }
125   }
126 
127   if (!studyUid.empty())
128   {
129     std::string study;
130     if (!MapWadoToOrthancIdentifier(study, OrthancPluginLookupStudy, studyUid))
131     {
132       OrthancPlugins::LogError("WADO-URI: No such StudyInstanceUID in Orthanc: \"" + studyUid + "\"");
133       return false;
134     }
135     else
136     {
137       Json::Value info;
138       if (!OrthancPlugins::RestApiGet(info, "/instances/" + instance + "/study", false) ||
139           info["MainDicomTags"]["StudyInstanceUID"] != studyUid)
140       {
141         OrthancPlugins::LogError("WADO-URI: Instance " + objectUid + " does not belong to study " + studyUid);
142         return false;
143       }
144     }
145   }
146 
147   return true;
148 }
149 
150 
AnswerDicom(OrthancPluginRestOutput * output,const std::string & instance)151 static void AnswerDicom(OrthancPluginRestOutput* output,
152                         const std::string& instance)
153 {
154   OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
155 
156   std::string uri = "/instances/" + instance + "/file";
157 
158   OrthancPlugins::MemoryBuffer dicom;
159   if (dicom.RestApiGet(uri, false))
160   {
161     OrthancPluginAnswerBuffer(context, output,
162                               dicom.GetData(), dicom.GetSize(), "application/dicom");
163   }
164   else
165   {
166     throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin,
167                                     "WADO-URI: Unable to retrieve DICOM file from " + uri);
168   }
169 }
170 
171 
AnswerPreview(OrthancPluginRestOutput * output,const std::string & instance,const std::map<std::string,std::string> & httpHeaders)172 static void AnswerPreview(OrthancPluginRestOutput* output,
173                           const std::string& instance,
174                           const std::map<std::string, std::string>& httpHeaders)
175 {
176   /**
177    * (*) We can use "/rendered" that was introduced in the REST API of
178    * Orthanc 1.6.0, as since release 1.2 of the DICOMweb plugin, the
179    * minimal SDK version is Orthanc 1.7.0 (in order to be able to use
180    * transcoding primitives). In releases <= 1.2, "/preview" was used,
181    * which caused one issue:
182    * https://groups.google.com/d/msg/orthanc-users/mKgr2QAKTCU/R7u4I1LvBAAJ
183    **/
184   const std::string uri = "/instances/" + instance + "/rendered";
185 
186   OrthancPluginContext* context = OrthancPlugins::GetGlobalContext();
187 
188   OrthancPlugins::MemoryBuffer png;
189   if (png.RestApiGet(uri, httpHeaders, true))
190   {
191     OrthancPluginAnswerBuffer(context, output, png.GetData(), png.GetSize(), "image/png");
192   }
193   else
194   {
195     OrthancPlugins::LogError("WADO-URI: Unable to generate a preview image for " + uri);
196     throw Orthanc::OrthancException(Orthanc::ErrorCode_Plugin);
197   }
198 }
199 
200 
AnswerPngPreview(OrthancPluginRestOutput * output,const std::string & instance)201 static void AnswerPngPreview(OrthancPluginRestOutput* output,
202                               const std::string& instance)
203 {
204   std::map<std::string, std::string> httpHeaders;
205   httpHeaders["Accept"] = "image/png";
206   AnswerPreview(output, instance, httpHeaders);
207 }
208 
209 
AnswerJpegPreview(OrthancPluginRestOutput * output,const std::string & instance)210 static void AnswerJpegPreview(OrthancPluginRestOutput* output,
211                               const std::string& instance)
212 {
213   std::map<std::string, std::string> httpHeaders;
214   httpHeaders["Accept"] = "image/jpeg";
215   AnswerPreview(output, instance, httpHeaders);
216 }
217 
218 
WadoUriCallback(OrthancPluginRestOutput * output,const char * url,const OrthancPluginHttpRequest * request)219 void WadoUriCallback(OrthancPluginRestOutput* output,
220                      const char* url,
221                      const OrthancPluginHttpRequest* request)
222 {
223   if (request->method != OrthancPluginHttpMethod_Get)
224   {
225     OrthancPluginSendMethodNotAllowed(OrthancPlugins::GetGlobalContext(), output, "GET");
226     return;
227   }
228 
229   std::string instance;
230   std::string contentType = "image/jpg";  // By default, JPEG image will be returned
231   if (!LocateInstanceWadoUri(instance, contentType, request))
232   {
233     throw Orthanc::OrthancException(Orthanc::ErrorCode_UnknownResource);
234   }
235 
236   if (contentType == "application/dicom")
237   {
238     AnswerDicom(output, instance);
239   }
240   else if (contentType == "image/png")
241   {
242     AnswerPngPreview(output, instance);
243   }
244   else if (contentType == "image/jpeg" ||
245            contentType == "image/jpg")
246   {
247     AnswerJpegPreview(output, instance);
248   }
249   else
250   {
251     throw Orthanc::OrthancException(
252       Orthanc::ErrorCode_BadRequest,
253       "WADO-URI: Unsupported content type: \"" + contentType + "\"");
254   }
255 }
256