-
Notifications
You must be signed in to change notification settings - Fork 65
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into processMultipleOperations
- Loading branch information
Showing
29 changed files
with
1,416 additions
and
806 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
// Copyright 2025, University of Freiburg | ||
// Chair of Algorithms and Data Structures | ||
// Authors: Julian Mundhahs <[email protected]> | ||
|
||
#include "ParsedRequestBuilder.h" | ||
|
||
using namespace ad_utility::url_parser::sparqlOperation; | ||
|
||
// ____________________________________________________________________________ | ||
ParsedRequestBuilder::ParsedRequestBuilder(const RequestType& request) { | ||
using namespace ad_utility::url_parser::sparqlOperation; | ||
// For an HTTP request, `request.target()` yields the HTTP Request-URI. | ||
// This is a concatenation of the URL path and the query strings. | ||
auto parsedUrl = ad_utility::url_parser::parseRequestTarget(request.target()); | ||
parsedRequest_ = {std::move(parsedUrl.path_), std::nullopt, | ||
std::move(parsedUrl.parameters_), None{}}; | ||
} | ||
|
||
// ____________________________________________________________________________ | ||
void ParsedRequestBuilder::extractAccessToken(const RequestType& request) { | ||
parsedRequest_.accessToken_ = | ||
determineAccessToken(request, parsedRequest_.parameters_); | ||
} | ||
|
||
// ____________________________________________________________________________ | ||
void ParsedRequestBuilder::extractDatasetClauses() { | ||
extractDatasetClauseIfOperationIs<Query>("default-graph-uri", false); | ||
extractDatasetClauseIfOperationIs<Query>("named-graph-uri", true); | ||
extractDatasetClauseIfOperationIs<Update>("using-graph-uri", false); | ||
extractDatasetClauseIfOperationIs<Update>("using-named-graph-uri", true); | ||
} | ||
|
||
// ____________________________________________________________________________ | ||
bool ParsedRequestBuilder::parameterIsContainedExactlyOnce( | ||
std::string_view key) const { | ||
return ad_utility::url_parser::getParameterCheckAtMostOnce( | ||
parsedRequest_.parameters_, key) | ||
.has_value(); | ||
} | ||
|
||
// ____________________________________________________________________________ | ||
bool ParsedRequestBuilder::isGraphStoreOperation() const { | ||
return parameterIsContainedExactlyOnce("graph") || | ||
parameterIsContainedExactlyOnce("default"); | ||
} | ||
|
||
// ____________________________________________________________________________ | ||
void ParsedRequestBuilder::extractGraphStoreOperation() { | ||
// SPARQL Graph Store HTTP Protocol with indirect graph identification | ||
if (parameterIsContainedExactlyOnce("graph") && | ||
parameterIsContainedExactlyOnce("default")) { | ||
throw std::runtime_error( | ||
R"(Parameters "graph" and "default" must not be set at the same time.)"); | ||
} | ||
AD_CORRECTNESS_CHECK(std::holds_alternative<None>(parsedRequest_.operation_)); | ||
// We only support passing the target graph as a query parameter | ||
// (`Indirect Graph Identification`). `Direct Graph Identification` (the | ||
// URL is the graph) is not supported. See also | ||
// https://www.w3.org/TR/2013/REC-sparql11-http-rdf-update-20130321/#graph-identification. | ||
parsedRequest_.operation_ = | ||
GraphStoreOperation{extractTargetGraph(parsedRequest_.parameters_)}; | ||
} | ||
|
||
// ____________________________________________________________________________ | ||
bool ParsedRequestBuilder::parametersContain(std::string_view param) const { | ||
return parsedRequest_.parameters_.contains(param); | ||
} | ||
|
||
// ____________________________________________________________________________ | ||
ad_utility::url_parser::ParsedRequest ParsedRequestBuilder::build() && { | ||
return std::move(parsedRequest_); | ||
} | ||
|
||
// ____________________________________________________________________________ | ||
void ParsedRequestBuilder::reportUnsupportedContentTypeIfGraphStore( | ||
std::string_view contentType) const { | ||
if (isGraphStoreOperation()) { | ||
throw std::runtime_error(absl::StrCat("Unsupported Content type \"", | ||
contentType, | ||
"\" for Graph Store protocol.")); | ||
} | ||
} | ||
|
||
// ____________________________________________________________________________ | ||
template <typename Operation> | ||
void ParsedRequestBuilder::extractDatasetClauseIfOperationIs( | ||
const std::string& key, bool isNamed) { | ||
if (Operation* op = std::get_if<Operation>(&parsedRequest_.operation_)) { | ||
ad_utility::appendVector(op->datasetClauses_, | ||
ad_utility::url_parser::parseDatasetClausesFrom( | ||
parsedRequest_.parameters_, key, isNamed)); | ||
} | ||
} | ||
|
||
// ____________________________________________________________________________ | ||
template <typename Operation> | ||
void ParsedRequestBuilder::extractOperationIfSpecified(string_view paramName) { | ||
auto operation = ad_utility::url_parser::getParameterCheckAtMostOnce( | ||
parsedRequest_.parameters_, paramName); | ||
if (operation.has_value()) { | ||
AD_CORRECTNESS_CHECK( | ||
std::holds_alternative<None>(parsedRequest_.operation_)); | ||
parsedRequest_.operation_ = Operation{operation.value(), {}}; | ||
parsedRequest_.parameters_.erase(paramName); | ||
} | ||
} | ||
|
||
template void ParsedRequestBuilder::extractOperationIfSpecified<Query>( | ||
string_view paramName); | ||
template void ParsedRequestBuilder::extractOperationIfSpecified<Update>( | ||
string_view paramName); | ||
|
||
// ____________________________________________________________________________ | ||
GraphOrDefault ParsedRequestBuilder::extractTargetGraph( | ||
const ad_utility::url_parser::ParamValueMap& params) { | ||
const std::optional<std::string> graphIri = | ||
ad_utility::url_parser::checkParameter(params, "graph", std::nullopt); | ||
const bool isDefault = | ||
ad_utility::url_parser::checkParameter(params, "default", "").has_value(); | ||
if (graphIri.has_value() == isDefault) { | ||
throw std::runtime_error( | ||
R"(Exactly one of the query parameters "default" or "graph" must be set to identify the graph for the graph store protocol request.)"); | ||
} | ||
if (graphIri.has_value()) { | ||
return GraphRef::fromIrirefWithoutBrackets(graphIri.value()); | ||
} else { | ||
AD_CORRECTNESS_CHECK(isDefault); | ||
return DEFAULT{}; | ||
} | ||
} | ||
|
||
// ____________________________________________________________________________ | ||
std::optional<std::string> ParsedRequestBuilder::determineAccessToken( | ||
const RequestType& request, | ||
const ad_utility::url_parser::ParamValueMap& params) { | ||
namespace http = boost::beast::http; | ||
std::optional<std::string> tokenFromAuthorizationHeader; | ||
std::optional<std::string> tokenFromParameter; | ||
if (request.find(http::field::authorization) != request.end()) { | ||
string_view authorization = request[http::field::authorization]; | ||
const std::string prefix = "Bearer "; | ||
if (!authorization.starts_with(prefix)) { | ||
throw std::runtime_error(absl::StrCat( | ||
"Authorization header doesn't start with \"", prefix, "\".")); | ||
} | ||
authorization.remove_prefix(prefix.length()); | ||
tokenFromAuthorizationHeader = std::string(authorization); | ||
} | ||
if (params.contains("access-token")) { | ||
tokenFromParameter = ad_utility::url_parser::getParameterCheckAtMostOnce( | ||
params, "access-token"); | ||
} | ||
// If both are specified, they must be equal. This way there is no hidden | ||
// precedence. | ||
if (tokenFromAuthorizationHeader && tokenFromParameter && | ||
tokenFromAuthorizationHeader != tokenFromParameter) { | ||
throw std::runtime_error( | ||
"Access token is specified both in the `Authorization` header and by " | ||
"the `access-token` parameter, but they are not the same"); | ||
} | ||
return tokenFromAuthorizationHeader ? std::move(tokenFromAuthorizationHeader) | ||
: std::move(tokenFromParameter); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
// Copyright 2025, University of Freiburg | ||
// Chair of Algorithms and Data Structures | ||
// Authors: Julian Mundhahs <[email protected]> | ||
|
||
#pragma once | ||
|
||
#include "util/http/UrlParser.h" | ||
#include "util/http/beast.h" | ||
|
||
// Helper for parsing `HttpRequest` into `ParsedRequest`. The parsing has many | ||
// common patterns but the details are slightly different. This struct | ||
// stores the partially parsed `ParsedRequest` and methods for common | ||
// operations used while parsing. | ||
struct ParsedRequestBuilder { | ||
FRIEND_TEST(ParsedRequestBuilderTest, extractTargetGraph); | ||
FRIEND_TEST(ParsedRequestBuilderTest, determineAccessToken); | ||
FRIEND_TEST(ParsedRequestBuilderTest, parameterIsContainedExactlyOnce); | ||
|
||
using RequestType = | ||
boost::beast::http::request<boost::beast::http::string_body>; | ||
|
||
ad_utility::url_parser::ParsedRequest parsedRequest_; | ||
|
||
// Initialize a `ParsedRequestBuilder`, parsing the request target into the | ||
// `ParsedRequest`. | ||
explicit ParsedRequestBuilder(const RequestType& request); | ||
|
||
// Extract the access token from the access-token parameter or the | ||
// Authorization header and set it for `ParsedRequest`. If both are given, | ||
// then they must be the same. | ||
void extractAccessToken(const RequestType& request); | ||
|
||
// If applicable extract the dataset clauses from the parameters and set them | ||
// on the Query or Update. | ||
void extractDatasetClauses(); | ||
|
||
// If the parameter is set, set the operation with the parameter's value as | ||
// operation string and empty dataset clauses. Setting an operation when one | ||
// is already set is an error. Note: processed parameters are removed from the | ||
// parameter map. | ||
template <typename Operation> | ||
void extractOperationIfSpecified(string_view paramName); | ||
|
||
// Returns whether the request is a Graph Store operation. | ||
bool isGraphStoreOperation() const; | ||
|
||
// Set the operation to the parsed Graph Store operation. | ||
void extractGraphStoreOperation(); | ||
|
||
// Returns whether the parameters contain a parameter with the given key. | ||
bool parametersContain(std::string_view param) const; | ||
|
||
// Check that requests don't both have these content types and are Graph | ||
// Store operations. | ||
void reportUnsupportedContentTypeIfGraphStore( | ||
std::string_view contentType) const; | ||
|
||
// Move the `ParsedRequest` out when parsing is finished. | ||
ad_utility::url_parser::ParsedRequest build() &&; | ||
|
||
private: | ||
// Adds a dataset clause to the operation if it is of the given type. The | ||
// dataset clause's IRI is the value of parameter `key`. The `isNamed_` of the | ||
// dataset clause is as given. | ||
template <typename Operation> | ||
void extractDatasetClauseIfOperationIs(const std::string& key, bool isNamed); | ||
|
||
// Check that a parameter is contained exactly once. An exception is thrown if | ||
// a parameter is contained more than once. | ||
bool parameterIsContainedExactlyOnce(std::string_view key) const; | ||
|
||
// Extract the graph to be acted upon using from the URL query parameters | ||
// (`Indirect Graph Identification`). See | ||
// https://www.w3.org/TR/2013/REC-sparql11-http-rdf-update-20130321/#indirect-graph-identification | ||
static GraphOrDefault extractTargetGraph( | ||
const ad_utility::url_parser::ParamValueMap& params); | ||
|
||
// Determine the access token from the parameters and the requests | ||
// Authorization header. | ||
static std::optional<std::string> determineAccessToken( | ||
const RequestType& request, | ||
const ad_utility::url_parser::ParamValueMap& params); | ||
}; |
Oops, something went wrong.