using System.Text;
namespace pgLabII.PgUtils;
public class Acl(string grantee, string rights, string grantor)
{
public string Grantee { get; } = grantee;
public string Rights { get; } = rights;
public string Grantor { get; } = grantor;
}
///
/// Parser for acl's. Acl's have a fairly simple structure grantee=right/grantor see also https://www.postgresql.org/docs/current/ddl-priv.html
/// When the grantee is empty it is "PUBLIC" which you can see as a wildcard that matches everybody that can authenticate.
/// Because the grantee and grantor name can contain special characters there is an escape mechinism in place.
/// When the name contains a conflicting character it is quoted. If the name contains a quote it is doubles.
///
public class AclParser
{
private int _position = 0;
private string _input = null!;
public Acl Parse(string acl)
{
_input = acl;
_position = 0;
ConsumeWhitespace();
string grantee = ConsumeRoleName();
string rights = ConsumeRights();
if (Current() != '/')
throw new ArgumentException("Unexpected symbol in acl string");
_position++;
string grantor = ConsumeRoleName();
return new Acl(grantee, rights, grantor);
}
///
/// There is a limited set of possible characters for the rights
///
///
///
private string ConsumeRights()
{
if (Current() != '=')
throw new ArgumentException("Unexpected symbol in acl string");
_position++;
int start = _position;
while (NotAtEnd() && Current() != '/')
_position++;
return _input.Substring(start, _position - start);
}
// Whitespace should have been consumed
// There should be either a " in which case the name is quoted
// a = in which case there is no name ie PUBLIC
// all other cases it is an unquoted name
private string ConsumeRoleName() =>
_input[_position] switch
{
'=' => "",
'"' => ParseQuotedName(),
_ => ParseUnquotedName()
};
private string ParseQuotedName()
{
_position++; // consume opening quote
int start = _position;
StringBuilder sb = new();
while (true)
{
while (NotAtEnd() && Current() != '"')
_position++;
char peek = _position + 1 < _input.Length ? _input[_position + 1] : '\0';
if (peek == '"')
{
_position++;
sb.Append(_input.AsSpan(start, _position - start));
_position++;
start = _position;
}
else
{
sb.Append(_input.AsSpan(start, _position - start));
_position++;
break;
}
}
return sb.ToString();
}
private string ParseUnquotedName()
{
int start = _position;
while (NotAtEnd() && Current() != '=')
_position++;
return _input.Substring(start, _position - start);
}
private void ConsumeWhitespace()
{
while (NotAtEnd() && char.IsWhiteSpace(Current()))
++_position;
}
private bool NotAtEnd() => _position < _input.Length;
private char Current() => _input[_position];
}