Skip to content

Commit

Permalink
Merge branch 'master' into sm/sm-863
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas-Avery authored Aug 8, 2023
2 parents a5ae4b4 + a04fdcd commit e5409fa
Show file tree
Hide file tree
Showing 33 changed files with 1,687 additions and 538 deletions.
14 changes: 0 additions & 14 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@
"stopAtEntry": false,
"env": {
"ASPNETCORE_ENVIRONMENT": "Development",
"WEBSITE_INSTANCE_ID": "dev",
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
Expand All @@ -124,7 +123,6 @@
"stopAtEntry": false,
"env": {
"ASPNETCORE_ENVIRONMENT": "Development",
"WEBSITE_INSTANCE_ID": "dev",
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
Expand All @@ -147,7 +145,6 @@
"stopAtEntry": false,
"env": {
"ASPNETCORE_ENVIRONMENT": "Development",
"WEBSITE_INSTANCE_ID": "dev",
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
Expand All @@ -172,7 +169,6 @@
"OS-COMMENT5": "Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser",
"env": {
"ASPNETCORE_ENVIRONMENT": "Development",
"WEBSITE_INSTANCE_ID": "dev",
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
Expand All @@ -195,7 +191,6 @@
"stopAtEntry": false,
"env": {
"ASPNETCORE_ENVIRONMENT": "Development",
"WEBSITE_INSTANCE_ID": "dev",
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
Expand All @@ -218,7 +213,6 @@
"stopAtEntry": false,
"env": {
"ASPNETCORE_ENVIRONMENT": "Development",
"WEBSITE_INSTANCE_ID": "dev",
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
Expand All @@ -241,7 +235,6 @@
"stopAtEntry": false,
"env": {
"ASPNETCORE_ENVIRONMENT": "Development",
"WEBSITE_INSTANCE_ID": "dev",
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
Expand All @@ -264,7 +257,6 @@
"stopAtEntry": false,
"env": {
"ASPNETCORE_ENVIRONMENT": "Development",
"WEBSITE_INSTANCE_ID": "dev",
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
Expand All @@ -289,7 +281,6 @@
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_URLS": "http://localhost:33657",
"developSelfHosted": "true",
"WEBSITE_INSTANCE_ID": "dev",
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
Expand All @@ -314,7 +305,6 @@
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_URLS": "http://localhost:4001",
"developSelfHosted": "true",
"WEBSITE_INSTANCE_ID": "dev",
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
Expand All @@ -341,7 +331,6 @@
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_URLS": "http://localhost:62912",
"developSelfHosted": "true",
"WEBSITE_INSTANCE_ID": "dev",
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
Expand All @@ -366,7 +355,6 @@
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_URLS": "http://localhost:51822",
"developSelfHosted": "true",
"WEBSITE_INSTANCE_ID": "dev",
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
Expand All @@ -391,7 +379,6 @@
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_URLS": "http://localhost:61841",
"developSelfHosted": "true",
"WEBSITE_INSTANCE_ID": "dev",
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
Expand All @@ -416,7 +403,6 @@
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_URLS": "http://localhost:46274",
"developSelfHosted": "true",
"WEBSITE_INSTANCE_ID": "dev",
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
Expand Down
2 changes: 1 addition & 1 deletion src/Icons/Controllers/IconsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public async Task<IActionResult> Get(string hostname)
}
else
{
icon = result.Icon;
icon = result;
}

// Only cache not found and smaller images (<= 50kb)
Expand Down
100 changes: 100 additions & 0 deletions src/Icons/Models/DomainIcons.cs
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;
}
}
}
}
110 changes: 110 additions & 0 deletions src/Icons/Models/IconHttpRequest.cs
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;
}
}
}
Loading

0 comments on commit e5409fa

Please sign in to comment.