[backend/federation] Improve WebFinger host-meta handling

This commit is contained in:
Laura Hausmann 2024-08-14 01:05:38 +02:00
parent b69f92dbdc
commit c86b2e192a
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
3 changed files with 20 additions and 19 deletions

View file

@ -16,7 +16,7 @@ public class HostMetaXmlResponseLink()
{ {
[XmlAttribute("rel")] public string Rel = "lrdd"; [XmlAttribute("rel")] public string Rel = "lrdd";
[XmlAttribute("template")] public required string Template; [XmlAttribute("template")] public required string Template;
[XmlAttribute("type")] public string Type = "application/xrd+xml"; [XmlAttribute("type")] public string Type = "application/jrd+json";
[SetsRequiredMembers] [SetsRequiredMembers]
public HostMetaXmlResponseLink(string webDomain) : this() => public HostMetaXmlResponseLink(string webDomain) : this() =>

View file

@ -24,7 +24,7 @@ namespace Iceshrimp.Backend.Controllers.Federation;
public class WellKnownController(IOptions<Config.InstanceSection> config, DatabaseContext db) : ControllerBase public class WellKnownController(IOptions<Config.InstanceSection> config, DatabaseContext db) : ControllerBase
{ {
[HttpGet("webfinger")] [HttpGet("webfinger")]
[Produces(MediaTypeNames.Application.Json)] [Produces(MediaTypeNames.Application.Json, "application/jrd+json")]
[ProducesResults(HttpStatusCode.OK)] [ProducesResults(HttpStatusCode.OK)]
[ProducesErrors(HttpStatusCode.NotFound)] [ProducesErrors(HttpStatusCode.NotFound)]
public async Task<WebFingerResponse> WebFinger([FromQuery] string resource) public async Task<WebFingerResponse> WebFinger([FromQuery] string resource)

View file

@ -1,6 +1,7 @@
using System.Collections.Immutable;
using System.Net; using System.Net;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Xml; using System.Xml.Linq;
using Iceshrimp.Backend.Controllers.Federation.Schemas; using Iceshrimp.Backend.Controllers.Federation.Schemas;
using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Middleware; using Iceshrimp.Backend.Core.Middleware;
@ -28,6 +29,11 @@ public class WebFingerService(
IOptions<Config.InstanceSection> config IOptions<Config.InstanceSection> config
) )
{ {
private static readonly ImmutableArray<string> Accept =
[
"application/jrd+json", "application/json", "application/xrd+xml", "application/xml"
];
public async Task<WebFingerResponse?> ResolveAsync(string query) public async Task<WebFingerResponse?> ResolveAsync(string query)
{ {
(query, var proto, var domain) = ParseQuery(query); (query, var proto, var domain) = ParseQuery(query);
@ -39,7 +45,7 @@ public class WebFingerService(
using var cts = CancellationTokenSource.CreateLinkedTokenSource(appLifetime.ApplicationStopping); using var cts = CancellationTokenSource.CreateLinkedTokenSource(appLifetime.ApplicationStopping);
cts.CancelAfter(TimeSpan.FromSeconds(10)); cts.CancelAfter(TimeSpan.FromSeconds(10));
var req = httpRqSvc.Get(webFingerUrl, ["application/jrd+json", "application/json"]); var req = httpRqSvc.Get(webFingerUrl, Accept);
var res = await client.SendAsync(req, cts.Token); var res = await client.SendAsync(req, cts.Token);
if (res.StatusCode == HttpStatusCode.Gone) if (res.StatusCode == HttpStatusCode.Gone)
@ -96,6 +102,7 @@ public class WebFingerService(
return template.Replace("{uri}", encoded); return template.Replace("{uri}", encoded);
} }
// Technically, we should be checking for rel=lrdd *and* type=application/jrd+json, but nearly all implementations break this, so we can't.
private async Task<string?> GetWebFingerTemplateFromHostMetaXmlAsync(string proto, string domain) private async Task<string?> GetWebFingerTemplateFromHostMetaXmlAsync(string proto, string domain)
{ {
try try
@ -105,19 +112,12 @@ public class WebFingerService(
HttpCompletionOption.ResponseHeadersRead); HttpCompletionOption.ResponseHeadersRead);
await using var stream = await res.Content.ReadAsStreamAsync(); await using var stream = await res.Content.ReadAsStreamAsync();
var xml = new XmlDocument(); return XElement.Load(stream)
xml.Load(stream); .Descendants(XName.Get("Link", "http://docs.oasis-open.org/ns/xri/xrd-1.0"))
//.Where(p => Accept.Contains(p.Attribute("type"))?.Value ?? ""))
var section = xml["XRD"]?.GetElementsByTagName("Link"); .FirstOrDefault(p => p.Attribute("rel")?.Value == "lrdd")
if (section == null) return null; ?.Attribute("template")
?.Value;
//TODO: implement https://stackoverflow.com/a/37322614/18402176 instead
for (var i = 0; i < section.Count; i++)
if (section[i]?.Attributes?["rel"]?.InnerText == "lrdd")
return section[i]?.Attributes?["template"]?.InnerText;
return null;
} }
catch catch
{ {
@ -125,6 +125,7 @@ public class WebFingerService(
} }
} }
// See above comment as for why jrd+json is commented out.
private async Task<string?> GetWebFingerTemplateFromHostMetaJsonAsync(string proto, string domain) private async Task<string?> GetWebFingerTemplateFromHostMetaJsonAsync(string proto, string domain)
{ {
try try
@ -137,7 +138,7 @@ public class WebFingerService(
var result = deserialized?.Links?.FirstOrDefault(p => p is var result = deserialized?.Links?.FirstOrDefault(p => p is
{ {
Rel: "lrdd", Rel: "lrdd",
Type: "application/jrd+json", //Type: "application/jrd+json",
Template: not null Template: not null
}); });
@ -159,7 +160,7 @@ public class WebFingerService(
var result = deserialized?.Links?.FirstOrDefault(p => p is var result = deserialized?.Links?.FirstOrDefault(p => p is
{ {
Rel: "lrdd", Rel: "lrdd",
Type: "application/jrd+json", //Type: "application/jrd+json",
Template: not null Template: not null
}); });