-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into sm/sm-863
- Loading branch information
Showing
33 changed files
with
1,687 additions
and
538 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
#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; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
#nullable enable | ||
|
||
using System.Net; | ||
using Bit.Icons.Extensions; | ||
using Bit.Icons.Services; | ||
|
||
namespace Bit.Icons.Models; | ||
|
||
public class IconHttpRequest | ||
{ | ||
private const int _maxRedirects = 2; | ||
|
||
private static readonly HttpStatusCode[] _redirectStatusCodes = new HttpStatusCode[] { HttpStatusCode.Redirect, HttpStatusCode.MovedPermanently, HttpStatusCode.RedirectKeepVerb, HttpStatusCode.SeeOther }; | ||
|
||
private readonly ILogger<IIconFetchingService> _logger; | ||
private readonly HttpClient _httpClient; | ||
private readonly IHttpClientFactory _httpClientFactory; | ||
private readonly IUriService _uriService; | ||
private readonly int _redirectsCount; | ||
private readonly Uri _uri; | ||
private static HttpResponseMessage NotFound => new(HttpStatusCode.NotFound); | ||
|
||
private IconHttpRequest(Uri uri, ILogger<IIconFetchingService> logger, IHttpClientFactory httpClientFactory, IUriService uriService, int redirectsCount) | ||
{ | ||
_logger = logger; | ||
_httpClientFactory = httpClientFactory; | ||
_httpClient = _httpClientFactory.CreateClient("Icons"); | ||
_uriService = uriService; | ||
_redirectsCount = redirectsCount; | ||
_uri = uri; | ||
} | ||
|
||
public static async Task<IconHttpResponse> FetchAsync(Uri uri, ILogger<IIconFetchingService> logger, IHttpClientFactory httpClientFactory, IUriService uriService) | ||
{ | ||
var pageIcons = new IconHttpRequest(uri, logger, httpClientFactory, uriService, 0); | ||
var httpResponse = await pageIcons.FetchAsync(); | ||
return new IconHttpResponse(httpResponse, logger, httpClientFactory, uriService); | ||
} | ||
|
||
private async Task<HttpResponseMessage> FetchAsync() | ||
{ | ||
if (!_uriService.TryGetUri(_uri, out var iconUri) || !iconUri!.IsValid) | ||
{ | ||
return NotFound; | ||
} | ||
|
||
var response = await GetAsync(iconUri); | ||
|
||
if (response.IsSuccessStatusCode) | ||
{ | ||
return response; | ||
} | ||
|
||
using var responseForRedirect = response; | ||
return await FollowRedirectsAsync(responseForRedirect, iconUri); | ||
} | ||
|
||
|
||
private async Task<HttpResponseMessage> GetAsync(IconUri iconUri) | ||
{ | ||
using var message = new HttpRequestMessage(); | ||
message.RequestUri = iconUri.InnerUri; | ||
message.Headers.Host = iconUri.Host; | ||
message.Method = HttpMethod.Get; | ||
|
||
try | ||
{ | ||
return await _httpClient.SendAsync(message); | ||
} | ||
catch | ||
{ | ||
return NotFound; | ||
} | ||
} | ||
|
||
private async Task<HttpResponseMessage> FollowRedirectsAsync(HttpResponseMessage response, IconUri originalIconUri) | ||
{ | ||
if (_redirectsCount >= _maxRedirects || response.Headers.Location == null || | ||
!_redirectStatusCodes.Contains(response.StatusCode)) | ||
{ | ||
return NotFound; | ||
} | ||
|
||
using var responseForRedirect = response; | ||
var redirectUri = DetermineRedirectUri(responseForRedirect.Headers.Location, originalIconUri); | ||
|
||
return await new IconHttpRequest(redirectUri, _logger, _httpClientFactory, _uriService, _redirectsCount + 1).FetchAsync(); | ||
} | ||
|
||
private static Uri DetermineRedirectUri(Uri responseUri, IconUri originalIconUri) | ||
{ | ||
if (responseUri.IsAbsoluteUri) | ||
{ | ||
if (!responseUri.IsHypertext()) | ||
{ | ||
return responseUri.ChangeScheme("https"); | ||
} | ||
return responseUri; | ||
} | ||
else | ||
{ | ||
return new UriBuilder | ||
{ | ||
Scheme = originalIconUri.Scheme, | ||
Host = originalIconUri.Host, | ||
Path = responseUri.ToString() | ||
}.Uri; | ||
} | ||
} | ||
} |
Oops, something went wrong.