Files
server/src/Icons/Models/DomainIcons.cs
Matt Gibson 4377c7a897 Rewrite Icon fetching (#3023)
* Rewrite Icon fetching

* Move validation to IconUri, Uri, or UriBuilder

* `dotnet format` 🤖

* PR suggestions

* Add not null compiler hint

* Add twitter to test case

* Move Uri manipulation to UriService

* Implement MockedHttpClient

Presents better, fluent handling of message matching and response
building.

* Add redirect handling tests

* Add testing to models

* More aggressively dispose content in icon link

* Format 🤖

* Update icon lockfile

* Convert to cloned stream for HttpResponseBuilder

Content was being disposed when HttResponseMessage was being disposed.
This avoids losing our reference to our content and allows multiple
usages of the same `MockedHttpMessageResponse`

* Move services to extension

Extension is shared by testing and allows access to services from
our service tests

* Remove unused `using`

* Prefer awaiting asyncs for better exception handling

* `dotnet format` 🤖

* Await async

* Update tests to use test TLD and ip ranges

* Remove unused interfaces

* Make assignments static when possible

* Prefer invariant comparer to downcasing

* Prefer injecting interface services to implementations

* Prefer comparer set in HashSet initialization

* Allow SVG icons

* Filter out icons with unknown formats

* Seek to beginning of MemoryStream after writing it

* More appropriate to not return icon if it's invalid

* Add svg icon test
2023-08-08 19:29:40 +00:00

101 lines
3.3 KiB
C#

#nullable enable
using System.Collections;
using AngleSharp.Html.Parser;
using Bit.Icons.Extensions;
using Bit.Icons.Services;
namespace Bit.Icons.Models;
public class DomainIcons : IEnumerable<Icon>
{
private readonly ILogger<IIconFetchingService> _logger;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IUriService _uriService;
private readonly List<Icon> _icons = new();
public string Domain { get; }
public Icon this[int i]
{
get
{
return _icons[i];
}
}
public IEnumerator<Icon> GetEnumerator() => ((IEnumerable<Icon>)_icons).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_icons).GetEnumerator();
private DomainIcons(string domain, ILogger<IIconFetchingService> logger, IHttpClientFactory httpClientFactory, IUriService uriService)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
_uriService = uriService;
Domain = domain;
}
public static async Task<DomainIcons> FetchAsync(string domain, ILogger<IIconFetchingService> logger, IHttpClientFactory httpClientFactory, IHtmlParser parser, IUriService uriService)
{
var pageIcons = new DomainIcons(domain, logger, httpClientFactory, uriService);
await pageIcons.FetchIconsAsync(parser);
return pageIcons;
}
private async Task FetchIconsAsync(IHtmlParser parser)
{
if (!Uri.TryCreate($"https://{Domain}", UriKind.Absolute, out var uri))
{
_logger.LogWarning("Bad domain: {domain}.", Domain);
return;
}
var host = uri.Host;
// first try https
using (var response = await IconHttpRequest.FetchAsync(uri, _logger, _httpClientFactory, _uriService))
{
if (response.IsSuccessStatusCode)
{
_icons.AddRange(await response.RetrieveIconsAsync(uri, Domain, parser));
return;
}
}
// then try http
uri = uri.ChangeScheme("http");
using (var response = await IconHttpRequest.FetchAsync(uri, _logger, _httpClientFactory, _uriService))
{
if (response.IsSuccessStatusCode)
{
_icons.AddRange(await response.RetrieveIconsAsync(uri, Domain, parser));
return;
}
}
var dotCount = Domain.Count(c => c == '.');
// Then try base domain
if (dotCount > 1 && DomainName.TryParseBaseDomain(Domain, out var baseDomain) &&
Uri.TryCreate($"https://{baseDomain}", UriKind.Absolute, out uri))
{
using var response = await IconHttpRequest.FetchAsync(uri, _logger, _httpClientFactory, _uriService);
if (response.IsSuccessStatusCode)
{
_icons.AddRange(await response.RetrieveIconsAsync(uri, Domain, parser));
return;
}
}
// Then try www
if (dotCount < 2 && Uri.TryCreate($"https://www.{host}", UriKind.Absolute, out uri))
{
using var response = await IconHttpRequest.FetchAsync(uri, _logger, _httpClientFactory, _uriService);
if (response.IsSuccessStatusCode)
{
_icons.AddRange(await response.RetrieveIconsAsync(uri, Domain, parser));
return;
}
}
}
}