static std::string GetAuthenticatedUsername(const IHttpHandler::Arguments& headers)
  {
    IHttpHandler::Arguments::const_iterator auth = headers.find("authorization");

    if (auth == headers.end())
    {
      return "";
    }

    std::string s = auth->second;
    if (s.size() <= 6 ||
        s.substr(0, 6) != "Basic ")
    {
      return "";
    }

    std::string b64 = s.substr(6);
    std::string decoded;
    Toolbox::DecodeBase64(decoded, b64);
    size_t semicolons = decoded.find(':');

    if (semicolons == std::string::npos)
    {
      // Bad-formatted request
      return "";
    }
    else
    {
      return decoded.substr(0, semicolons);
    }
  }
Beispiel #2
0
TEST(RestApi, ParseCookies)
{
  IHttpHandler::Arguments headers;
  IHttpHandler::Arguments cookies;

  headers["cookie"] = "a=b;c=d;;;e=f;;g=h;";
  HttpToolbox::ParseCookies(cookies, headers);
  ASSERT_EQ(4u, cookies.size());
  ASSERT_EQ("b", cookies["a"]);
  ASSERT_EQ("d", cookies["c"]);
  ASSERT_EQ("f", cookies["e"]);
  ASSERT_EQ("h", cookies["g"]);

  headers["cookie"] = "  name =  value  ; name2=value2";
  HttpToolbox::ParseCookies(cookies, headers);
  ASSERT_EQ(2u, cookies.size());
  ASSERT_EQ("value", cookies["name"]);
  ASSERT_EQ("value2", cookies["name2"]);

  headers["cookie"] = "  ;;;    ";
  HttpToolbox::ParseCookies(cookies, headers);
  ASSERT_EQ(0u, cookies.size());

  headers["cookie"] = "  ;   n=v  ;;    ";
  HttpToolbox::ParseCookies(cookies, headers);
  ASSERT_EQ(1u, cookies.size());
  ASSERT_EQ("v", cookies["n"]);
}
TEST(ParseGetArguments, SingleEmpty)
{
  IHttpHandler::GetArguments b;
  HttpToolbox::ParseGetArguments(b, "aaa");

  IHttpHandler::Arguments a;
  HttpToolbox::CompileGetArguments(a, b);

  ASSERT_EQ(1u, a.size());
  ASSERT_EQ(a["aaa"], "");
}
TEST(ParseGetArguments, BasicEmpty)
{
  IHttpHandler::GetArguments b;
  HttpToolbox::ParseGetArguments(b, "aaa&bb=aa&aa");

  IHttpHandler::Arguments a;
  HttpToolbox::CompileGetArguments(a, b);

  ASSERT_EQ(3u, a.size());
  ASSERT_EQ(a["aaa"], "");
  ASSERT_EQ(a["bb"], "aa");
  ASSERT_EQ(a["aa"], "");
}
Beispiel #5
0
 std::string HttpToolbox::GetArgument(const IHttpHandler::Arguments& getArguments,
                                      const std::string& name,
                                      const std::string& defaultValue)
 {
   IHttpHandler::Arguments::const_iterator it = getArguments.find(name);
   if (it == getArguments.end())
   {
     return defaultValue;
   }
   else
   {
     return it->second;
   }
 }
