Iceshrimp.NET/Iceshrimp.Backend/Core/Services/HttpRequestService.cs
2024-11-18 19:02:44 +01:00

97 lines
No EOL
2.8 KiB
C#

using System.Net;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Federation.Cryptography;
using Microsoft.Extensions.Options;
namespace Iceshrimp.Backend.Core.Services;
public class HttpRequestService(IOptions<Config.InstanceSection> options) : ISingletonService
{
private static HttpRequestMessage GenerateRequest(
string url, HttpMethod method,
string? body = null,
string? contentType = null,
IEnumerable<string>? accept = null
)
{
var message = new HttpRequestMessage
{
RequestUri = new Uri(url),
Method = method,
// Default to HTTP/2, but allow for down-negotiation to HTTP/1.1 or HTTP/1.0
Version = HttpVersion.Version20,
VersionPolicy = HttpVersionPolicy.RequestVersionOrLower
};
if (body != null)
{
ArgumentNullException.ThrowIfNull(contentType);
message.Content = new StringContent(body, MediaTypeHeaderValue.Parse(contentType));
}
if (accept != null)
foreach (var type in accept.Select(MediaTypeWithQualityHeaderValue.Parse))
message.Headers.Accept.Add(type);
return message;
}
public HttpRequestMessage Get(string url, IEnumerable<string>? accept)
{
return GenerateRequest(url, HttpMethod.Get, accept: accept);
}
public HttpRequestMessage Post(string url, string body, string contentType)
{
return GenerateRequest(url, HttpMethod.Post, body, contentType);
}
public HttpRequestMessage GetSigned(
string url, IEnumerable<string>? accept, string actorId,
string privateKey
)
{
return Get(url, accept)
.Sign(["(request-target)", "date", "host", "accept"], privateKey,
$"https://{options.Value.WebDomain}/users/{actorId}#main-key");
}
public HttpRequestMessage GetSigned(
string url, IEnumerable<string>? accept, User actor,
UserKeypair keypair
)
{
return GetSigned(url, accept, actor.Id, keypair.PrivateKey);
}
public async Task<HttpRequestMessage> PostSignedAsync(
string url, string body, string contentType, string actorId,
string privateKey
)
{
var message = Post(url, body, contentType);
ArgumentNullException.ThrowIfNull(message.Content);
// Generate and attach digest header
var content = await message.Content.ReadAsStreamAsync();
var digest = await SHA256.HashDataAsync(content);
message.Headers.Add("Digest", "SHA-256=" + Convert.ToBase64String(digest));
// Return the signed message
return message.Sign(["(request-target)", "date", "host", "digest"], privateKey,
$"https://{options.Value.WebDomain}/users/{actorId}#main-key");
}
public Task<HttpRequestMessage> PostSignedAsync(
string url, string body, string contentType, User actor,
UserKeypair keypair
)
{
return PostSignedAsync(url, body, contentType, actor.Id, keypair.PrivateKey);
}
}