// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.Net.Http; using System.Text; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using System.Web; #pragma warning disable CA2234 // Pass system uri objects instead of strings #pragma warning disable CA1054 // URI-like parameters should not be strings namespace EpicGames.Core { /// /// Extension methods for consuming REST APIs via JSON objects /// public static class HttpClientExtensions { /// /// Gets a resource from an HTTP endpoint and parses it as a JSON object /// /// The object type to return /// The http client instance /// The url to retrieve /// Cancels the request /// New instance of the object public static async Task GetAsync(this HttpClient client, string url, CancellationToken cancellationToken) { using (HttpResponseMessage response = await client.GetAsync(url, cancellationToken)) { response.EnsureSuccessStatusCode(); return await ParseJsonContentAsync(response, cancellationToken); } } /// public static async Task GetAsync(this HttpClient client, Uri url, CancellationToken cancellationToken) { using (HttpResponseMessage response = await client.GetAsync(url, cancellationToken)) { response.EnsureSuccessStatusCode(); return await ParseJsonContentAsync(response, cancellationToken); } } /// /// Posts an object to an HTTP endpoint as a JSON object /// /// The object type to post /// The http client instance /// The url to post to /// The object to post /// Cancels the request /// Response message public static async Task PostAsync(this HttpClient client, string url, TRequest request, CancellationToken cancellationToken) { using HttpContent content = ToJsonContent(request); return await client.PostAsync(url, content, cancellationToken); } /// public static async Task PostAsync(this HttpClient client, Uri url, TRequest request, CancellationToken cancellationToken) { using HttpContent content = ToJsonContent(request); return await client.PostAsync(url, content, cancellationToken); } /// /// Posts an object to an HTTP endpoint as a JSON object, and parses the response object /// /// The object type to return /// The object type to post /// The http client instance /// The url to post to /// The object to post /// Cancels the request /// The response parsed into the requested type public static async Task PostAsync(this HttpClient client, string url, TRequest request, CancellationToken cancellationToken) { using (HttpResponseMessage response = await PostAsync(client, url, request, cancellationToken)) { response.EnsureSuccessStatusCode(); return await ParseJsonContentAsync(response, cancellationToken); } } /// public static async Task PostAsync(this HttpClient client, Uri url, TRequest request, CancellationToken cancellationToken) { using (HttpResponseMessage response = await PostAsync(client, url, request, cancellationToken)) { response.EnsureSuccessStatusCode(); return await ParseJsonContentAsync(response, cancellationToken); } } /// /// Puts an object to an HTTP endpoint as a JSON object /// /// The object type to post /// The http client instance /// The url to post to /// The object to post /// Cancels the request /// Response message public static async Task PutAsync(this HttpClient client, string url, TRequest request, CancellationToken cancellationToken) { using HttpContent content = ToJsonContent(request); return await client.PutAsync(url, content, cancellationToken); } /// public static async Task PutAsync(this HttpClient client, Uri url, TRequest request, CancellationToken cancellationToken) { using HttpContent content = ToJsonContent(request); return await client.PutAsync(url, content, cancellationToken); } /// /// Puts an object to an HTTP endpoint as a JSON object, and parses the response object /// /// The object type to return /// The object type to post /// The http client instance /// The url to post to /// The object to post /// Cancels the request /// The response parsed into the requested type public static async Task PutAsync(this HttpClient client, string url, TRequest request, CancellationToken cancellationToken) { using (HttpResponseMessage response = await PutAsync(client, url, request, cancellationToken)) { response.EnsureSuccessStatusCode(); return await ParseJsonContentAsync(response, cancellationToken); } } /// public static async Task PutAsync(this HttpClient client, Uri url, TRequest request, CancellationToken cancellationToken) { using (HttpResponseMessage response = await PutAsync(client, url, request, cancellationToken)) { response.EnsureSuccessStatusCode(); return await ParseJsonContentAsync(response, cancellationToken); } } /// /// Converts an object to a JSON http content object /// /// Type of the object to parse /// The object instance /// Http content object private static HttpContent ToJsonContent(T obj) { return new StringContent(JsonSerializer.Serialize(obj), Encoding.UTF8, "application/json"); } /// /// Parses a HTTP response as a JSON object /// /// Type of the object to parse /// The message received /// Cancellation token for the operation /// Parsed object instance private static async Task ParseJsonContentAsync(HttpResponseMessage message, CancellationToken cancellationToken) { byte[] bytes = await message.Content.ReadAsByteArrayAsync(cancellationToken); return JsonSerializer.Deserialize(bytes, new JsonSerializerOptions { PropertyNameCaseInsensitive = true })!; } } /// /// Extension methods for HttpRequestMessage /// public static class HttpRequestMessageExtensions { private static readonly HashSet s_sensitiveKeys = new(StringComparer.OrdinalIgnoreCase) { "x-amz-security-token", "Signature", "AWSAccessKeyId" }; /// /// Returns a new URI with sensitive query parameters redacted from the request URI. /// public static Uri? RedactedRequestUri(this HttpRequestMessage request) { Uri? uri = request?.RequestUri; if (uri?.Query is not { Length: > 0 }) { return uri; } NameValueCollection queryParams = HttpUtility.ParseQueryString(uri.Query); foreach (string key in queryParams.AllKeys.OfType()) { string value = s_sensitiveKeys.Contains(key) ? "redacted" : queryParams[key] ?? String.Empty; queryParams[key] = value; } return new UriBuilder(uri) { Query = String.Join("&", queryParams) }.Uri; } } }