Mercurial > hg > orthanc-dicomweb
view Plugin/Configuration.cpp @ 371:c0b95d747337
Fix support for client certificate authentication
author | Alain Mazy <alain@mazy.be> |
---|---|
date | Mon, 28 Oct 2019 11:48:46 +0100 |
parents | 351db3241ea6 |
children | 9bf97ec22e22 |
line wrap: on
line source
/** * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics * Department, University Hospital of Liege, Belgium * Copyright (C) 2017-2019 Osimis S.A., Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ #include "Configuration.h" #include "DicomWebServers.h" #include <Plugins/Samples/Common/OrthancPluginCppWrapper.h> #include <Core/Toolbox.h> #include <fstream> #include <json/reader.h> #include <boost/regex.hpp> #include <boost/lexical_cast.hpp> #include <boost/algorithm/string/predicate.hpp> namespace OrthancPlugins { bool LookupHttpHeader(std::string& value, const OrthancPluginHttpRequest* request, const std::string& header) { value.clear(); for (uint32_t i = 0; i < request->headersCount; i++) { std::string s = request->headersKeys[i]; Orthanc::Toolbox::ToLowerCase(s); if (s == header) { value = request->headersValues[i]; return true; } } return false; } void ParseContentType(std::string& application, std::map<std::string, std::string>& attributes, const std::string& header) { application.clear(); attributes.clear(); std::vector<std::string> tokens; Orthanc::Toolbox::TokenizeString(tokens, header, ';'); assert(tokens.size() > 0); application = tokens[0]; Orthanc::Toolbox::StripSpaces(application); Orthanc::Toolbox::ToLowerCase(application); boost::regex pattern("\\s*([^=]+)\\s*=\\s*(([^=\"]+)|\"([^=\"]+)\")\\s*"); for (size_t i = 1; i < tokens.size(); i++) { boost::cmatch what; if (boost::regex_match(tokens[i].c_str(), what, pattern)) { std::string key(what[1]); std::string value(what.length(3) != 0 ? what[3] : what[4]); Orthanc::Toolbox::ToLowerCase(key); attributes[key] = value; } } } void ParseAssociativeArray(std::map<std::string, std::string>& target, const Json::Value& value) { if (value.type() != Json::objectValue) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "The JSON object is not a JSON associative array as expected"); } Json::Value::Members names = value.getMemberNames(); for (size_t i = 0; i < names.size(); i++) { if (value[names[i]].type() != Json::stringValue) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Value \"" + names[i] + "\" in the associative array " "is not a string as expected"); } else { target[names[i]] = value[names[i]].asString(); } } } void ParseAssociativeArray(std::map<std::string, std::string>& target, const Json::Value& value, const std::string& key) { if (value.type() != Json::objectValue) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "This is not a JSON object"); } if (value.isMember(key)) { ParseAssociativeArray(target, value[key]); } else { target.clear(); } } bool ParseTag(Orthanc::DicomTag& target, const std::string& name) { OrthancPluginDictionaryEntry entry; if (OrthancPluginLookupDictionary(OrthancPlugins::GetGlobalContext(), &entry, name.c_str()) == OrthancPluginErrorCode_Success) { target = Orthanc::DicomTag(entry.group, entry.element); return true; } else { return false; } } void ParseJsonBody(Json::Value& target, const OrthancPluginHttpRequest* request) { Json::Reader reader; if (!reader.parse(reinterpret_cast<const char*>(request->body), reinterpret_cast<const char*>(request->body) + request->bodySize, target)) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "A JSON file was expected"); } } std::string RemoveMultipleSlashes(const std::string& source) { std::string target; target.reserve(source.size()); size_t prefix = 0; if (boost::starts_with(source, "https://")) { prefix = 8; } else if (boost::starts_with(source, "http://")) { prefix = 7; } for (size_t i = 0; i < prefix; i++) { target.push_back(source[i]); } bool isLastSlash = false; for (size_t i = prefix; i < source.size(); i++) { if (source[i] == '/') { if (!isLastSlash) { target.push_back('/'); isLastSlash = true; } } else { target.push_back(source[i]); isLastSlash = false; } } return target; } bool LookupStringValue(std::string& target, const Json::Value& json, const std::string& key) { if (json.type() != Json::objectValue) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); } else if (!json.isMember(key)) { return false; } else if (json[key].type() != Json::stringValue) { throw Orthanc::OrthancException( Orthanc::ErrorCode_BadFileFormat, "The field \"" + key + "\" in a JSON object should be a string"); } else { target = json[key].asString(); return true; } } bool LookupIntegerValue(int& target, const Json::Value& json, const std::string& key) { if (json.type() != Json::objectValue) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); } else if (!json.isMember(key)) { return false; } else if (json[key].type() != Json::intValue && json[key].type() != Json::uintValue) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); } else { target = json[key].asInt(); return true; } } bool LookupBooleanValue(bool& target, const Json::Value& json, const std::string& key) { if (json.type() != Json::objectValue) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); } else if (!json.isMember(key)) { return false; } else if (json[key].type() != Json::booleanValue) { throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat); } else { target = json[key].asBool(); return true; } } namespace Configuration { // Assume Latin-1 encoding by default (as in the Orthanc core) static Orthanc::Encoding defaultEncoding_ = Orthanc::Encoding_Latin1; static std::auto_ptr<OrthancConfiguration> configuration_; void Initialize() { configuration_.reset(new OrthancConfiguration); OrthancPlugins::OrthancConfiguration global; global.GetSection(*configuration_, "DicomWeb"); std::string s; if (global.LookupStringValue(s, "DefaultEncoding")) { defaultEncoding_ = Orthanc::StringToEncoding(s.c_str()); } OrthancPlugins::OrthancConfiguration servers; configuration_->GetSection(servers, "Servers"); OrthancPlugins::DicomWebServers::GetInstance().Load(servers.GetJson()); } std::string GetStringValue(const std::string& key, const std::string& defaultValue) { assert(configuration_.get() != NULL); return configuration_->GetStringValue(key, defaultValue); } bool GetBooleanValue(const std::string& key, bool defaultValue) { assert(configuration_.get() != NULL); return configuration_->GetBooleanValue(key, defaultValue); } unsigned int GetUnsignedIntegerValue(const std::string& key, unsigned int defaultValue) { assert(configuration_.get() != NULL); return configuration_->GetUnsignedIntegerValue(key, defaultValue); } std::string GetDicomWebRoot() { assert(configuration_.get() != NULL); std::string root = configuration_->GetStringValue("Root", "/dicom-web/"); // Make sure the root URI starts and ends with a slash if (root.size() == 0 || root[0] != '/') { root = "/" + root; } if (root[root.length() - 1] != '/') { root += "/"; } return root; } std::string GetOrthancApiRoot() { std::string root = OrthancPlugins::Configuration::GetDicomWebRoot(); std::vector<std::string> tokens; Orthanc::Toolbox::TokenizeString(tokens, root, '/'); int depth = 0; for (size_t i = 0; i < tokens.size(); i++) { if (tokens[i].empty() || tokens[i] == ".") { // Don't change the depth } else if (tokens[i] == "..") { depth--; } else { depth++; } } std::string orthancRoot = "./"; for (int i = 0; i < depth; i++) { orthancRoot += "../"; } return orthancRoot; } std::string GetWadoRoot() { assert(configuration_.get() != NULL); std::string root = configuration_->GetStringValue("WadoRoot", "/wado/"); // Make sure the root URI starts with a slash if (root.size() == 0 || root[0] != '/') { root = "/" + root; } // Remove the trailing slash, if any if (root[root.length() - 1] == '/') { root = root.substr(0, root.length() - 1); } return root; } static bool IsHttpsProto(const std::string& proto, bool defaultValue) { if (proto == "http") { return false; } else if (proto == "https") { return true; } else { return defaultValue; } } static bool LookupHttpHeader2(std::string& value, const OrthancPlugins::HttpClient::HttpHeaders& headers, const std::string& name) { for (OrthancPlugins::HttpClient::HttpHeaders::const_iterator it = headers.begin(); it != headers.end(); ++it) { if (boost::iequals(it->first, name)) { value = it->second; return true; } } return false; } std::string GetBaseUrl(const OrthancPlugins::HttpClient::HttpHeaders& headers) { assert(configuration_.get() != NULL); std::string host = configuration_->GetStringValue("Host", ""); bool https = configuration_->GetBooleanValue("Ssl", false); std::string forwarded; if (host.empty() && LookupHttpHeader2(forwarded, headers, "forwarded")) { // There is a "Forwarded" HTTP header in the query // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded std::vector<std::string> forwarders; Orthanc::Toolbox::TokenizeString(forwarders, forwarded, ','); // Only consider the first forwarder, if any if (!forwarders.empty()) { std::vector<std::string> tokens; Orthanc::Toolbox::TokenizeString(tokens, forwarders[0], ';'); for (size_t j = 0; j < tokens.size(); j++) { std::vector<std::string> args; Orthanc::Toolbox::TokenizeString(args, tokens[j], '='); if (args.size() == 2) { std::string key = Orthanc::Toolbox::StripSpaces(args[0]); std::string value = Orthanc::Toolbox::StripSpaces(args[1]); Orthanc::Toolbox::ToLowerCase(key); if (key == "host") { host = value; } else if (key == "proto") { https = IsHttpsProto(value, https); } } } } } if (host.empty() && !LookupHttpHeader2(host, headers, "host")) { // Should never happen: The "host" header should always be present // in HTTP requests. Provide a default value anyway. host = "localhost:8042"; } return (https ? "https://" : "http://") + host + GetDicomWebRoot(); } std::string GetBaseUrl(const OrthancPluginHttpRequest* request) { OrthancPlugins::HttpClient::HttpHeaders headers; std::string value; if (LookupHttpHeader(value, request, "forwarded")) { headers["Forwarded"] = value; } if (LookupHttpHeader(value, request, "host")) { headers["Host"] = value; } return GetBaseUrl(headers); } std::string GetWadoUrl(const std::string& wadoBase, const std::string& studyInstanceUid, const std::string& seriesInstanceUid, const std::string& sopInstanceUid) { if (studyInstanceUid.empty() || seriesInstanceUid.empty() || sopInstanceUid.empty()) { return ""; } else { return (wadoBase + "studies/" + studyInstanceUid + "/series/" + seriesInstanceUid + "/instances/" + sopInstanceUid + "/"); } } Orthanc::Encoding GetDefaultEncoding() { return defaultEncoding_; } static bool IsXmlExpected(const std::string& acceptHeader) { std::string accept; Orthanc::Toolbox::ToLowerCase(accept, acceptHeader); if (accept == "application/dicom+json" || accept == "application/json" || accept == "*/*") { return false; } else if (accept == "application/dicom+xml" || accept == "application/xml" || accept == "text/xml") { return true; } else { OrthancPlugins::LogError("Unsupported return MIME type: " + accept + ", will return DICOM+JSON"); return false; } } bool IsXmlExpected(const std::map<std::string, std::string>& headers) { std::map<std::string, std::string>::const_iterator found = headers.find("accept"); if (found == headers.end()) { return false; // By default, return DICOM+JSON } else { return IsXmlExpected(found->second); } } bool IsXmlExpected(const OrthancPluginHttpRequest* request) { std::string accept; if (OrthancPlugins::LookupHttpHeader(accept, request, "accept")) { return IsXmlExpected(accept); } else { return false; // By default, return DICOM+JSON } } } }