TEST(ParseGetQuery, Test2)
{
  UriComponents uri;
  IHttpHandler::GetArguments b;
  HttpToolbox::ParseGetQuery(uri, b, "/instances/test/world");

  IHttpHandler::Arguments a;
  HttpToolbox::CompileGetArguments(a, b);

  ASSERT_EQ(3u, uri.size());
  ASSERT_EQ("instances", uri[0]);
  ASSERT_EQ("test", uri[1]);
  ASSERT_EQ("world", uri[2]);
  ASSERT_EQ(0u, a.size());
}
TEST(ParseGetQuery, Test1)
{
  UriComponents uri;
  IHttpHandler::GetArguments b;
  HttpToolbox::ParseGetQuery(uri, b, "/instances/test/world?aaa=baaa&bb=a&aa=c");

  IHttpHandler::Arguments a;
  HttpToolbox::CompileGetArguments(a, b);

  ASSERT_EQ(3u, uri.size());
  ASSERT_EQ("instances", uri[0]);
  ASSERT_EQ("test", uri[1]);
  ASSERT_EQ("world", uri[2]);
  ASSERT_EQ(3u, a.size());
  ASSERT_EQ(a["aaa"], "baaa");
  ASSERT_EQ(a["bb"], "a");
  ASSERT_EQ(a["aa"], "c");
}
Beispiel #8
0
  void HttpToolbox::CompileGetArguments(IHttpHandler::Arguments& compiled,
                                        const IHttpHandler::GetArguments& source)
  {
    compiled.clear();

    for (size_t i = 0; i < source.size(); i++)
    {
      compiled[source[i].first] = source[i].second;
    }
  }
  static bool IsAccessGranted(const MongooseServer& that,
                              const IHttpHandler::Arguments& headers)
  {
    bool granted = false;

    IHttpHandler::Arguments::const_iterator auth = headers.find("authorization");
    if (auth != headers.end())
    {
      std::string s = auth->second;
      if (s.size() > 6 &&
          s.substr(0, 6) == "Basic ")
      {
        std::string b64 = s.substr(6);
        granted = that.IsValidBasicHttpAuthentication(b64);
      }
    }

    return granted;
  }
  static PostDataStatus ReadBody(std::string& postData,
                                 struct mg_connection *connection,
                                 const IHttpHandler::Arguments& headers)
  {
    IHttpHandler::Arguments::const_iterator cs = headers.find("content-length");
    if (cs == headers.end())
    {
      return PostDataStatus_NoLength;
    }

    int length;      
    try
    {
      length = boost::lexical_cast<int>(cs->second);
    }
    catch (boost::bad_lexical_cast)
    {
      return PostDataStatus_NoLength;
    }

    if (length < 0)
    {
      length = 0;
    }

    postData.resize(length);

    size_t pos = 0;
    while (length > 0)
    {
      int r = mg_read(connection, &postData[pos], length);
      if (r <= 0)
      {
        return PostDataStatus_Failure;
      }

      assert(r <= length);
      length -= r;
      pos += r;
    }

    return PostDataStatus_Success;
  }
