2025-10-26 14:14:00 +01:00
|
|
|
|
using System.Globalization;
|
2025-08-31 13:11:59 +02:00
|
|
|
|
using Npgsql;
|
|
|
|
|
|
|
|
|
|
|
|
namespace pgLabII.PgUtils.ConnectionStrings;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Shared helper utilities for codecs to reduce duplication (SSL mode mapping, host:port parsing/formatting,
|
|
|
|
|
|
/// URL query parsing, and .NET/libpq quoting helpers).
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
internal static class CodecCommon
|
|
|
|
|
|
{
|
|
|
|
|
|
// SSL mapping
|
|
|
|
|
|
public static SslMode ParseSslModeLoose(string s)
|
|
|
|
|
|
=> s.Trim().ToLowerInvariant() switch
|
|
|
|
|
|
{
|
|
|
|
|
|
"disable" => SslMode.Disable,
|
|
|
|
|
|
"allow" => SslMode.Allow,
|
|
|
|
|
|
"prefer" => SslMode.Prefer,
|
|
|
|
|
|
"require" => SslMode.Require,
|
|
|
|
|
|
"verify-ca" or "verifyca" => SslMode.VerifyCA,
|
|
|
|
|
|
"verify-full" or "verifyfull" => SslMode.VerifyFull,
|
|
|
|
|
|
_ => throw new ArgumentException($"Not a valid SSL Mode: {s}")
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
public static string FormatSslModeUrlLike(SslMode mode) => mode switch
|
|
|
|
|
|
{
|
|
|
|
|
|
SslMode.Disable => "disable",
|
|
|
|
|
|
SslMode.Allow => "allow",
|
|
|
|
|
|
SslMode.Prefer => "prefer",
|
|
|
|
|
|
SslMode.Require => "require",
|
|
|
|
|
|
SslMode.VerifyCA => "verify-ca",
|
|
|
|
|
|
SslMode.VerifyFull => "verify-full",
|
|
|
|
|
|
_ => "prefer"
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// host:port parsing for plain or [IPv6]:port
|
|
|
|
|
|
public static void ParseHostPort(string hostPart, out string host, out ushort? port)
|
|
|
|
|
|
{
|
|
|
|
|
|
host = hostPart;
|
|
|
|
|
|
port = null;
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(hostPart))
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
if (hostPart[0] == '[')
|
|
|
|
|
|
{
|
|
|
|
|
|
int end = hostPart.IndexOf(']');
|
|
|
|
|
|
if (end > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
host = hostPart[1..end];
|
|
|
|
|
|
if (end + 1 < hostPart.Length && hostPart[end + 1] == ':')
|
|
|
|
|
|
{
|
|
|
|
|
|
string ps = hostPart[(end + 2)..];
|
|
|
|
|
|
if (ushort.TryParse(ps, NumberStyles.Integer, CultureInfo.InvariantCulture, out var p))
|
|
|
|
|
|
port = p;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
int colon = hostPart.LastIndexOf(':');
|
|
|
|
|
|
if (colon > 0 && colon < hostPart.Length - 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
var ps = hostPart.Substring(colon + 1);
|
|
|
|
|
|
if (ushort.TryParse(ps, NumberStyles.Integer, CultureInfo.InvariantCulture, out var p))
|
|
|
|
|
|
{
|
|
|
|
|
|
host = hostPart.Substring(0, colon);
|
|
|
|
|
|
port = p;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static string FormatHost(HostEndpoint h)
|
|
|
|
|
|
{
|
|
|
|
|
|
var host = h.Host;
|
|
|
|
|
|
if (host.Contains(':') && !host.StartsWith("["))
|
|
|
|
|
|
host = "[" + host + "]"; // IPv6
|
|
|
|
|
|
|
|
|
|
|
|
return h.Port.HasValue ? host + ":" + h.Port.Value.ToString(CultureInfo.InvariantCulture) : host;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static string[] SplitHosts(string hostList)
|
|
|
|
|
|
=> hostList.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
|
|
|
|
|
|
|
|
|
|
|
public static Dictionary<string, string> ParseQuery(string query)
|
|
|
|
|
|
{
|
|
|
|
|
|
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
|
if (string.IsNullOrEmpty(query)) return dict;
|
|
|
|
|
|
foreach (var kv in query.Split('&', StringSplitOptions.RemoveEmptyEntries))
|
|
|
|
|
|
{
|
|
|
|
|
|
var parts = kv.Split('=', 2);
|
|
|
|
|
|
var key = Uri.UnescapeDataString(parts[0]);
|
|
|
|
|
|
var val = parts.Length > 1 ? Uri.UnescapeDataString(parts[1]) : string.Empty;
|
|
|
|
|
|
dict[key] = val;
|
|
|
|
|
|
}
|
|
|
|
|
|
return dict;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|