using System.Net.Http.Headers; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; namespace Foxchat.Core.Federation; public partial class RequestSigningService { public const string USER_AGENT_HEADER = "User-Agent"; public const string USER_AGENT = "Foxchat.NET"; public const string DATE_HEADER = "Date"; public const string CONTENT_LENGTH_HEADER = "Content-Length"; public const string CONTENT_TYPE_HEADER = "Content-Type"; public const string CONTENT_TYPE = "application/json; charset=utf-8"; public const string SERVER_HEADER = "X-Foxchat-Server"; public const string SIGNATURE_HEADER = "X-Foxchat-Signature"; public const string USER_HEADER = "X-Foxchat-User"; private static readonly JsonSerializerSettings _jsonSerializerSettings = new() { ContractResolver = new DefaultContractResolver { NamingStrategy = new SnakeCaseNamingStrategy() } }; public async Task RequestAsync(HttpMethod method, string domain, string requestPath, string? userId = null, object? body = null) { var request = BuildHttpRequest(method, domain, requestPath, userId, body); _logger.Debug("Content length in header: '{ContentLength}'", request.Headers.Where(c => c.Key == "Content-Length")); var resp = await _httpClient.SendAsync(request); if (!resp.IsSuccessStatusCode) { var error = await resp.Content.ReadAsStringAsync(); _logger.Error("Received {Status}, body: {Error}", resp.StatusCode, error); // TODO: replace this with specific exception type throw new Exception("oh no a request error"); } var bodyString = await resp.Content.ReadAsStringAsync(); // TODO: replace this with specific exception type return DeserializeObject(bodyString) ?? throw new Exception("oh no invalid json"); } private HttpRequestMessage BuildHttpRequest(HttpMethod method, string domain, string requestPath, string? userId = null, object? bodyData = null) { var body = bodyData != null ? SerializeObject(bodyData) : null; var now = _clock.GetCurrentInstant(); var url = $"https://{domain}{requestPath}"; var signature = GenerateSignature(new SignatureData(now, domain, requestPath, body?.Length, userId)); var request = new HttpRequestMessage(method, url); request.Headers.Clear(); request.Headers.Add(USER_AGENT_HEADER, USER_AGENT); request.Headers.Add(DATE_HEADER, FormatTime(now)); request.Headers.Add(SERVER_HEADER, _config.Domain); request.Headers.Add(SIGNATURE_HEADER, signature); if (userId != null) request.Headers.Add(USER_HEADER, userId); if (body != null) { request.Content = new StringContent(body, new MediaTypeHeaderValue("application/json", "utf-8")); } return request; } public static string SerializeObject(object data) => JsonConvert.SerializeObject(data, _jsonSerializerSettings); public static T? DeserializeObject(string data) => JsonConvert.DeserializeObject(data, _jsonSerializerSettings); }