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 Lesser 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  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this program. If not, see
19  * <http://www.gnu.org/licenses/>.
20  **/
21 
22 
23 #include "../PrecompiledHeaders.h"
24 #include "HttpContentNegociation.h"
25 
26 #include "../Logging.h"
27 #include "../OrthancException.h"
28 #include "../Toolbox.h"
29 
30 #include <boost/lexical_cast.hpp>
31 
32 namespace Orthanc
33 {
Handler(const std::string & type,const std::string & subtype,IHandler & handler)34   HttpContentNegociation::Handler::Handler(const std::string& type,
35                                            const std::string& subtype,
36                                            IHandler& handler) :
37     type_(type),
38     subtype_(subtype),
39     handler_(handler)
40   {
41   }
42 
43 
IsMatch(const std::string & type,const std::string & subtype) const44   bool HttpContentNegociation::Handler::IsMatch(const std::string& type,
45                                                 const std::string& subtype) const
46   {
47     if (type == "*" && subtype == "*")
48     {
49       return true;
50     }
51 
52     if (subtype == "*" && type == type_)
53     {
54       return true;
55     }
56 
57     return type == type_ && subtype == subtype_;
58   }
59 
60 
61   struct HttpContentNegociation::Reference : public boost::noncopyable
62   {
63     const Handler&  handler_;
64     uint8_t         level_;
65     float           quality_;
66 
ReferenceOrthanc::HttpContentNegociation::Reference67     Reference(const Handler& handler,
68               const std::string& type,
69               const std::string& subtype,
70               float quality) :
71       handler_(handler),
72       quality_(quality)
73     {
74       if (type == "*" && subtype == "*")
75       {
76         level_ = 0;
77       }
78       else if (subtype == "*")
79       {
80         level_ = 1;
81       }
82       else
83       {
84         level_ = 2;
85       }
86     }
87 
operator <Orthanc::HttpContentNegociation::Reference88     bool operator< (const Reference& other) const
89     {
90       if (level_ < other.level_)
91       {
92         return true;
93       }
94 
95       if (level_ > other.level_)
96       {
97         return false;
98       }
99 
100       return quality_ < other.quality_;
101     }
102   };
103 
104 
SplitPair(std::string & first,std::string & second,const std::string & source,char separator)105   bool HttpContentNegociation::SplitPair(std::string& first /* out */,
106                                          std::string& second /* out */,
107                                          const std::string& source,
108                                          char separator)
109   {
110     size_t pos = source.find(separator);
111 
112     if (pos == std::string::npos)
113     {
114       return false;
115     }
116     else
117     {
118       first = Toolbox::StripSpaces(source.substr(0, pos));
119       second = Toolbox::StripSpaces(source.substr(pos + 1));
120       return true;
121     }
122   }
123 
124 
GetQuality(const Tokens & parameters)125   float HttpContentNegociation::GetQuality(const Tokens& parameters)
126   {
127     for (size_t i = 1; i < parameters.size(); i++)
128     {
129       std::string key, value;
130       if (SplitPair(key, value, parameters[i], '=') &&
131           key == "q")
132       {
133         float quality;
134         bool ok = false;
135 
136         try
137         {
138           quality = boost::lexical_cast<float>(value);
139           ok = (quality >= 0.0f && quality <= 1.0f);
140         }
141         catch (boost::bad_lexical_cast&)
142         {
143         }
144 
145         if (ok)
146         {
147           return quality;
148         }
149         else
150         {
151           throw OrthancException(
152             ErrorCode_BadRequest,
153             "Quality parameter out of range in a HTTP request (must be between 0 and 1): " + value);
154         }
155       }
156     }
157 
158     return 1.0f;  // Default quality
159   }
160 
161 
SelectBestMatch(std::unique_ptr<Reference> & best,const Handler & handler,const std::string & type,const std::string & subtype,float quality)162   void HttpContentNegociation::SelectBestMatch(std::unique_ptr<Reference>& best,
163                                                const Handler& handler,
164                                                const std::string& type,
165                                                const std::string& subtype,
166                                                float quality)
167   {
168     std::unique_ptr<Reference> match(new Reference(handler, type, subtype, quality));
169 
170     if (best.get() == NULL ||
171         *best < *match)
172     {
173 #if __cplusplus < 201103L
174       best.reset(match.release());
175 #else
176       best = std::move(match);
177 #endif
178     }
179   }
180 
181 
Register(const std::string & mime,IHandler & handler)182   void HttpContentNegociation::Register(const std::string& mime,
183                                         IHandler& handler)
184   {
185     std::string type, subtype;
186 
187     if (SplitPair(type, subtype, mime, '/') &&
188         type != "*" &&
189         subtype != "*")
190     {
191       handlers_.push_back(Handler(type, subtype, handler));
192     }
193     else
194     {
195       throw OrthancException(ErrorCode_ParameterOutOfRange);
196     }
197   }
198 
199 
Apply(const HttpHeaders & headers)200   bool HttpContentNegociation::Apply(const HttpHeaders& headers)
201   {
202     HttpHeaders::const_iterator accept = headers.find("accept");
203     if (accept != headers.end())
204     {
205       return Apply(accept->second);
206     }
207     else
208     {
209       return Apply("*/*");
210     }
211   }
212 
213 
Apply(const std::string & accept)214   bool HttpContentNegociation::Apply(const std::string& accept)
215   {
216     // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
217     // https://en.wikipedia.org/wiki/Content_negotiation
218     // http://www.newmediacampaigns.com/blog/browser-rest-http-accept-headers
219 
220     Tokens mediaRanges;
221     Toolbox::TokenizeString(mediaRanges, accept, ',');
222 
223     std::unique_ptr<Reference> bestMatch;
224 
225     for (Tokens::const_iterator it = mediaRanges.begin();
226          it != mediaRanges.end(); ++it)
227     {
228       Tokens parameters;
229       Toolbox::TokenizeString(parameters, *it, ';');
230 
231       if (parameters.size() > 0)
232       {
233         float quality = GetQuality(parameters);
234 
235         std::string type, subtype;
236         if (SplitPair(type, subtype, parameters[0], '/'))
237         {
238           for (Handlers::const_iterator it2 = handlers_.begin();
239                it2 != handlers_.end(); ++it2)
240           {
241             if (it2->IsMatch(type, subtype))
242             {
243               SelectBestMatch(bestMatch, *it2, type, subtype, quality);
244             }
245           }
246         }
247       }
248     }
249 
250     if (bestMatch.get() == NULL)  // No match was found
251     {
252       return false;
253     }
254     else
255     {
256       bestMatch->handler_.Call();
257       return true;
258     }
259   }
260 }
261