Reputation: 3218
I am working on a client for a RESTful service, using .NET Core 2.0. The remote service returns challenges like this:
WwwAuthenticate: Bearer realm="https://somesite/auth",service="some site",scope="some scope"
Which need to get turned into token requests like:
GET https://somesite/auth?service=some%20site&scope=some%20scope
Parsing the header to get a scheme and parameter is easy with AuthenticationHeaderValue
, but that just gets me the realm="https://somesite/auth",service="some site",scope="some scope"
string. How can I easily and reliably parse this to the individual realm
, service
, and scope
components? It's not quite JSON, so deserializing it with NewtonSoft JsonConvert
won't work. I could regex it into something that looks like XML or JSON, but that seems incredibly hacky (not to mention unreliable).
Surely there's a better way?
Upvotes: 1
Views: 535
Reputation: 5245
Possible duplicate of How to parse values from Www-Authenticate
Using the schema defined in RFC6750 and RFC2616, a slightly more precise parser implementation is included below. This parser takes into account the possibility that strings might contain =
, ,
, and/or escaped "
.
internal class AuthParamParser
{
private string _buffer;
private int _i;
private AuthParamParser(string param)
{
_buffer = param;
_i = 0;
}
public static Dictionary<string, string> Parse(string param)
{
var state = new AuthParamParser(param);
var result = new Dictionary<string, string>();
var token = state.ReadToken();
while (!string.IsNullOrEmpty(token))
{
if (!state.ReadDelim('='))
return result;
result.Add(token, state.ReadString());
if (!state.ReadDelim(','))
return result;
token = state.ReadToken();
}
return result;
}
private string ReadToken()
{
var start = _i;
while (_i < _buffer.Length && ValidTokenChar(_buffer[_i]))
_i++;
return _buffer.Substring(start, _i - start);
}
private bool ReadDelim(char ch)
{
while (_i < _buffer.Length && char.IsWhiteSpace(_buffer[_i]))
_i++;
if (_i >= _buffer.Length || _buffer[_i] != ch)
return false;
_i++;
while (_i < _buffer.Length && char.IsWhiteSpace(_buffer[_i]))
_i++;
return true;
}
private string ReadString()
{
if (_i < _buffer.Length && _buffer[_i] == '"')
{
var buffer = new StringBuilder();
_i++;
while (_i < _buffer.Length)
{
if (_buffer[_i] == '\\' && (_i + 1) < _buffer.Length)
{
_i++;
buffer.Append(_buffer[_i]);
_i++;
}
else if (_buffer[_i] == '"')
{
_i++;
return buffer.ToString();
}
else
{
buffer.Append(_buffer[_i]);
_i++;
}
}
return buffer.ToString();
}
else
{
return ReadToken();
}
}
private bool ValidTokenChar(char ch)
{
if (ch < 32)
return false;
if (ch == '(' || ch == ')' || ch == '<' || ch == '>' || ch == '@'
|| ch == ',' || ch == ';' || ch == ':' || ch == '\\' || ch == '"'
|| ch == '/' || ch == '[' || ch == ']' || ch == '?' || ch == '='
|| ch == '{' || ch == '}' || ch == 127 || ch == ' ' || ch == '\t')
return false;
return true;
}
}
Upvotes: 1
Reputation: 12546
Since I don't see a non-hacky way. Maybe this hacky way may help
string input = @"WwwAuthenticate: Bearer realm=""https://somesite/auth"",service=""some site"",scope=""some, scope""";
var dict = Regex.Matches(input, @"[\W]+(\w+)=""(.+?)""").Cast<Match>()
.ToDictionary(x => x.Groups[1].Value, x => x.Groups[2].Value);
var url = dict["realm"] + "?" + string.Join("&", dict.Where(x => x.Key != "realm").Select(x => x.Key + "=" + WebUtility.UrlEncode(x.Value)));
OUTPUT
url => https://somesite/auth?service=some+site&scope=some%2C+scope
BTW: I added a ,
in "scope"
Upvotes: 2