#nullable enable using System.Net; using Bit.Icons.Extensions; using Bit.Icons.Services; namespace Bit.Icons.Models; public class IconHttpRequest { private const int _maxRedirects = 2; private static readonly HttpStatusCode[] _redirectStatusCodes = new HttpStatusCode[] { HttpStatusCode.Redirect, HttpStatusCode.MovedPermanently, HttpStatusCode.RedirectKeepVerb, HttpStatusCode.SeeOther }; private readonly ILogger _logger; private readonly HttpClient _httpClient; private readonly IHttpClientFactory _httpClientFactory; private readonly IUriService _uriService; private readonly int _redirectsCount; private readonly Uri _uri; private static HttpResponseMessage NotFound => new(HttpStatusCode.NotFound); private IconHttpRequest(Uri uri, ILogger logger, IHttpClientFactory httpClientFactory, IUriService uriService, int redirectsCount) { _logger = logger; _httpClientFactory = httpClientFactory; _httpClient = _httpClientFactory.CreateClient("Icons"); _uriService = uriService; _redirectsCount = redirectsCount; _uri = uri; } public static async Task FetchAsync(Uri uri, ILogger logger, IHttpClientFactory httpClientFactory, IUriService uriService) { var pageIcons = new IconHttpRequest(uri, logger, httpClientFactory, uriService, 0); var httpResponse = await pageIcons.FetchAsync(); return new IconHttpResponse(httpResponse, logger, httpClientFactory, uriService); } private async Task FetchAsync() { if (!_uriService.TryGetUri(_uri, out var iconUri) || !iconUri!.IsValid) { return NotFound; } var response = await GetAsync(iconUri); if (response.IsSuccessStatusCode) { return response; } using var responseForRedirect = response; return await FollowRedirectsAsync(responseForRedirect, iconUri); } private async Task GetAsync(IconUri iconUri) { using var message = new HttpRequestMessage(); message.RequestUri = iconUri.InnerUri; message.Headers.Host = iconUri.Host; message.Method = HttpMethod.Get; try { return await _httpClient.SendAsync(message); } catch { return NotFound; } } private async Task FollowRedirectsAsync(HttpResponseMessage response, IconUri originalIconUri) { if (_redirectsCount >= _maxRedirects || response.Headers.Location == null || !_redirectStatusCodes.Contains(response.StatusCode)) { return NotFound; } using var responseForRedirect = response; var redirectUri = DetermineRedirectUri(responseForRedirect.Headers.Location, originalIconUri); return await new IconHttpRequest(redirectUri, _logger, _httpClientFactory, _uriService, _redirectsCount + 1).FetchAsync(); } 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; } } }