1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-28 14:46:14 -05:00
bitwarden/src/Icons/Services/UriService.cs
Matt Gibson 4377c7a897
Rewrite Icon fetching (#3023)
* Rewrite Icon fetching

* Move validation to IconUri, Uri, or UriBuilder

* `dotnet format` 🤖

* PR suggestions

* Add not null compiler hint

* Add twitter to test case

* Move Uri manipulation to UriService

* Implement MockedHttpClient

Presents better, fluent handling of message matching and response
building.

* Add redirect handling tests

* Add testing to models

* More aggressively dispose content in icon link

* Format 🤖

* Update icon lockfile

* Convert to cloned stream for HttpResponseBuilder

Content was being disposed when HttResponseMessage was being disposed.
This avoids losing our reference to our content and allows multiple
usages of the same `MockedHttpMessageResponse`

* Move services to extension

Extension is shared by testing and allows access to services from
our service tests

* Remove unused `using`

* Prefer awaiting asyncs for better exception handling

* `dotnet format` 🤖

* Await async

* Update tests to use test TLD and ip ranges

* Remove unused interfaces

* Make assignments static when possible

* Prefer invariant comparer to downcasing

* Prefer injecting interface services to implementations

* Prefer comparer set in HashSet initialization

* Allow SVG icons

* Filter out icons with unknown formats

* Seek to beginning of MemoryStream after writing it

* More appropriate to not return icon if it's invalid

* Add svg icon test
2023-08-08 19:29:40 +00:00

110 lines
2.7 KiB
C#

#nullable enable
using System.Net;
using System.Net.Sockets;
using Bit.Icons.Extensions;
using Bit.Icons.Models;
namespace Bit.Icons.Services;
public class UriService : IUriService
{
public IconUri GetUri(string inputUri)
{
var uri = new Uri(inputUri);
return new IconUri(uri, DetermineIp(uri));
}
public bool TryGetUri(string stringUri, out IconUri? iconUri)
{
if (!Uri.TryCreate(stringUri, UriKind.Absolute, out var uri))
{
iconUri = null;
return false;
}
return TryGetUri(uri, out iconUri);
}
public IconUri GetUri(Uri uri)
{
return new IconUri(uri, DetermineIp(uri));
}
public bool TryGetUri(Uri uri, out IconUri? iconUri)
{
try
{
iconUri = GetUri(uri);
return true;
}
catch (Exception)
{
iconUri = null;
return false;
}
}
public IconUri GetRedirect(HttpResponseMessage response, IconUri originalUri)
{
if (response.Headers.Location == null)
{
throw new Exception("No redirect location found.");
}
var redirectUri = DetermineRedirectUri(response.Headers.Location, originalUri);
return new IconUri(redirectUri, DetermineIp(redirectUri));
}
public bool TryGetRedirect(HttpResponseMessage response, IconUri originalUri, out IconUri? iconUri)
{
try
{
iconUri = GetRedirect(response, originalUri);
return true;
}
catch (Exception)
{
iconUri = null;
return false;
}
}
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;
}
}
private static IPAddress DetermineIp(Uri uri)
{
if (IPAddress.TryParse(uri.Host, out var ip))
{
return ip;
}
var hostEntry = Dns.GetHostEntry(uri.Host);
ip = hostEntry.AddressList.FirstOrDefault(ip => ip.AddressFamily == AddressFamily.InterNetwork || ip.IsIPv4MappedToIPv6)?.MapToIPv4();
if (ip == null)
{
throw new Exception($"Unable to determine IP for {uri.Host}");
}
return ip;
}
}