view Sources/RestApiRouter.cpp @ 77:80b663d5f8fe default tip

replaced boost::math::iround() by Orthanc::Math::llround()
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 27 Jan 2026 17:05:03 +0100
parents 0f8c46d755e2
children
line wrap: on
line source

/**
 * SPDX-FileCopyrightText: 2024-2026 Sebastien Jodogne, EPL UCLouvain, Belgium
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

/**
 * Orthanc for Education
 * Copyright (C) 2024-2026 Sebastien Jodogne, EPL UCLouvain, 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 "RestApiRouter.h"

#include <boost/thread.hpp>
#include <cassert>


namespace
{
  class Route : public boost::noncopyable
  {
  private:
    typedef std::map<std::string, Route*>  Children;

    AuthorizationStatus  status_;
    Children             children_;
    Route*               universal_;

  public:
    Route() :
      status_(AuthorizationStatus_Forbidden),
      universal_(NULL)
    {
    }

    ~Route()
    {
      for (Children::iterator it = children_.begin(); it != children_.end(); ++it)
      {
        assert(it->second != NULL);
        delete it->second;
      }

      if (universal_ != NULL)
      {
        delete universal_;
      }
    }

    AuthorizationStatus Classify(const std::vector<std::string>& path,
                                 size_t index) const
    {
      if (index > path.size())
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
      }
      else if (index == path.size())
      {
        return status_;
      }
      else if (universal_ != NULL)
      {
        assert(children_.empty());
        return universal_->Classify(path, index + 1);
      }
      else
      {
        Children::const_iterator found = children_.find(path[index]);
        if (found == children_.end())
        {
          return AuthorizationStatus_Forbidden;
        }
        else
        {
          assert(found->second != NULL);
          return found->second->Classify(path, index + 1);
        }
      }
    }

    void Register(AuthorizationStatus status,
                  const std::vector<std::string>& path,
                  size_t index)
    {
      if (status == AuthorizationStatus_Forbidden ||
          index > path.size())
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
      }
      else if (index == path.size())
      {
        if (status_ == AuthorizationStatus_Forbidden)
        {
          status_ = status;
        }
        else
        {
          // Cannot register twice the same target
          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
        }
      }
      else if (path[index].empty())
      {
        throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange);
      }
      else if (path[index] == "{}")
      {
        if (!children_.empty())
        {
          // Cannot combine a universal target with named target
          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
        }
        else
        {
          if (universal_ == NULL)
          {
            universal_ = new Route;
          }

          universal_->Register(status, path, index + 1);
        }
      }
      else
      {
        if (universal_ != NULL)
        {
          // Cannot combine a universal target with named target
          throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls);
        }
        else
        {
          Children::iterator found = children_.find(path[index]);
          if (found == children_.end())
          {
            std::unique_ptr<Route> child(new Route);
            child->Register(status, path, index + 1);
            children_[path[index]] = child.release();
          }
          else
          {
            assert(found->second != NULL);
            found->second->Register(status, path, index + 1);
          }
        }
      }
    }
  };
}


static std::unique_ptr<Route>  root_(new Route);
static boost::shared_mutex     authorizationRoutesMutex_;


namespace RestApiRouter
{
  void RegisterRoute(const std::string& uri,
                     AuthorizationStatus status,
                     OrthancPluginRestCallback callback)
  {
    boost::unique_lock<boost::shared_mutex> lock(authorizationRoutesMutex_);

    std::vector<std::string> path;
    Orthanc::Toolbox::SplitUriComponents(path, uri);
    root_->Register(status, path, 0);

    std::string regex = "/";

    {
      for (size_t i = 0; i < path.size(); i++)
      {
        if (i > 0)
        {
          regex += "/";
        }

        if (path[i] == "{}")
        {
          regex += "([0-9a-zA-Z._-]+)";
        }
        else
        {
          for (size_t j = 0; j < path[i].size(); j++)
          {
            if ((path[i][j] >= '0' && path[i][j] <= '9') ||
                (path[i][j] >= 'a' && path[i][j] <= 'z') ||
                (path[i][j] >= 'A' && path[i][j] <= 'Z') ||
                path[i][j] == '_' ||
                path[i][j] == '-')
            {
              regex += path[i][j];
            }
            else if (path[i][j] == '.')
            {
              regex += "\\.";
            }
            else
            {
              throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange,
                                              "Character \"" + std::string(1, path[i][j]) + "\" not allowed in route: " + uri);
            }
          }
        }
      }
    }

    // "NoLock" because all education callbacks are thread-safe
    OrthancPluginRegisterRestCallbackNoLock(OrthancPlugins::GetGlobalContext(), regex.c_str(), callback);
  }


  bool LookupRoute(AuthorizationStatus& status,
                   const std::string& uri)
  {
    std::vector<std::string> path;
    Orthanc::Toolbox::SplitUriComponents(path, uri);

    {
      boost::shared_lock<boost::shared_mutex>  lock(authorizationRoutesMutex_);

      status = root_->Classify(path, 0);
      return (status != AuthorizationStatus_Forbidden);
    }
  }
}