1
0
mirror of https://github.com/bitwarden/server synced 2025-12-19 09:43:25 +00:00

cleanup memory refs. switch to anglesharp lib

This commit is contained in:
Kyle Spearrin
2018-06-19 15:14:12 -04:00
parent 145e4c69d4
commit e1df06ec33
3 changed files with 192 additions and 180 deletions

View File

@@ -9,7 +9,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="HtmlAgilityPack" Version="1.8.2" />
<PackageReference Include="AngleSharp" Version="0.9.9.2" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" />
</ItemGroup>

View File

@@ -1,5 +1,4 @@
using System;
using HtmlAgilityPack;
namespace Bit.Icons.Models
{

View File

@@ -6,35 +6,45 @@ using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Bit.Icons.Models;
using HtmlAgilityPack;
using AngleSharp.Parser.Html;
namespace Bit.Icons.Services
{
public class IconFetchingService : IIconFetchingService
{
private static HashSet<string> _iconRels = new HashSet<string> { "icon", "apple-touch-icon", "shortcut icon" };
private static HashSet<string> _iconExtensions = new HashSet<string> { ".ico", ".png", ".jpg", ".jpeg" };
private static readonly HttpClient _httpClient = new HttpClient(new HttpClientHandler
private readonly HashSet<string> _iconRels =
new HashSet<string> { "icon", "apple-touch-icon", "shortcut icon" };
private readonly HashSet<string> _iconExtensions =
new HashSet<string> { ".ico", ".png", ".jpg", ".jpeg" };
private readonly string _pngMediaType = "image/png";
private readonly byte[] _pngHeader = new byte[] { 137, 80, 78, 71 };
private readonly string _icoMediaType = "image/x-icon";
private readonly string _icoAltMediaType = "image/vnd.microsoft.icon";
private readonly byte[] _icoHeader = new byte[] { 00, 00, 01, 00 };
private readonly string _jpegMediaType = "image/jpeg";
private readonly byte[] _jpegHeader = new byte[] { 255, 216, 255 };
private readonly HashSet<string> _allowedMediaTypes;
private readonly HttpClient _httpClient;
public IconFetchingService()
{
_allowedMediaTypes = new HashSet<string>
{
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 readonly HashSet<string> _allowedMediaTypes = new HashSet<string>{
_pngMediaType,
_icoMediaType,
_icoAltMediaType,
_jpegMediaType
};
public IconFetchingService()
_httpClient = new HttpClient(new HttpClientHandler
{
AllowAutoRedirect = false,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
});
_httpClient.Timeout = TimeSpan.FromSeconds(20);
}
@@ -44,10 +54,12 @@ namespace Bit.Icons.Services
var response = await GetAndFollowAsync(uri, 2);
if(response == null || !response.IsSuccessStatusCode)
{
Cleanup(response);
uri = new Uri($"http://{domain}");
response = await GetAndFollowAsync(uri, 2);
if(response == null || !response.IsSuccessStatusCode)
{
Cleanup(response);
uri = new Uri($"https://www.{domain}");
response = await GetAndFollowAsync(uri, 2);
}
@@ -55,38 +67,23 @@ namespace Bit.Icons.Services
if(response?.Content == null || !response.IsSuccessStatusCode)
{
Cleanup(response);
return null;
}
uri = response.RequestMessage.RequestUri;
var doc = new HtmlDocument();
var parser = new HtmlParser();
using(response)
using(var htmlStream = await response.Content.ReadAsStreamAsync())
using(var document = await parser.ParseAsync(htmlStream))
{
if(htmlStream == null)
uri = response.RequestMessage.RequestUri;
if(document.DocumentElement == null)
{
doc = null;
return null;
}
try
{
doc.Load(htmlStream);
if(doc.DocumentNode == null)
{
doc = null;
return null;
}
}
catch
{
doc = null;
return null;
}
}
var baseUrl = "/";
var baseUrlNode = doc.DocumentNode.SelectSingleNode(@"//head/base[@href]");
var baseUrlNode = document.QuerySelector("head base[href]");
if(baseUrlNode != null)
{
var hrefAttr = baseUrlNode.Attributes["href"];
@@ -100,8 +97,7 @@ namespace Bit.Icons.Services
}
var icons = new List<IconResult>();
var links = doc.DocumentNode.SelectNodes(@"//head/link[@href]");
doc = null;
var links = document.QuerySelectorAll("head link[href]");
if(links != null)
{
foreach(var link in links.Take(40))
@@ -188,17 +184,21 @@ namespace Bit.Icons.Services
return icons.Where(i => i.Icon != null).OrderBy(i => i.Priority).First();
}
}
private async Task<IconResult> GetIconAsync(Uri uri)
{
var response = await GetAndFollowAsync(uri, 2);
using(var response = await GetAndFollowAsync(uri, 2))
{
if(response?.Content?.Headers == null || !response.IsSuccessStatusCode)
{
response?.Content?.Dispose();
return null;
}
var format = response.Content.Headers?.ContentType?.MediaType;
var bytes = await response.Content.ReadAsByteArrayAsync();
response.Content.Dispose();
if(format == null || !_allowedMediaTypes.Contains(format))
{
if(HeaderMatch(bytes, _icoHeader))
@@ -221,6 +221,7 @@ namespace Bit.Icons.Services
return new IconResult(uri, bytes, format);
}
}
private async Task<HttpResponseMessage> GetAndFollowAsync(Uri uri, int maxRedirectCount)
{
@@ -234,11 +235,10 @@ namespace Bit.Icons.Services
private async Task<HttpResponseMessage> GetAsync(Uri uri)
{
var message = new HttpRequestMessage
using(var message = new HttpRequestMessage())
{
RequestUri = uri,
Method = HttpMethod.Get
};
message.RequestUri = uri;
message.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.
@@ -259,6 +259,7 @@ namespace Bit.Icons.Services
return null;
}
}
}
private async Task<HttpResponseMessage> FollowRedirectsAsync(HttpResponseMessage response,
int maxFollowCount, int followCount = 0)
@@ -272,14 +273,15 @@ namespace Bit.Icons.Services
response.StatusCode == HttpStatusCode.MovedPermanently ||
response.StatusCode == HttpStatusCode.RedirectKeepVerb ||
response.StatusCode == HttpStatusCode.SeeOther) ||
!response.Headers.Contains("Location"))
response.Headers.Location == null)
{
Cleanup(response);
return null;
}
var locationHeader = response.Headers.GetValues("Location").FirstOrDefault();
if(!string.IsNullOrWhiteSpace(locationHeader))
if(response.Headers.Location != null)
{
var locationHeader = response.Headers.Location.ToString();
if(!Uri.TryCreate(locationHeader, UriKind.Absolute, out Uri location))
{
if(Uri.TryCreate(locationHeader, UriKind.Relative, out Uri relLocation))
@@ -293,12 +295,17 @@ namespace Bit.Icons.Services
}
}
Cleanup(response);
var newResponse = await GetAsync(location);
if(newResponse != null)
{
var redirectedResponse = await FollowRedirectsAsync(newResponse, maxFollowCount, followCount++);
if(redirectedResponse != null)
{
if(redirectedResponse != newResponse)
{
Cleanup(newResponse);
}
return redirectedResponse;
}
}
@@ -324,5 +331,11 @@ namespace Bit.Icons.Services
}
return new Uri(url);
}
private void Cleanup(IDisposable obj)
{
obj?.Dispose();
obj = null;
}
}
}