Files
Deal/src/core/Deal.Infrastructure/Integrations/UriExtensions.cs
T
Rustam Khalimov 413eaac48c Вынести условия-предикаты в extension-методы
HasUser из 13 endpoint-файлов сведён в AuthHelpers.HasUser;
15 приватных предикатов заменены extension-методами с удалением
дублирующих приватных методов: IsCommunicationFailure,
IsTransportFailure, IsPrivateEndpoint, IsConfigured, IsTrue,
IsExpired, IsFailedLogin/IsSuccessfulLogin, HasChanges, HasBudget,
HasAnyTerm, IsCurrencyLetter, IsEmojiCodePoint, ContainsFooterHint,
IsTypeLabel.
2026-09-11 13:08:47 +03:00

56 lines
2.2 KiB
C#

using System.Net;
using System.Net.Sockets;
namespace Deal.Infrastructure.Integrations;
/// <summary>
/// Расширения <see cref="Uri"/> для SSRF-гейта интеграций (проверка приватности адреса).
/// </summary>
internal static class UriExtensions
{
/// <summary>
/// Проверяет, указывает ли URL на приватный/loopback/link-local адрес (SSRF-гейт).
/// Распознаются IP-литералы (IPv4/IPv6) и имя localhost; DNS-имена считаются публичными
/// (полный egress-контроль с резолвом выполняется на сетевом периметре).
/// </summary>
/// <param name="uri">Абсолютный http(s)-адрес.</param>
/// <returns>True — адрес приватный/локальный (HTTP к нему запрещён).</returns>
public static bool IsPrivateEndpoint(this Uri uri)
{
string host = uri.Host;
if (string.Equals(host, "localhost", StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (!IPAddress.TryParse(host, out IPAddress? address))
{
return false; // DNS-имя — резолв вне этого слоя
}
if (address.IsIPv4MappedToIPv6)
{
address = address.MapToIPv4();
}
if (IPAddress.IsLoopback(address))
{
return true;
}
if (address.AddressFamily == AddressFamily.InterNetwork)
{
byte[] bytes = address.GetAddressBytes();
return bytes[0] == 10
|| (bytes[0] == 172 && bytes[1] is >= 16 and <= 31)
|| (bytes[0] == 192 && bytes[1] == 168)
|| bytes[0] == 169 && bytes[1] == 254 // link-local (включая 169.254.169.254 metadata)
|| bytes[0] == 0;
}
// IPv6: уникальные локальные (fc00::/7) и link-local (fe80::/10).
byte[] v6 = address.GetAddressBytes();
return (v6[0] & 0xFE) == 0xFC || (v6[0] == 0xFE && (v6[1] & 0xC0) == 0x80);
}
}