using Bit.Core.PhishingDomainFeatures.Interfaces;
using Bit.Core.Settings;
using Microsoft.Extensions.Logging;
namespace Bit.Core.PhishingDomainFeatures;
///
/// Implementation of ICloudPhishingDomainQuery for cloud environments
/// that directly calls the external phishing domain source
///
public class CloudPhishingDomainDirectQuery : ICloudPhishingDomainQuery
{
private readonly IGlobalSettings _globalSettings;
private readonly IHttpClientFactory _httpClientFactory;
private readonly ILogger _logger;
public CloudPhishingDomainDirectQuery(
IGlobalSettings globalSettings,
IHttpClientFactory httpClientFactory,
ILogger logger)
{
_globalSettings = globalSettings;
_httpClientFactory = httpClientFactory;
_logger = logger;
}
public async Task> GetPhishingDomainsAsync()
{
if (string.IsNullOrWhiteSpace(_globalSettings.PhishingDomain?.UpdateUrl))
{
throw new InvalidOperationException("Phishing domain update URL is not configured.");
}
var httpClient = _httpClientFactory.CreateClient("PhishingDomains");
var response = await httpClient.GetAsync(_globalSettings.PhishingDomain.UpdateUrl);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return ParseDomains(content);
}
///
/// Gets the SHA256 checksum of the remote phishing domains list
///
/// The SHA256 checksum as a lowercase hex string
public async Task GetRemoteChecksumAsync()
{
if (string.IsNullOrWhiteSpace(_globalSettings.PhishingDomain?.ChecksumUrl))
{
_logger.LogWarning("Phishing domain checksum URL is not configured.");
return string.Empty;
}
try
{
var httpClient = _httpClientFactory.CreateClient("PhishingDomains");
var response = await httpClient.GetAsync(_globalSettings.PhishingDomain.ChecksumUrl);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return ParseChecksumResponse(content);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error retrieving phishing domain checksum from {Url}",
_globalSettings.PhishingDomain.ChecksumUrl);
return string.Empty;
}
}
///
/// Parses a checksum response in the format "hash *filename"
///
private static string ParseChecksumResponse(string checksumContent)
{
if (string.IsNullOrWhiteSpace(checksumContent))
{
return string.Empty;
}
// Format is typically "hash *filename"
var parts = checksumContent.Split(' ', 2);
return parts.Length > 0 ? parts[0].Trim() : string.Empty;
}
private static List ParseDomains(string content)
{
if (string.IsNullOrWhiteSpace(content))
{
return [];
}
return content
.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries)
.Select(line => line.Trim())
.Where(line => !string.IsNullOrWhiteSpace(line) && !line.StartsWith("#"))
.ToList();
}
}