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); } }
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"], ""); }
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"); }
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; }
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; }
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()); } }