20inline constexpr core::StringConstant WwwAuthenticateResourceMetadataParam{
22inline constexpr core::StringConstant WwwAuthenticateErrorParam{
"error"};
23inline constexpr core::StringConstant WwwAuthenticateScopeParam{
"scope"};
24inline constexpr core::StringConstant WwwAuthenticateInsufficientScopeError{
25 "insufficient_scope"};
29inline bool is_http_token_char(
char value) {
30 const auto ch =
static_cast<unsigned char>(value);
31 return std::isalnum(ch) != 0 || value ==
'!' || value ==
'#' ||
32 value ==
'$' || value ==
'%' || value ==
'&' || value ==
'\'' ||
33 value ==
'*' || value ==
'+' || value ==
'-' || value ==
'.' ||
34 value ==
'^' || value ==
'_' || value ==
'`' || value ==
'|' ||
38inline bool is_token68_char(
char value) {
39 const auto ch =
static_cast<unsigned char>(value);
40 return std::isalnum(ch) != 0 || value ==
'-' || value ==
'.' ||
41 value ==
'_' || value ==
'~' || value ==
'+' || value ==
'/' ||
45inline void skip_ows(std::string_view input, std::size_t& pos) {
46 while (pos < input.size() && (input[pos] ==
' ' || input[pos] ==
'\t')) {
51inline std::string parse_token(std::string_view input, std::size_t& pos) {
52 const auto begin = pos;
53 while (pos < input.size() && is_http_token_char(input[pos])) {
56 return std::string(input.substr(begin, pos - begin));
59inline std::string ascii_lower(std::string value) {
60 for (
auto& ch : value) {
61 if (ch >=
'A' && ch <=
'Z') {
62 ch =
static_cast<char>(ch -
'A' +
'a');
68inline bool ascii_iequals(std::string_view lhs, std::string_view rhs) {
69 if (lhs.size() != rhs.size()) {
72 for (std::size_t index = 0; index < lhs.size(); ++index) {
73 char left = lhs[index];
74 char right = rhs[index];
75 if (left >=
'A' && left <=
'Z') {
76 left =
static_cast<char>(left -
'A' +
'a');
78 if (right >=
'A' && right <=
'Z') {
79 right =
static_cast<char>(right -
'A' +
'a');
88inline core::Error www_auth_parse_error(std::string message) {
89 return core::Error{1, std::move(message), {}, std::string(AuthErrorCategory)};
92inline core::Result<std::string> parse_quoted_string(std::string_view input,
94 if (pos >= input.size() || input[pos] !=
'"') {
96 www_auth_parse_error(
"expected quoted WWW-Authenticate value"));
101 while (pos < input.size()) {
102 const auto ch = input[pos++];
107 if (pos >= input.size()) {
109 www_auth_parse_error(
"unterminated WWW-Authenticate escape"));
111 result.push_back(input[pos++]);
114 result.push_back(ch);
118 www_auth_parse_error(
"unterminated WWW-Authenticate quoted value"));
121inline bool comma_starts_parameter(std::string_view input, std::size_t pos) {
122 if (pos >= input.size() || input[pos] !=
',') {
126 skip_ows(input, pos);
127 const auto name_begin = pos;
128 const auto name = parse_token(input, pos);
129 if (name.empty() || name_begin >= input.size()) {
132 skip_ows(input, pos);
133 if (pos >= input.size() || input[pos] !=
'=') {
137 skip_ows(input, pos);
138 return pos < input.size() &&
139 (input[pos] ==
'"' || is_http_token_char(input[pos]));
142inline bool starts_parameter(std::string_view input, std::size_t pos) {
143 skip_ows(input, pos);
144 const auto name_begin = pos;
145 auto name = parse_token(input, pos);
149 skip_ows(input, pos);
150 if (name_begin >= input.size() || pos >= input.size() || input[pos] !=
'=') {
154 skip_ows(input, pos);
155 return pos < input.size() &&
156 (input[pos] ==
'"' || is_http_token_char(input[pos]));
166 MetadataMap parameters;
168 std::string parameter(std::string key)
const {
169 const auto iter = parameters.find(key);
170 if (iter != parameters.end()) {
173 const auto normalized = detail::ascii_lower(std::move(key));
174 const auto normalized_iter = parameters.find(normalized);
175 return normalized_iter == parameters.end() ? std::string{}
176 : normalized_iter->second;
180 bool bearer()
const {
return detail::ascii_iequals(scheme,
"Bearer"); }
187 challenge.parameter(std::string(WwwAuthenticateResourceMetadataParam));
196 return challenge.parameter(std::string(WwwAuthenticateErrorParam)) ==
197 WwwAuthenticateInsufficientScopeError;
202 const std::vector<WwwAuthenticateChallenge>& challenges) {
203 for (
const auto& challenge : challenges) {
205 if (value.has_value()) {
221 const std::string& header_value)
const = 0;
232 const std::string& header_value)
const override {
233 const std::string_view input(header_value);
234 std::vector<WwwAuthenticateChallenge> challenges;
238 detail::skip_ows(input, pos);
239 while (pos < input.size() && input[pos] ==
',') {
241 detail::skip_ows(input, pos);
243 if (pos >= input.size()) {
247 auto scheme = detail::parse_token(input, pos);
248 if (scheme.empty()) {
249 return mcp::core::unexpected(detail::www_auth_parse_error(
250 "expected WWW-Authenticate auth scheme"));
254 challenge.scheme = std::move(scheme);
255 detail::skip_ows(input, pos);
257 if (pos < input.size() && input[pos] !=
',') {
258 if (!detail::starts_parameter(input, pos)) {
259 auto token68 = parse_token68(input, pos);
260 if (!token68.has_value()) {
261 return mcp::core::unexpected(token68.error());
263 challenge.
token68 = std::move(*token68);
265 auto parsed = parse_parameters(input, pos, challenge);
266 if (!parsed.has_value()) {
267 return mcp::core::unexpected(parsed.error());
272 challenges.push_back(std::move(challenge));
273 if (pos >= input.size()) {
276 if (input[pos] ==
',') {
280 return mcp::core::unexpected(detail::www_auth_parse_error(
281 "expected comma after WWW-Authenticate challenge"));
290 const auto begin = pos;
291 while (pos < input.size() && detail::is_token68_char(input[pos])) {
295 return mcp::core::unexpected(
296 detail::www_auth_parse_error(
"expected WWW-Authenticate token68"));
298 const auto token68 = std::string(input.substr(begin, pos - begin));
299 detail::skip_ows(input, pos);
300 if (pos < input.size() && input[pos] !=
',') {
301 return mcp::core::unexpected(detail::www_auth_parse_error(
302 "unexpected character after WWW-Authenticate token68"));
308 std::string_view input, std::size_t& pos,
310 while (pos < input.size()) {
311 detail::skip_ows(input, pos);
313 const auto name = detail::parse_token(input, pos);
315 return mcp::core::unexpected(detail::www_auth_parse_error(
316 "expected WWW-Authenticate parameter name"));
319 detail::skip_ows(input, pos);
320 if (pos >= input.size() || input[pos] !=
'=') {
321 return mcp::core::unexpected(detail::www_auth_parse_error(
322 "expected '=' after WWW-Authenticate parameter name"));
325 detail::skip_ows(input, pos);
327 auto value = parse_parameter_value(input, pos);
328 if (!value.has_value()) {
329 return mcp::core::unexpected(value.error());
331 challenge.parameters[detail::ascii_lower(name)] = std::move(*value);
333 detail::skip_ows(input, pos);
334 if (pos >= input.size()) {
337 if (input[pos] !=
',') {
338 return mcp::core::unexpected(detail::www_auth_parse_error(
339 "expected comma after WWW-Authenticate parameter"));
341 if (!detail::comma_starts_parameter(input, pos)) {
352 if (pos >= input.size()) {
353 return mcp::core::unexpected(detail::www_auth_parse_error(
354 "expected WWW-Authenticate parameter value"));
356 if (input[pos] ==
'"') {
357 return detail::parse_quoted_string(input, pos);
359 auto value = detail::parse_token(input, pos);
361 return mcp::core::unexpected(detail::www_auth_parse_error(
362 "expected WWW-Authenticate token parameter value"));
Shared lightweight value types for cxxmcp auth contracts.
Default RFC-style parser for WWW-Authenticate challenge headers.
Definition www_auth.hpp:229
Parser boundary for HTTP authentication challenges.
Definition www_auth.hpp:216
Shared result and error primitives used by the public cxxmcp SDK.
std::monostate Unit
Success value for operations that only need to report failure.
Definition result.hpp:55
tl::expected< T, Error > Result
Alias for the SDK result type.
Definition result.hpp:64
constexpr auto unexpected(E &&value)
Creates an unexpected result value for the active expected backend.
Definition result.hpp:24
Parsed authentication challenge from a WWW-Authenticate header.
Definition www_auth.hpp:162
bool bearer() const
Returns true when this is a Bearer challenge.
Definition www_auth.hpp:180
std::optional< std::string > token68
token68 payload for schemes that use it instead of key-value parameters.
Definition www_auth.hpp:165
bool insufficient_scope(const WwwAuthenticateChallenge &challenge)
Returns true for an OAuth insufficient_scope challenge.
Definition www_auth.hpp:195
std::optional< std::string > resource_metadata_url(const WwwAuthenticateChallenge &challenge)
Returns the MCP OAuth protected-resource metadata URL, when present.
Definition www_auth.hpp:184
core::Result< std::vector< WwwAuthenticateChallenge > > parse_www_authenticate(const std::string &header_value)
Parse a WWW-Authenticate header with the default parser.
Definition www_auth.hpp:370
std::optional< std::string > first_resource_metadata_url(const std::vector< WwwAuthenticateChallenge > &challenges)
Returns the first resource_metadata parameter in parsed challenges.
Definition www_auth.hpp:201