// 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;
}
}
}