mirror of
https://github.com/bitwarden/server.git
synced 2025-07-14 06:07:36 -05:00
new icon fetching service. remove besticon dep.
This commit is contained in:
@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.Icons.Models;
|
||||
using Bit.Icons.Services;
|
||||
@ -14,37 +10,20 @@ namespace Bit.Icons.Controllers
|
||||
[Route("")]
|
||||
public class IconsController : Controller
|
||||
{
|
||||
private static readonly HttpClient _httpClient = new HttpClient(new HttpClientHandler
|
||||
{
|
||||
AllowAutoRedirect = false,
|
||||
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
|
||||
});
|
||||
private static string _pngMediaType = "image/png";
|
||||
private static byte[] _pngHeader = new byte[] { 137, 80, 78, 71 };
|
||||
private static string _icoMediaType = "image/x-icon";
|
||||
private static string _icoAltMediaType = "image/vnd.microsoft.icon";
|
||||
private static byte[] _icoHeader = new byte[] { 00, 00, 01, 00 };
|
||||
private static string _jpegMediaType = "image/jpeg";
|
||||
private static byte[] _jpegHeader = new byte[] { 255, 216, 255 };
|
||||
private static string _octetMediaType = "application/octet-stream";
|
||||
private static readonly HashSet<string> _allowedMediaTypes = new HashSet<string>{
|
||||
_pngMediaType,
|
||||
_icoMediaType,
|
||||
_icoAltMediaType,
|
||||
_jpegMediaType,
|
||||
_octetMediaType
|
||||
};
|
||||
private readonly IMemoryCache _memoryCache;
|
||||
private readonly IDomainMappingService _domainMappingService;
|
||||
private readonly IIconFetchingService _iconFetchingService;
|
||||
private readonly IconsSettings _iconsSettings;
|
||||
|
||||
public IconsController(
|
||||
IMemoryCache memoryCache,
|
||||
IDomainMappingService domainMappingService,
|
||||
IIconFetchingService iconFetchingService,
|
||||
IconsSettings iconsSettings)
|
||||
{
|
||||
_memoryCache = memoryCache;
|
||||
_domainMappingService = domainMappingService;
|
||||
_iconFetchingService = iconFetchingService;
|
||||
_iconsSettings = iconsSettings;
|
||||
}
|
||||
|
||||
@ -66,46 +45,16 @@ namespace Bit.Icons.Controllers
|
||||
var mappedDomain = _domainMappingService.MapDomain(uri.Host);
|
||||
if(!_memoryCache.TryGetValue(mappedDomain, out Icon icon))
|
||||
{
|
||||
var iconUrl = new Uri($"{_iconsSettings.BestIconBaseUrl}/icon" +
|
||||
$"?url={mappedDomain}&size=16..32..256&fallback_icon_url=" +
|
||||
$"https://raw.githubusercontent.com/bitwarden/web/master/src/images/fa-globe.png");
|
||||
var response = await _httpClient.GetAsync(iconUrl);
|
||||
response = await FollowRedirectsAsync(response, 1);
|
||||
if(!response.IsSuccessStatusCode ||
|
||||
!_allowedMediaTypes.Contains(response.Content.Headers.ContentType.MediaType))
|
||||
var result = await _iconFetchingService.GetIconAsync(mappedDomain);
|
||||
if(result == null)
|
||||
{
|
||||
return new NotFoundResult();
|
||||
}
|
||||
|
||||
var image = await response.Content.ReadAsByteArrayAsync();
|
||||
icon = new Icon
|
||||
{
|
||||
Image = image,
|
||||
Format = response.Content.Headers.ContentType.MediaType
|
||||
};
|
||||
|
||||
if(icon.Format == _octetMediaType)
|
||||
{
|
||||
if(HeaderMatch(icon, _icoHeader))
|
||||
{
|
||||
icon.Format = _icoMediaType;
|
||||
}
|
||||
else if(HeaderMatch(icon, _pngHeader))
|
||||
{
|
||||
icon.Format = _pngMediaType;
|
||||
}
|
||||
else if(HeaderMatch(icon, _jpegHeader))
|
||||
{
|
||||
icon.Format = _jpegMediaType;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new NotFoundResult();
|
||||
}
|
||||
}
|
||||
icon = result.Icon;
|
||||
|
||||
// Only cache smaller images (<= 50kb)
|
||||
if(image.Length <= 50012)
|
||||
if(icon.Image.Length <= 50012)
|
||||
{
|
||||
_memoryCache.Set(mappedDomain, icon, new MemoryCacheEntryOptions
|
||||
{
|
||||
@ -116,47 +65,5 @@ namespace Bit.Icons.Controllers
|
||||
|
||||
return new FileContentResult(icon.Image, icon.Format);
|
||||
}
|
||||
|
||||
private async Task<HttpResponseMessage> FollowRedirectsAsync(HttpResponseMessage response, int followCount)
|
||||
{
|
||||
if(response.IsSuccessStatusCode || followCount > 2)
|
||||
{
|
||||
return response;
|
||||
}
|
||||
|
||||
if((response.StatusCode == HttpStatusCode.Redirect || response.StatusCode == HttpStatusCode.MovedPermanently) &&
|
||||
response.Headers.Contains("Location"))
|
||||
{
|
||||
var locationHeader = response.Headers.GetValues("Location").FirstOrDefault();
|
||||
if(!string.IsNullOrWhiteSpace(locationHeader) &&
|
||||
Uri.TryCreate(locationHeader, UriKind.Absolute, out Uri location))
|
||||
{
|
||||
var message = new HttpRequestMessage
|
||||
{
|
||||
RequestUri = location,
|
||||
Method = HttpMethod.Get
|
||||
};
|
||||
|
||||
// Let's add some headers to look like we're coming from a web browser request. Some websites
|
||||
// will block our request without these.
|
||||
message.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36");
|
||||
message.Headers.Add("Accept-Language", "en-US,en;q=0.8");
|
||||
message.Headers.Add("Cache-Control", "no-cache");
|
||||
message.Headers.Add("Pragma", "no-cache");
|
||||
message.Headers.Add("Accept", "image/webp,image/apng,image/*,*/*;q=0.8");
|
||||
|
||||
response = await _httpClient.SendAsync(message);
|
||||
response = await FollowRedirectsAsync(response, followCount++);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private bool HeaderMatch(Icon icon, byte[] header)
|
||||
{
|
||||
return icon.Image.Length >= header.Length && header.SequenceEqual(icon.Image.Take(header.Length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user