From b84f280e04e2b1f8f69226c2ceabe133f0c206d9 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Thu, 9 Jul 2026 13:47:26 -0400 Subject: [PATCH 1/3] Add google favicon API to icon service lookups --- src/Icons/IconsSettings.cs | 1 + src/Icons/Services/IconFetchingService.cs | 52 ++++++++++++++++++++- src/Icons/appsettings.json | 3 +- test/Icons.Test/Services/ServiceTestBase.cs | 4 +- 4 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/Icons/IconsSettings.cs b/src/Icons/IconsSettings.cs index 7cfd64d1127f..d12337678c19 100644 --- a/src/Icons/IconsSettings.cs +++ b/src/Icons/IconsSettings.cs @@ -5,4 +5,5 @@ public class IconsSettings public virtual bool CacheEnabled { get; set; } public virtual int CacheHours { get; set; } public virtual long? CacheSizeLimit { get; set; } + public virtual bool GoogleFaviconEnabled { get; set; } } diff --git a/src/Icons/Services/IconFetchingService.cs b/src/Icons/Services/IconFetchingService.cs index b2b8d016a5ad..7a86814602f8 100644 --- a/src/Icons/Services/IconFetchingService.cs +++ b/src/Icons/Services/IconFetchingService.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using AngleSharp.Html.Parser; using Bit.Icons.Extensions; @@ -8,26 +8,74 @@ namespace Bit.Icons.Services; public class IconFetchingService : IIconFetchingService { + private const int GoogleFaviconSize = 64; + private readonly IHttpClientFactory _httpClientFactory; private readonly ILogger _logger; private readonly IHtmlParser _parser; private readonly IUriService _uriService; + private readonly IconsSettings _iconsSettings; - public IconFetchingService(ILogger logger, IHttpClientFactory httpClientFactory, IHtmlParser parser, IUriService uriService) + public IconFetchingService( + ILogger logger, + IHttpClientFactory httpClientFactory, + IHtmlParser parser, + IUriService uriService, + IconsSettings iconsSettings) { _logger = logger; _httpClientFactory = httpClientFactory; _parser = parser; _uriService = uriService; + _iconsSettings = iconsSettings; } public async Task GetIconAsync(string domain) { + if (_iconsSettings.GoogleFaviconEnabled) + { + var googleIcon = await GetGoogleFaviconAsync(domain); + if (googleIcon != null) + { + return googleIcon; + } + } + var domainIcons = await DomainIcons.FetchAsync(domain, _logger, _httpClientFactory, _parser, _uriService); var result = domainIcons.Where(result => result != null).FirstOrDefault(); return result ?? await GetFaviconAsync(domain); } + private async Task GetGoogleFaviconAsync(string domain) + { + // Google's undocumented favicon endpoint. Returns a PNG, 301-redirects to + // t0.gstatic.com/faviconV2. + var googleUriBuilder = new UriBuilder + { + Scheme = "https", + Host = "www.google.com", + Path = "/s2/favicons", + Query = $"domain={Uri.EscapeDataString(domain)}&sz={GoogleFaviconSize}" + }; + + if (!googleUriBuilder.TryBuild(out var googleUri)) + { + return null; + } + + try + { + // A 404 from Google indicates no favicon exists for the domain; the existing + // IconLink/IconHttpRequest pipeline propagates that as a null return. + return await new IconLink(googleUri!).FetchAsync(_logger, _httpClientFactory, _uriService); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Google favicon lookup failed for {domain}.", domain); + return null; + } + } + private async Task GetFaviconAsync(string domain) { // Fall back to favicon diff --git a/src/Icons/appsettings.json b/src/Icons/appsettings.json index 5e1113b1505a..8b401898d65b 100644 --- a/src/Icons/appsettings.json +++ b/src/Icons/appsettings.json @@ -5,7 +5,8 @@ "iconsSettings": { "cacheEnabled": true, "cacheHours": 24, - "cacheSizeLimit": null + "cacheSizeLimit": null, + "googleFaviconEnabled": true }, "changePasswordUriSettings": { "cacheEnabled": true, diff --git a/test/Icons.Test/Services/ServiceTestBase.cs b/test/Icons.Test/Services/ServiceTestBase.cs index 37d816972eaa..f19bfc51a558 100644 --- a/test/Icons.Test/Services/ServiceTestBase.cs +++ b/test/Icons.Test/Services/ServiceTestBase.cs @@ -1,4 +1,5 @@ -using Bit.Icons.Extensions; +using Bit.Icons; +using Bit.Icons.Extensions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -20,6 +21,7 @@ public ServiceTestBase() _services.ConfigureHttpClients(); _services.AddHtmlParsing(); + _services.AddSingleton(new IconsSettings()); _services.AddServices(); _provider = _services.BuildServiceProvider(); From 12a3ad32acb214f42d312f8e82e20709fe4871b2 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Thu, 9 Jul 2026 13:57:52 -0400 Subject: [PATCH 2/3] lint fixes --- src/Icons/Services/IconFetchingService.cs | 2 +- test/Icons.Test/Services/ServiceTestBase.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Icons/Services/IconFetchingService.cs b/src/Icons/Services/IconFetchingService.cs index 7a86814602f8..baeae149cc21 100644 --- a/src/Icons/Services/IconFetchingService.cs +++ b/src/Icons/Services/IconFetchingService.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using AngleSharp.Html.Parser; using Bit.Icons.Extensions; diff --git a/test/Icons.Test/Services/ServiceTestBase.cs b/test/Icons.Test/Services/ServiceTestBase.cs index f19bfc51a558..d3197ea0ee06 100644 --- a/test/Icons.Test/Services/ServiceTestBase.cs +++ b/test/Icons.Test/Services/ServiceTestBase.cs @@ -1,5 +1,4 @@ -using Bit.Icons; -using Bit.Icons.Extensions; +using Bit.Icons.Extensions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; From 4a76fd855547b60472d84e60d221c619d292830d Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Fri, 10 Jul 2026 16:31:30 -0400 Subject: [PATCH 3/3] off for self host --- src/Icons/appsettings.SelfHosted.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Icons/appsettings.SelfHosted.json b/src/Icons/appsettings.SelfHosted.json index 37faf24b5980..4091f81a1fa7 100644 --- a/src/Icons/appsettings.SelfHosted.json +++ b/src/Icons/appsettings.SelfHosted.json @@ -15,5 +15,8 @@ "internalSso": null, "internalScim": null } + }, + "iconsSettings": { + "googleFaviconEnabled": false } }