Beispiel #11
0
  void HttpToolbox::ParseCookies(IHttpHandler::Arguments& result, 
                                 const IHttpHandler::Arguments& httpHeaders)
  {
    result.clear();

    IHttpHandler::Arguments::const_iterator it = httpHeaders.find("cookie");
    if (it != httpHeaders.end())
    {
      const std::string& cookies = it->second;

      size_t pos = 0;
      while (pos != std::string::npos)
      {
        size_t nextSemicolon = cookies.find(";", pos);
        std::string cookie;

        if (nextSemicolon == std::string::npos)
        {
          cookie = cookies.substr(pos);
          pos = std::string::npos;
        }
        else
        {
          cookie = cookies.substr(pos, nextSemicolon - pos);
          pos = nextSemicolon + 1;
        }

        size_t equal = cookie.find("=");
        if (equal != std::string::npos)
        {
          std::string name = Toolbox::StripSpaces(cookie.substr(0, equal));
          std::string value = Toolbox::StripSpaces(cookie.substr(equal + 1));
          result[name] = value;
        }
      }
    }
  }
  bool RestApiPath::Match(IHttpHandler::Arguments& components,
                          UriComponents& trailing,
                          const UriComponents& uri) const
  {
    assert(uri_.size() == components_.size());

    if (uri.size() < uri_.size())
    {
      return false;
    }

    if (!hasTrailing_ && uri.size() > uri_.size())
    {
      return false;
    }

    components.clear();
    trailing.clear();

    assert(uri_.size() <= uri.size());
    for (size_t i = 0; i < uri_.size(); i++)
    {
      if (components_[i].size() == 0)
      {
        // This URI component is not a free parameter
        if (uri_[i] != uri[i])
        {
          return false;
        }
      }
      else
      {
        // This URI component is a free parameter
        components[components_[i]] = uri[i];
      }
    }

    if (hasTrailing_)
    {
      trailing.assign(uri.begin() + uri_.size(), uri.end());
    }

    return true;
  }
  static void InternalCallback(struct mg_connection *connection,
                               const struct mg_request_info *request)
  {
    MongooseServer* that = reinterpret_cast<MongooseServer*>(request->user_data);

    MongooseOutputStream stream(connection);
    HttpOutput output(stream, that->IsKeepAliveEnabled());

    // Check remote calls
    if (!that->IsRemoteAccessAllowed() &&
        request->remote_ip != LOCALHOST)
    {
      output.SendUnauthorized(ORTHANC_REALM);
      return;
    }


    // Extract the HTTP headers
    IHttpHandler::Arguments headers;
    for (int i = 0; i < request->num_headers; i++)
    {
      std::string name = request->http_headers[i].name;
      std::transform(name.begin(), name.end(), name.begin(), ::tolower);
      headers.insert(std::make_pair(name, request->http_headers[i].value));
    }


    // Extract the GET arguments
    IHttpHandler::GetArguments argumentsGET;
    if (!strcmp(request->request_method, "GET"))
    {
      HttpToolbox::ParseGetArguments(argumentsGET, request->query_string);
    }


    // Compute the HTTP method, taking method faking into consideration
    HttpMethod method = HttpMethod_Get;
    if (!ExtractMethod(method, request, headers, argumentsGET))
    {
      output.SendStatus(HttpStatus_400_BadRequest);
      return;
    }


    // Authenticate this connection
    if (that->IsAuthenticationEnabled() && !IsAccessGranted(*that, headers))
    {
      output.SendUnauthorized(ORTHANC_REALM);
      return;
    }


    // Apply the filter, if it is installed
    const IIncomingHttpRequestFilter *filter = that->GetIncomingHttpRequestFilter();
    if (filter != NULL)
    {
      std::string username = GetAuthenticatedUsername(headers);

      char remoteIp[24];
      sprintf(remoteIp, "%d.%d.%d.%d", 
              reinterpret_cast<const uint8_t*>(&request->remote_ip) [3], 
              reinterpret_cast<const uint8_t*>(&request->remote_ip) [2], 
              reinterpret_cast<const uint8_t*>(&request->remote_ip) [1], 
              reinterpret_cast<const uint8_t*>(&request->remote_ip) [0]);

      if (!filter->IsAllowed(method, request->uri, remoteIp, username.c_str()))
      {
        output.SendUnauthorized(ORTHANC_REALM);
        return;
      }
    }


    // Extract the body of the request for PUT and POST

    // TODO Avoid unneccessary memcopy of the body

    std::string body;
    if (method == HttpMethod_Post ||
        method == HttpMethod_Put)
    {
      PostDataStatus status;

      IHttpHandler::Arguments::const_iterator ct = headers.find("content-type");
      if (ct == headers.end())
      {
        // No content-type specified. Assume no multi-part content occurs at this point.
        status = ReadBody(body, connection, headers);          
      }
      else
      {
        std::string contentType = ct->second;
        if (contentType.size() >= multipartLength &&
            !memcmp(contentType.c_str(), multipart, multipartLength))
        {
          status = ParseMultipartPost(body, connection, headers, contentType, that->GetChunkStore());
        }
        else
        {
          status = ReadBody(body, connection, headers);
        }
      }

      switch (status)
      {
        case PostDataStatus_NoLength:
          output.SendStatus(HttpStatus_411_LengthRequired);
          return;

        case PostDataStatus_Failure:
          output.SendStatus(HttpStatus_400_BadRequest);
          return;

        case PostDataStatus_Pending:
          output.SendBody();
          return;

        default:
          break;
      }
    }


    // Decompose the URI into its components
    UriComponents uri;
    try
    {
      Toolbox::SplitUriComponents(uri, request->uri);
    }
    catch (OrthancException)
    {
      output.SendStatus(HttpStatus_400_BadRequest);
      return;
    }


    LOG(INFO) << EnumerationToString(method) << " " << Toolbox::FlattenUri(uri);

    bool found = false;

    try
    {
      if (that->HasHandler())
      {
        found = that->GetHandler().Handle(output, method, uri, headers, argumentsGET, body.c_str(), body.size());
      }
    }
    catch (OrthancException& e)
    {
      // Using this candidate handler results in an exception
      LOG(ERROR) << "Exception in the HTTP handler: " << e.What();

      try
      {
        switch (e.GetErrorCode())
        {
          case ErrorCode_InexistentFile:
          case ErrorCode_InexistentItem:
          case ErrorCode_UnknownResource:
            output.SendStatus(HttpStatus_404_NotFound);
            break;

          case ErrorCode_BadRequest:
          case ErrorCode_UriSyntax:
            output.SendStatus(HttpStatus_400_BadRequest);
            break;

          default:
            output.SendStatus(HttpStatus_500_InternalServerError);
        }
      }
      catch (OrthancException&)
      {
        // An exception here reflects the fact that an exception was
        // triggered after the status code was sent by the HTTP handler.
      }

      return;
    }
    catch (boost::bad_lexical_cast&)
    {
      LOG(ERROR) << "Exception in the HTTP handler: Bad lexical cast";
      output.SendStatus(HttpStatus_400_BadRequest);
      return;
    }
    catch (std::runtime_error&)
    {
      LOG(ERROR) << "Exception in the HTTP handler: Presumably a bad JSON request";
      output.SendStatus(HttpStatus_400_BadRequest);
      return;
    }

    if (!found)
    {
      output.SendStatus(HttpStatus_404_NotFound);
    }
  }
  static bool ExtractMethod(HttpMethod& method,
                            const struct mg_request_info *request,
                            const IHttpHandler::Arguments& headers,
                            const IHttpHandler::GetArguments& argumentsGET)
  {
    std::string overriden;

    // Check whether some PUT/DELETE faking is done

    // 1. Faking with Google's approach
    IHttpHandler::Arguments::const_iterator methodOverride =
      headers.find("x-http-method-override");

    if (methodOverride != headers.end())
    {
      overriden = methodOverride->second;
    }
    else if (!strcmp(request->request_method, "GET"))
    {
      // 2. Faking with Ruby on Rail's approach
      // GET /my/resource?_method=delete <=> DELETE /my/resource
      for (size_t i = 0; i < argumentsGET.size(); i++)
      {
        if (argumentsGET[i].first == "_method")
        {
          overriden = argumentsGET[i].second;
          break;
        }
      }
    }

    if (overriden.size() > 0)
    {
      // A faking has been done within this request
      Toolbox::ToUpperCase(overriden);

      LOG(INFO) << "HTTP method faking has been detected for " << overriden;

      if (overriden == "PUT")
      {
        method = HttpMethod_Put;
        return true;
      }
      else if (overriden == "DELETE")
      {
        method = HttpMethod_Delete;
        return true;
      }
      else
      {
        return false;
      }
    }

    // No PUT/DELETE faking was present
    if (!strcmp(request->request_method, "GET"))
    {
      method = HttpMethod_Get;
    }
    else if (!strcmp(request->request_method, "POST"))
    {
      method = HttpMethod_Post;
    }
    else if (!strcmp(request->request_method, "DELETE"))
    {
      method = HttpMethod_Delete;
    }
    else if (!strcmp(request->request_method, "PUT"))
    {
      method = HttpMethod_Put;
    }
    else
    {
      return false;
    }    

    return true;
  }
  static PostDataStatus ParseMultipartPost(std::string &completedFile,
                                           struct mg_connection *connection,
                                           const IHttpHandler::Arguments& headers,
                                           const std::string& contentType,
                                           ChunkStore& chunkStore)
  {
    std::string boundary = "--" + contentType.substr(multipartLength);

    std::string postData;
    PostDataStatus status = ReadBody(postData, connection, headers);

    if (status != PostDataStatus_Success)
    {
      return status;
    }

    /*for (IHttpHandler::Arguments::const_iterator i = headers.begin(); i != headers.end(); i++)
      {
      std::cout << "Header [" << i->first << "] = " << i->second << "\n";
      }
      printf("CHUNK\n");*/

    typedef IHttpHandler::Arguments::const_iterator ArgumentIterator;

    ArgumentIterator requestedWith = headers.find("x-requested-with");
    ArgumentIterator fileName = headers.find("x-file-name");
    ArgumentIterator fileSizeStr = headers.find("x-file-size");

    if (requestedWith != headers.end() &&
        requestedWith->second != "XMLHttpRequest")
    {
      return PostDataStatus_Failure; 
    }

    size_t fileSize = 0;
    if (fileSizeStr != headers.end())
    {
      try
      {
        fileSize = boost::lexical_cast<size_t>(fileSizeStr->second);
      }
      catch (boost::bad_lexical_cast)
      {
        return PostDataStatus_Failure;
      }
    }

    typedef boost::find_iterator<std::string::iterator> FindIterator;
    typedef boost::iterator_range<char*> Range;

    //chunkStore.Print();

    try
    {
      FindIterator last;
      for (FindIterator it =
             make_find_iterator(postData, boost::first_finder(boundary));
           it!=FindIterator();
           ++it)
      {
        if (last != FindIterator())
        {
          Range part(&last->back(), &it->front());
          Range content = boost::find_first(part, "\r\n\r\n");
          if (/*content != Range()*/!content.empty())
          {
            Range c(&content.back() + 1, &it->front() - 2);
            size_t chunkSize = c.size();

            if (chunkSize > 0)
            {
              const char* chunkData = &c.front();

              if (fileName == headers.end())
              {
                // This file is stored in a single chunk
                completedFile.resize(chunkSize);
                if (chunkSize > 0)
                {
                  memcpy(&completedFile[0], chunkData, chunkSize);
                }
                return PostDataStatus_Success;
              }
              else
              {
                return chunkStore.Store(completedFile, chunkData, chunkSize, fileName->second, fileSize);
              }
            }
          }
        }

        last = it;
      }
    }
    catch (std::length_error)
    {
      return PostDataStatus_Failure;
    }

    return PostDataStatus_Pending;
  }
