From 0833cf49d2f0839f977542e09c32f2b9746ae225 Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Tue, 12 Mar 2024 05:34:45 +0100 Subject: [PATCH] [backend/federation] Fall back to JRD during host-meta step of the WebFinger process (ISH-162) --- .../Schemas/HostMetaJsonResponse.cs | 24 ++++++-- .../Federation/WebFinger/WebFingerService.cs | 56 ++++++++++++++++++- 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/Iceshrimp.Backend/Controllers/Federation/Schemas/HostMetaJsonResponse.cs b/Iceshrimp.Backend/Controllers/Federation/Schemas/HostMetaJsonResponse.cs index f0567e8a..d376fd76 100644 --- a/Iceshrimp.Backend/Controllers/Federation/Schemas/HostMetaJsonResponse.cs +++ b/Iceshrimp.Backend/Controllers/Federation/Schemas/HostMetaJsonResponse.cs @@ -2,14 +2,26 @@ using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; namespace Iceshrimp.Backend.Controllers.Federation.Schemas; -public class HostMetaJsonResponse(string webDomain) +public class HostMetaJsonResponse() { - [J("links")] public List Links => [new HostMetaJsonResponseLink(webDomain)]; + public HostMetaJsonResponse(string webDomain) : this() + { + Links = [new HostMetaJsonResponseLink(webDomain)]; + } + + [J("links")] public List? Links { get; set; } } -public class HostMetaJsonResponseLink(string webDomain) +public class HostMetaJsonResponseLink() { - [J("rel")] public string Rel => "lrdd"; - [J("type")] public string Type => "application/jrd+json"; - [J("template")] public string Template => $"https://{webDomain}/.well-known/webfinger?resource={{uri}}"; + public HostMetaJsonResponseLink(string webDomain) : this() + { + Rel = "lrdd"; + Type = "application/jrd+json"; + Template = $"https://{webDomain}/.well-known/webfinger?resource={{uri}}"; + } + + [J("rel")] public string? Rel { get; set; } + [J("type")] public string? Type { get; set; } + [J("template")] public string? Template { get; set; } } \ No newline at end of file diff --git a/Iceshrimp.Backend/Core/Federation/WebFinger/WebFingerService.cs b/Iceshrimp.Backend/Core/Federation/WebFinger/WebFingerService.cs index a05d4ac6..c3420f6f 100644 --- a/Iceshrimp.Backend/Core/Federation/WebFinger/WebFingerService.cs +++ b/Iceshrimp.Backend/Core/Federation/WebFinger/WebFingerService.cs @@ -1,6 +1,7 @@ using System.Net; using System.Text.Encodings.Web; using System.Xml; +using Iceshrimp.Backend.Controllers.Federation.Schemas; using Iceshrimp.Backend.Core.Middleware; using Iceshrimp.Backend.Core.Services; @@ -75,17 +76,20 @@ public class WebFingerService(HttpClient client, HttpRequestService httpRqSvc, I private async Task GetWebFingerUrlAsync(string query, string proto, string domain) { - var template = await GetWebFingerTemplateFromHostMetaAsync($"{proto}://{domain}/.well-known/host-meta") ?? + var template = await GetWebFingerTemplateFromHostMetaXmlAsync(proto, domain) ?? + await GetWebFingerTemplateFromHostMetaJsonAsync(proto, domain) ?? $"{proto}://{domain}/.well-known/webfinger?resource={{uri}}"; + var finalQuery = query.StartsWith('@') ? $"acct:{query[1..]}" : query; var encoded = UrlEncoder.Default.Encode(finalQuery); return template.Replace("{uri}", encoded); } - private async Task GetWebFingerTemplateFromHostMetaAsync(string hostMetaUrl) + private async Task GetWebFingerTemplateFromHostMetaXmlAsync(string proto, string domain) { try { + var hostMetaUrl = $"{proto}://{domain}/.well-known/host-meta"; using var res = await client.SendAsync(httpRqSvc.Get(hostMetaUrl, ["application/xrd+xml"]), HttpCompletionOption.ResponseHeadersRead); using var stream = await res.Content.ReadAsStreamAsync(); @@ -109,4 +113,52 @@ public class WebFingerService(HttpClient client, HttpRequestService httpRqSvc, I return null; } } + + private async Task GetWebFingerTemplateFromHostMetaJsonAsync(string proto, string domain) + { + try + { + var hostMetaUrl = $"{proto}://{domain}/.well-known/host-meta.json"; + using var res = await client.SendAsync(httpRqSvc.Get(hostMetaUrl, ["application/jrd+json"]), + HttpCompletionOption.ResponseHeadersRead); + var deserialized = await res.Content.ReadFromJsonAsync(); + + var result = deserialized?.Links?.FirstOrDefault(p => p is + { + Rel: "lrdd", + Type: "application/jrd+json", + Template: not null + }); + + if (result?.Template != null) + return result.Template; + } + catch + { + // ignored + } + + try + { + var hostMetaUrl = $"{proto}://{domain}/.well-known/host-meta"; + using var res = await client.SendAsync(httpRqSvc.Get(hostMetaUrl, ["application/jrd+json"]), + HttpCompletionOption.ResponseHeadersRead); + var deserialized = await res.Content.ReadFromJsonAsync(); + + var result = deserialized?.Links?.FirstOrDefault(p => p is + { + Rel: "lrdd", + Type: "application/jrd+json", + Template: not null + }); + + return result?.Template; + } + catch + { + // ignored + } + + return null; + } } \ No newline at end of file