Files

105 lines
3.6 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Globalization;
using System.Net;
namespace MoShiro.Protocol;
/// <summary>
/// 单个 Agent 任务的目标:<c>http</c> 是 <c>http(s)://主机[:端口][/路径]</c>,其余 Kind 是 <c>主机[:端口]</c>(IP 或域名,IPv6 带端口时用方括号)。
/// Backend 只检查格式,不做 DNS 解析;解析在 Agent 上进行,每个解析出的地址都要在 <c>allowed_target_cidrs</c> 里。
/// </summary>
public static class TargetHost
{
public const string HttpKind = "http_request";
/// <summary>解析目标;<paramref name="port"/> 为 0 表示没有写端口,<paramref name="url"/> 只在 http(s) URL 时有值。</summary>
public static bool TryParse(string? target, out string host, out int port, out Uri? url)
{
host = "";
port = 0;
url = null;
if (string.IsNullOrWhiteSpace(target) || target.Length > 2048 || target.Any(char.IsWhiteSpace))
{
return false;
}
if (target.Contains("://", StringComparison.Ordinal))
{
if (!Uri.TryCreate(target, UriKind.Absolute, out Uri? uri) || uri.Scheme is not ("http" or "https") || uri.UserInfo.Length > 0 || !ValidHost(uri.IdnHost))
{
return false;
}
url = uri;
host = uri.IdnHost.Trim('[', ']');
port = uri.IsDefaultPort ? 0 : uri.Port;
return true;
}
// IPAddress.TryParse 会把「[v6]:端口」的端口丢掉,带方括号的先按端点解析
if (!target.StartsWith('[') && IPAddress.TryParse(target, out IPAddress? ip))
{
host = ip.ToString();
return true;
}
if (IPEndPoint.TryParse(target, out IPEndPoint? endpoint))
{
if (endpoint.Port is 0)
{
return false;
}
host = endpoint.Address.ToString();
port = endpoint.Port;
return true;
}
int colon = target.LastIndexOf(':');
string name = colon < 0 ? target : target[..colon];
if (colon >= 0 && (!int.TryParse(target.AsSpan(colon + 1), NumberStyles.None, CultureInfo.InvariantCulture, out port) || port is < 1 or > 65535))
{
return false;
}
if (!ValidHost(name))
{
return false;
}
host = name.ToLowerInvariant();
return true;
}
/// <summary>按 Kind 检查并规范化:<c>http</c> 必须是 http(s) URL,其余 Kind 必须是主机[:端口];不合格返回 null。</summary>
public static string? Normalize(string kindId, string? target)
{
if (!TryParse(target, out string host, out int port, out Uri? url) || (url is not null) != (kindId == HttpKind))
{
return null;
}
if (url is not null)
{
return url.AbsoluteUri;
}
string shown = IPAddress.TryParse(host, out IPAddress? ip) && ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6 && port != 0 ? "[" + host + "]" : host;
return port == 0 ? host : shown + ":" + port.ToString(CultureInfo.InvariantCulture);
}
private static bool ValidHost(string name)
{
if (IPAddress.TryParse(name.Trim('[', ']'), out _))
{
return true;
}
if (name.Length is 0 or > 253 || name[^1] == '-' || name[0] is '-' or '.')
{
return false;
}
return name.TrimEnd('.').Split('.').All(label => label.Length is > 0 and <= 63 && label.All(c => char.IsAsciiLetterOrDigit(c) || c == '-'));
}
}