Beispiel #16
0
TEST(RestApi, RestApiPath)
{
  IHttpHandler::Arguments args;
  UriComponents trail;

  {
    RestApiPath uri("/coucou/{abc}/d/*");
    ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/e/f/g"));
    ASSERT_EQ(1u, args.size());
    ASSERT_EQ(3u, trail.size());
    ASSERT_EQ("moi", args["abc"]);
    ASSERT_EQ("e", trail[0]);
    ASSERT_EQ("f", trail[1]);
    ASSERT_EQ("g", trail[2]);

    ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/f"));
    ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/"));
    ASSERT_FALSE(uri.Match(args, trail, "/a/moi/d"));
    ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi"));

    ASSERT_EQ(3u, uri.GetLevelCount());
    ASSERT_TRUE(uri.IsUniversalTrailing());

    ASSERT_EQ("coucou", uri.GetLevelName(0));
    ASSERT_THROW(uri.GetWildcardName(0), OrthancException);

    ASSERT_EQ("abc", uri.GetWildcardName(1));
    ASSERT_THROW(uri.GetLevelName(1), OrthancException);

    ASSERT_EQ("d", uri.GetLevelName(2));
    ASSERT_THROW(uri.GetWildcardName(2), OrthancException);
  }

  {
    RestApiPath uri("/coucou/{abc}/d");
    ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/d/e/f/g"));
    ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d"));
    ASSERT_EQ(1u, args.size());
    ASSERT_EQ(0u, trail.size());
    ASSERT_EQ("moi", args["abc"]);

    ASSERT_EQ(3u, uri.GetLevelCount());
    ASSERT_FALSE(uri.IsUniversalTrailing());

    ASSERT_EQ("coucou", uri.GetLevelName(0));
    ASSERT_THROW(uri.GetWildcardName(0), OrthancException);

    ASSERT_EQ("abc", uri.GetWildcardName(1));
    ASSERT_THROW(uri.GetLevelName(1), OrthancException);

    ASSERT_EQ("d", uri.GetLevelName(2));
    ASSERT_THROW(uri.GetWildcardName(2), OrthancException);
  }

  {
    RestApiPath uri("/*");
    ASSERT_TRUE(uri.Match(args, trail, "/a/b/c"));
    ASSERT_EQ(0u, args.size());
    ASSERT_EQ(3u, trail.size());
    ASSERT_EQ("a", trail[0]);
    ASSERT_EQ("b", trail[1]);
    ASSERT_EQ("c", trail[2]);

    ASSERT_EQ(0u, uri.GetLevelCount());
    ASSERT_TRUE(uri.IsUniversalTrailing());
  }
}