544 lines
13 KiB
C++
544 lines
13 KiB
C++
/**
|
|
* Orthanc - A Lightweight, RESTful DICOM Store
|
|
* Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
|
|
* Department, University Hospital of Liege, Belgium
|
|
* Copyright (C) 2017-2023 Osimis S.A., Belgium
|
|
* Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
|
|
* Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
|
|
*
|
|
* This program is free software: you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this program. If not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
**/
|
|
|
|
|
|
#include "../PrecompiledHeaders.h"
|
|
#include "RestApiHierarchy.h"
|
|
|
|
#include "../OrthancException.h"
|
|
|
|
#include <cassert>
|
|
#include <stdio.h>
|
|
|
|
namespace Orthanc
|
|
{
|
|
RestApiHierarchy::Resource::Resource() :
|
|
getHandler_(NULL),
|
|
postHandler_(NULL),
|
|
putHandler_(NULL),
|
|
deleteHandler_(NULL)
|
|
{
|
|
}
|
|
|
|
|
|
bool RestApiHierarchy::Resource::HasHandler(HttpMethod method) const
|
|
{
|
|
switch (method)
|
|
{
|
|
case HttpMethod_Get:
|
|
return getHandler_ != NULL;
|
|
|
|
case HttpMethod_Post:
|
|
return postHandler_ != NULL;
|
|
|
|
case HttpMethod_Put:
|
|
return putHandler_ != NULL;
|
|
|
|
case HttpMethod_Delete:
|
|
return deleteHandler_ != NULL;
|
|
|
|
default:
|
|
throw OrthancException(ErrorCode_ParameterOutOfRange);
|
|
}
|
|
}
|
|
|
|
|
|
void RestApiHierarchy::Resource::Register(RestApiGetCall::Handler handler)
|
|
{
|
|
getHandler_ = handler;
|
|
}
|
|
|
|
void RestApiHierarchy::Resource::Register(RestApiPutCall::Handler handler)
|
|
{
|
|
putHandler_ = handler;
|
|
}
|
|
|
|
void RestApiHierarchy::Resource::Register(RestApiPostCall::Handler handler)
|
|
{
|
|
postHandler_ = handler;
|
|
}
|
|
|
|
void RestApiHierarchy::Resource::Register(RestApiDeleteCall::Handler handler)
|
|
{
|
|
deleteHandler_ = handler;
|
|
}
|
|
|
|
|
|
bool RestApiHierarchy::Resource::IsEmpty() const
|
|
{
|
|
return (getHandler_ == NULL &&
|
|
postHandler_ == NULL &&
|
|
putHandler_ == NULL &&
|
|
deleteHandler_ == NULL);
|
|
}
|
|
|
|
|
|
RestApiHierarchy& RestApiHierarchy::AddChild(Children& children,
|
|
const std::string& name)
|
|
{
|
|
Children::iterator it = children.find(name);
|
|
|
|
if (it == children.end())
|
|
{
|
|
// Create new child
|
|
RestApiHierarchy *child = new RestApiHierarchy;
|
|
children[name] = child;
|
|
return *child;
|
|
}
|
|
else
|
|
{
|
|
return *it->second;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool RestApiHierarchy::Resource::Handle(RestApiGetCall& call) const
|
|
{
|
|
if (getHandler_ != NULL)
|
|
{
|
|
getHandler_(call);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
bool RestApiHierarchy::Resource::Handle(RestApiPutCall& call) const
|
|
{
|
|
if (putHandler_ != NULL)
|
|
{
|
|
putHandler_(call);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
bool RestApiHierarchy::Resource::Handle(RestApiPostCall& call) const
|
|
{
|
|
if (postHandler_ != NULL)
|
|
{
|
|
postHandler_(call);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
bool RestApiHierarchy::Resource::Handle(RestApiDeleteCall& call) const
|
|
{
|
|
if (deleteHandler_ != NULL)
|
|
{
|
|
deleteHandler_(call);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
void RestApiHierarchy::DeleteChildren(Children& children)
|
|
{
|
|
for (Children::iterator it = children.begin();
|
|
it != children.end(); ++it)
|
|
{
|
|
delete it->second;
|
|
}
|
|
}
|
|
|
|
|
|
template <typename Handler>
|
|
void RestApiHierarchy::RegisterInternal(const RestApiPath& path,
|
|
Handler handler,
|
|
size_t level)
|
|
{
|
|
if (path.GetLevelCount() == level)
|
|
{
|
|
if (path.IsUniversalTrailing())
|
|
{
|
|
handlersWithTrailing_.Register(handler);
|
|
}
|
|
else
|
|
{
|
|
handlers_.Register(handler);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RestApiHierarchy* child;
|
|
if (path.IsWildcardLevel(level))
|
|
{
|
|
child = &AddChild(wildcardChildren_, path.GetWildcardName(level));
|
|
}
|
|
else
|
|
{
|
|
child = &AddChild(children_, path.GetLevelName(level));
|
|
}
|
|
|
|
child->RegisterInternal(path, handler, level + 1);
|
|
}
|
|
}
|
|
|
|
|
|
bool RestApiHierarchy::LookupResource(HttpToolbox::Arguments& components,
|
|
const UriComponents& uri,
|
|
IVisitor& visitor,
|
|
size_t level)
|
|
{
|
|
if (uri.size() != 0 &&
|
|
level > uri.size())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Look for an exact match on the resource of interest
|
|
if (uri.size() == 0 ||
|
|
level == uri.size())
|
|
{
|
|
UriComponents noTrailing;
|
|
|
|
if (!handlers_.IsEmpty() &&
|
|
visitor.Visit(handlers_, uri, false, components, noTrailing))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
if (level < uri.size()) // A recursive call is possible
|
|
{
|
|
// Try and go down in the hierarchy, using an exact match for the child
|
|
Children::const_iterator child = children_.find(uri[level]);
|
|
if (child != children_.end())
|
|
{
|
|
if (child->second->LookupResource(components, uri, visitor, level + 1))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Try and go down in the hierarchy, using wildcard rules for children
|
|
for (child = wildcardChildren_.begin();
|
|
child != wildcardChildren_.end(); ++child)
|
|
{
|
|
HttpToolbox::Arguments subComponents = components;
|
|
subComponents[child->first] = uri[level];
|
|
|
|
if (child->second->LookupResource(subComponents, uri, visitor, level + 1))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// As a last resort, call the universal handlers, if any
|
|
if (!handlersWithTrailing_.IsEmpty())
|
|
{
|
|
UriComponents trailing;
|
|
trailing.resize(uri.size() - level);
|
|
size_t pos = 0;
|
|
for (size_t i = level; i < uri.size(); i++, pos++)
|
|
{
|
|
trailing[pos] = uri[i];
|
|
}
|
|
|
|
assert(pos == trailing.size());
|
|
|
|
if (visitor.Visit(handlersWithTrailing_, uri, true, components, trailing))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool RestApiHierarchy::CanGenerateDirectory() const
|
|
{
|
|
return (handlersWithTrailing_.IsEmpty() &&
|
|
wildcardChildren_.empty());
|
|
}
|
|
|
|
|
|
bool RestApiHierarchy::GetDirectory(Json::Value& result,
|
|
const UriComponents& uri,
|
|
size_t level)
|
|
{
|
|
if (uri.size() == level)
|
|
{
|
|
if (CanGenerateDirectory())
|
|
{
|
|
result = Json::arrayValue;
|
|
|
|
for (Children::const_iterator it = children_.begin();
|
|
it != children_.end(); ++it)
|
|
{
|
|
result.append(it->first);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Children::const_iterator child = children_.find(uri[level]);
|
|
if (child != children_.end())
|
|
{
|
|
if (child->second->GetDirectory(result, uri, level + 1))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (child = wildcardChildren_.begin();
|
|
child != wildcardChildren_.end(); ++child)
|
|
{
|
|
if (child->second->GetDirectory(result, uri, level + 1))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
RestApiHierarchy::~RestApiHierarchy()
|
|
{
|
|
DeleteChildren(children_);
|
|
DeleteChildren(wildcardChildren_);
|
|
}
|
|
|
|
void RestApiHierarchy::Register(const std::string& uri,
|
|
RestApiGetCall::Handler handler)
|
|
{
|
|
RestApiPath path(uri);
|
|
RegisterInternal(path, handler, 0);
|
|
}
|
|
|
|
void RestApiHierarchy::Register(const std::string& uri,
|
|
RestApiPutCall::Handler handler)
|
|
{
|
|
RestApiPath path(uri);
|
|
RegisterInternal(path, handler, 0);
|
|
}
|
|
|
|
void RestApiHierarchy::Register(const std::string& uri,
|
|
RestApiPostCall::Handler handler)
|
|
{
|
|
RestApiPath path(uri);
|
|
RegisterInternal(path, handler, 0);
|
|
}
|
|
|
|
void RestApiHierarchy::Register(const std::string& uri,
|
|
RestApiDeleteCall::Handler handler)
|
|
{
|
|
RestApiPath path(uri);
|
|
RegisterInternal(path, handler, 0);
|
|
}
|
|
|
|
void RestApiHierarchy::CreateSiteMap(Json::Value& target) const
|
|
{
|
|
target = Json::objectValue;
|
|
|
|
/*std::string s = " ";
|
|
if (handlers_.HasHandler(HttpMethod_Get))
|
|
{
|
|
s += "GET ";
|
|
}
|
|
|
|
if (handlers_.HasHandler(HttpMethod_Post))
|
|
{
|
|
s += "POST ";
|
|
}
|
|
|
|
if (handlers_.HasHandler(HttpMethod_Put))
|
|
{
|
|
s += "PUT ";
|
|
}
|
|
|
|
if (handlers_.HasHandler(HttpMethod_Delete))
|
|
{
|
|
s += "DELETE ";
|
|
}
|
|
|
|
target = s;*/
|
|
|
|
for (Children::const_iterator it = children_.begin();
|
|
it != children_.end(); ++it)
|
|
{
|
|
it->second->CreateSiteMap(target[it->first]);
|
|
}
|
|
|
|
for (Children::const_iterator it = wildcardChildren_.begin();
|
|
it != wildcardChildren_.end(); ++it)
|
|
{
|
|
it->second->CreateSiteMap(target["<" + it->first + ">"]);
|
|
}
|
|
}
|
|
|
|
bool RestApiHierarchy::GetDirectory(Json::Value &result, const UriComponents &uri)
|
|
{
|
|
return GetDirectory(result, uri, 0);
|
|
}
|
|
|
|
|
|
bool RestApiHierarchy::LookupResource(const UriComponents& uri,
|
|
IVisitor& visitor)
|
|
{
|
|
HttpToolbox::Arguments components;
|
|
return LookupResource(components, uri, visitor, 0);
|
|
}
|
|
|
|
|
|
|
|
namespace
|
|
{
|
|
// Anonymous namespace to avoid clashes between compilation modules
|
|
|
|
class AcceptedMethodsVisitor : public RestApiHierarchy::IVisitor
|
|
{
|
|
private:
|
|
std::set<HttpMethod>& methods_;
|
|
|
|
public:
|
|
explicit AcceptedMethodsVisitor(std::set<HttpMethod>& methods) :
|
|
methods_(methods)
|
|
{
|
|
}
|
|
|
|
virtual bool Visit(const RestApiHierarchy::Resource& resource,
|
|
const UriComponents& uri,
|
|
bool hasTrailing,
|
|
const HttpToolbox::Arguments& components,
|
|
const UriComponents& trailing)
|
|
{
|
|
if (!hasTrailing) // Ignore universal handlers
|
|
{
|
|
if (resource.HasHandler(HttpMethod_Get))
|
|
{
|
|
methods_.insert(HttpMethod_Get);
|
|
}
|
|
|
|
if (resource.HasHandler(HttpMethod_Post))
|
|
{
|
|
methods_.insert(HttpMethod_Post);
|
|
}
|
|
|
|
if (resource.HasHandler(HttpMethod_Put))
|
|
{
|
|
methods_.insert(HttpMethod_Put);
|
|
}
|
|
|
|
if (resource.HasHandler(HttpMethod_Delete))
|
|
{
|
|
methods_.insert(HttpMethod_Delete);
|
|
}
|
|
}
|
|
|
|
return false; // Continue to check all the possible ways to access this URI
|
|
}
|
|
};
|
|
}
|
|
|
|
void RestApiHierarchy::GetAcceptedMethods(std::set<HttpMethod>& methods,
|
|
const UriComponents& uri)
|
|
{
|
|
HttpToolbox::Arguments components;
|
|
AcceptedMethodsVisitor visitor(methods);
|
|
if (LookupResource(components, uri, visitor, 0))
|
|
{
|
|
Json::Value d;
|
|
if (GetDirectory(d, uri))
|
|
{
|
|
methods.insert(HttpMethod_Get);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RestApiHierarchy::ExploreAllResources(IVisitor& visitor,
|
|
const UriComponents& path,
|
|
const std::set<std::string>& uriArguments) const
|
|
{
|
|
HttpToolbox::Arguments args;
|
|
|
|
for (std::set<std::string>::const_iterator it = uriArguments.begin(); it != uriArguments.end(); ++it)
|
|
{
|
|
args[*it] = "";
|
|
}
|
|
|
|
if (!handlers_.IsEmpty())
|
|
{
|
|
visitor.Visit(handlers_, path, false, args, UriComponents());
|
|
}
|
|
|
|
if (!handlersWithTrailing_.IsEmpty())
|
|
{
|
|
visitor.Visit(handlersWithTrailing_, path, true, args, UriComponents());
|
|
}
|
|
|
|
for (Children::const_iterator
|
|
it = children_.begin(); it != children_.end(); ++it)
|
|
{
|
|
assert(it->second != NULL);
|
|
UriComponents c = path;
|
|
c.push_back(it->first);
|
|
it->second->ExploreAllResources(visitor, c, uriArguments);
|
|
}
|
|
|
|
for (Children::const_iterator
|
|
it = wildcardChildren_.begin(); it != wildcardChildren_.end(); ++it)
|
|
{
|
|
if (uriArguments.find(it->first) != uriArguments.end())
|
|
{
|
|
throw OrthancException(ErrorCode_InternalError, "Twice the same URI argument in a path: " + it->first);
|
|
}
|
|
|
|
std::set<std::string> d = uriArguments;
|
|
d.insert(it->first);
|
|
|
|
assert(it->second != NULL);
|
|
UriComponents c = path;
|
|
c.push_back("{" + it->first + "}");
|
|
it->second->ExploreAllResources(visitor, c, d);
|
|
}
|
|
}
|
|
}
|