mirror of
https://github.com/bitwarden/server.git
synced 2025-06-28 14:46:14 -05:00

* 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
110 lines
2.7 KiB
C#
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;
|
|
}
|
|
}
|