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]; }