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