/**
* Orthanc - A Lightweight, RESTful DICOM Store
* Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
* Department, University Hospital of Liege, Belgium
* Copyright (C) 2017-2021 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 "DicomWebServers.h"
#include "Configuration.h"
#include <Compatibility.h>
#include <Toolbox.h>
#include <boost/algorithm/string/predicate.hpp>
namespace OrthancPlugins
{
void DicomWebServers::Clear()
{
for (Servers::iterator it = servers_.begin(); it != servers_.end(); ++it)
{
assert(it->second != NULL);
delete it->second;
}
servers_.clear();
}
void DicomWebServers::LoadGlobalConfiguration(const Json::Value& servers)
{
boost::mutex::scoped_lock lock(mutex_);
Clear();
bool ok = true;
try
{
if (servers.type() != Json::objectValue)
{
ok = false;
}
else
{
Json::Value::Members members = servers.getMemberNames();
for (size_t i = 0; i < members.size(); i++)
{
std::unique_ptr<Orthanc::WebServiceParameters> parameters
(new Orthanc::WebServiceParameters(servers[members[i]]));
servers_[members[i]] = parameters.release();
}
}
}
catch (Orthanc::OrthancException& e)
{
LogError("Exception while parsing the \"DicomWeb.Servers\" section "
"of the configuration file: " + std::string(e.What()));
throw;
}
if (!ok)
{
throw Orthanc::OrthancException(
Orthanc::ErrorCode_BadFileFormat,
"Cannot parse the \"DicomWeb.Servers\" section of the configuration file");
}
}
DicomWebServers& DicomWebServers::GetInstance()
{
static DicomWebServers singleton;
return singleton;
}
Orthanc::WebServiceParameters DicomWebServers::GetServer(const std::string& name)
{
boost::mutex::scoped_lock lock(mutex_);
Servers::const_iterator server = servers_.find(name);
if (server == servers_.end() ||
server->second == NULL)
{
throw Orthanc::OrthancException(Orthanc::ErrorCode_InexistentItem,
"Inexistent server: " + name);
}
else
{
return *server->second;
}
}
void DicomWebServers::ListServers(std::list<std::string>& servers)
{
boost::mutex::scoped_lock lock(mutex_);
servers.clear();
for (Servers::const_iterator it = servers_.begin(); it != servers_.end(); ++it)
{
assert(it->second != NULL);
servers.push_back(it->first);
}
}
void DicomWebServers::ConfigureHttpClient(HttpClient& client,
std::map<std::string, std::string>& userProperties,
const std::string& name,
const std::string& uri)
{
static const char* HAS_CHUNKED_TRANSFERS = "ChunkedTransfers";
const Orthanc::WebServiceParameters parameters = GetServer(name);
client.SetUrl(RemoveMultipleSlashes(parameters.GetUrl() + "/" + uri));
client.SetHeaders(parameters.GetHttpHeaders());
if (!parameters.GetUsername().empty())
{
client.SetCredentials(parameters.GetUsername(), parameters.GetPassword());
}
if (!parameters.GetCertificateFile().empty())
{
client.SetCertificate(
parameters.GetCertificateFile(),
parameters.GetCertificateKeyFile(),
parameters.GetCertificateKeyPassword());
}
client.SetPkcs11(parameters.IsPkcs11Enabled());
// By default, enable chunked transfers
client.SetChunkedTransfersAllowed(
parameters.GetBooleanUserProperty(HAS_CHUNKED_TRANSFERS, true));
userProperties = parameters.GetUserProperties();
if (parameters.HasTimeout())
{
client.SetTimeout(parameters.GetTimeout());
}
}
void DicomWebServers::DeleteServer(const std::string& name)
{
boost::mutex::scoped_lock lock(mutex_);
Servers::iterator found = servers_.find(name);
if (found == servers_.end())
{
throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
"Unknown DICOMweb server: " + name);
}
else
{
assert(found->second != NULL);
delete found->second;
servers_.erase(found);
}
}
void DicomWebServers::SetServer(const std::string& name,
const Orthanc::WebServiceParameters& parameters)
{
boost::mutex::scoped_lock lock(mutex_);
Servers::iterator found = servers_.find(name);
if (found != servers_.end())
{
assert(found->second != NULL);
delete found->second;
servers_.erase(found);
}
servers_[name] = new Orthanc::WebServiceParameters(parameters);
}
static const char* ConvertToCString(const std::string& s)
{
if (s.empty())
{
return NULL;
}
else
{
return s.c_str();
}
}
void CallServer(MemoryBuffer& answerBody /* out */,
std::map<std::string, std::string>& answerHeaders /* out */,
const Orthanc::WebServiceParameters& server,
OrthancPluginHttpMethod method,
const std::map<std::string, std::string>& httpHeaders,
const std::string& uri,
const std::string& body)
{
answerBody.Clear();
answerHeaders.clear();
std::string url = server.GetUrl();
assert(!url.empty() && url[url.size() - 1] == '/');
// Remove the leading "/" in the URI if need be
if (!uri.empty() &&
uri[0] == '/')
{
url += uri.substr(1);
}
else
{
url += uri;
}
std::map<std::string, std::string> allHttpHeaders = server.GetHttpHeaders();
{
// Add the user-specified HTTP headers to the HTTP headers
// coming from the Orthanc configuration file
for (std::map<std::string, std::string>::const_iterator
it = httpHeaders.begin(); it != httpHeaders.end(); ++it)
{
allHttpHeaders[it->first] = it->second;
}
}
std::vector<const char*> httpHeadersKeys(allHttpHeaders.size());
std::vector<const char*> httpHeadersValues(allHttpHeaders.size());
{
size_t pos = 0;
for (std::map<std::string, std::string>::const_iterator
it = allHttpHeaders.begin(); it != allHttpHeaders.end(); ++it)
{
httpHeadersKeys[pos] = it->first.c_str();
httpHeadersValues[pos] = it->second.c_str();
pos += 1;
}
assert(pos == allHttpHeaders.size());
}
const char* bodyContent = NULL;
size_t bodySize = 0;
if ((method == OrthancPluginHttpMethod_Put ||
method == OrthancPluginHttpMethod_Post) &&
!body.empty())
{
bodyContent = body.c_str();
bodySize = body.size();
}
OrthancPluginContext* context = GetGlobalContext();
uint16_t status = 0;
MemoryBuffer answerHeadersTmp;
OrthancPluginErrorCode code = OrthancPluginHttpClient(
context,
/* Outputs */
*answerBody, *answerHeadersTmp, &status,
method,
url.c_str(),
/* HTTP headers*/
allHttpHeaders.size(),
httpHeadersKeys.empty() ? NULL : &httpHeadersKeys[0],
httpHeadersValues.empty() ? NULL : &httpHeadersValues[0],
bodyContent, bodySize,
ConvertToCString(server.GetUsername()), /* Authentication */
ConvertToCString(server.GetPassword()),
0, /* Timeout */
ConvertToCString(server.GetCertificateFile()),
ConvertToCString(server.GetCertificateKeyFile()),
ConvertToCString(server.GetCertificateKeyPassword()),
server.IsPkcs11Enabled() ? 1 : 0);
if (code != OrthancPluginErrorCode_Success ||
(status < 200 || status >= 300))
{
throw Orthanc::OrthancException(
static_cast<Orthanc::ErrorCode>(code),
"Cannot issue an HTTP query to " + url +
" (HTTP status: " + boost::lexical_cast<std::string>(status) + ")");
}
Json::Value json;
answerHeadersTmp.ToJson(json);
answerHeadersTmp.Clear();
if (json.type() != Json::objectValue)
{
throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
}
Json::Value::Members members = json.getMemberNames();
for (size_t i = 0; i < members.size(); i++)
{
const std::string& key = members[i];
if (json[key].type() != Json::stringValue)
{
throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError);
}
else
{
answerHeaders[key] = json[key].asString();
}
}
}
void DicomWebServers::UriEncode(std::string& uri,
const std::string& resource,
const std::map<std::string, std::string>& getArguments)
{
if (resource.find('?') != std::string::npos)
{
throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat,
"The GET arguments must be provided in a separate field "
"(explicit \"?\" is disallowed): " + resource);
}
uri = resource;
bool isFirst = true;
for (std::map<std::string, std::string>::const_iterator
it = getArguments.begin(); it != getArguments.end(); ++it)
{
if (isFirst)
{
uri += '?';
isFirst = false;
}
else
{
uri += '&';
}
std::string key, value;
Orthanc::Toolbox::UriEncode(key, it->first);
Orthanc::Toolbox::UriEncode(value, it->second);
if (value.empty())
{
uri += key;
}
else
{
uri += key + "=" + value;
}
}
}
void DicomWebServers::SerializeGlobalProperty(std::string& target)
{
boost::mutex::scoped_lock lock(mutex_);
Json::Value json = Json::objectValue;
for (Servers::const_iterator it = servers_.begin(); it != servers_.end(); ++it)
{
assert(it->second != NULL);
Json::Value server;
it->second->Serialize(server, true /* advanced format */, true /* store passwords */);
json[it->first] = server;
}
OrthancPlugins::WriteFastJson(target, json);
}
void DicomWebServers::UnserializeGlobalProperty(const std::string& source)
{
Json::Value json;
if (!OrthancPlugins::ReadJson(json, source) ||
json.type() != Json::objectValue)
{
throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat, "Cannot unserialize "
"the list of DICOMweb servers from global properties");
}
Clear();
std::vector<std::string> members = json.getMemberNames();
for (size_t i = 0; i < members.size(); i++)
{
const std::string& name = members[i];
std::unique_ptr<Orthanc::WebServiceParameters> server(new Orthanc::WebServiceParameters);
server->Unserialize(json[name]);
assert(servers_.find(name) == servers_.end());
servers_[name] = server.release();
}
}
}