X Tutup
using System; using System.Collections.Generic; using System.IO; using System.Text; namespace Npgsql; /// /// Represents a .pgpass file, which contains passwords for noninteractive connections /// sealed class PgPassFile { #region Properties /// /// File name being parsed for credentials /// internal string FileName { get; } #endregion #region Construction /// /// Initializes a new instance of the class /// /// public PgPassFile(string fileName) => FileName = fileName; #endregion /// /// Parses file content and gets all credentials from the file /// /// corresponding to all lines in the .pgpass file internal IEnumerable Entries { get { var bytes = File.ReadAllBytes(FileName); var mem = new MemoryStream(bytes); using var reader = new StreamReader(mem); while (reader.ReadLine() is { } l) { var line = l.Trim(); if (line.Length > 0 && line[0] != '#') yield return Entry.Parse(line); } } } /// /// Searches queries loaded from .PGPASS file to find first entry matching the provided parameters. /// /// Hostname to query. Use null to match any. /// Port to query. Use null to match any. /// Database to query. Use null to match any. /// User name to query. Use null to match any. /// Matching if match was found. Otherwise, returns null. internal Entry? GetFirstMatchingEntry(string? host = null, int? port = null, string? database = null, string? username = null) { foreach (var entry in Entries) if (entry.IsMatch(host, port, database, username)) return entry; return null; } /// /// Represents a hostname, port, database, username, and password combination that has been retrieved from a .pgpass file /// internal sealed class Entry { #region Fields and Properties /// /// Hostname parsed from the .pgpass file /// internal string? Host { get; } /// /// Port parsed from the .pgpass file /// internal int? Port { get; } /// /// Database parsed from the .pgpass file /// internal string? Database { get; } /// /// User name parsed from the .pgpass file /// internal string? Username { get; } /// /// Password parsed from the .pgpass file /// internal string? Password { get; } #endregion #region Construction / Initialization /// /// This class represents an entry from the .pgpass file /// /// Hostname parsed from the .pgpass file /// Port parsed from the .pgpass file /// Database parsed from the .pgpass file /// User name parsed from the .pgpass file /// Password parsed from the .pgpass file Entry(string? host, int? port, string? database, string? username, string? password) { Host = host; Port = port; Database = database; Username = username; Password = password; } /// /// Creates new based on string in the format hostname:port:database:username:password. The : and \ characters should be escaped with a \. /// /// string for the entry from the pgpass file /// New instance of for the string /// Entry is not formatted as hostname:port:database:username:password or non-wildcard port is not a number internal static Entry Parse(string serializedEntry) { var parts = new List(5); var builder = new StringBuilder(); for (var pos = 0; pos < serializedEntry.Length; pos++) { var c = serializedEntry[pos]; switch (c) { case '\\' when pos < serializedEntry.Length - 1: // Strip backslash before colon or backslash, otherwise preserve it c = serializedEntry[++pos]; if (c is not (':' or '\\')) { builder.Append('\\'); } builder.Append(c); continue; case ':': var part = builder.ToString(); parts.Add(part == "*" ? null : part); builder.Clear(); continue; default: builder.Append(c); continue; } } var lastPart = builder.ToString(); parts.Add(lastPart == "*" ? null : lastPart); if (parts.Count != 5) throw new FormatException("pgpass entry was not well-formed. Please ensure all non-comment entries are formatted as hostname:port:database:username:password. If colon is included, it must be escaped like \\:."); int? port = null; if (parts[1] != null) { if (!int.TryParse(parts[1], out var tempPort)) throw new FormatException("pgpass entry was not formatted correctly. Port must be a valid integer."); port = tempPort; } return new Entry(parts[0], port, parts[2], parts[3], parts[4]); } #endregion /// /// Checks whether this matches the parameters supplied /// /// Hostname to check against this entry /// Port to check against this entry /// Database to check against this entry /// Username to check against this entry /// True if the entry is a match. False otherwise. internal bool IsMatch(string? host, int? port, string? database, string? username) => AreValuesMatched(host, Host) && AreValuesMatched(port, Port) && AreValuesMatched(database, Database) && AreValuesMatched(username, Username); /// /// Checks if 2 strings are a match for a considering that either value can be a wildcard (*) /// /// Value being searched /// Value from the PGPASS entry /// True if the values are a match. False otherwise. bool AreValuesMatched(string? query, string? actual) => query == actual || actual == null || query == null; bool AreValuesMatched(int? query, int? actual) => query == actual || actual == null || query == null; } }
X Tutup