From e39b20707bfffd6487329723d51a9f83750c7dbd Mon Sep 17 00:00:00 2001 From: Katherine Marsee Date: Fri, 19 Jan 2024 22:41:51 -0500 Subject: [PATCH] Add Profile field support to HTTP ContentType (resolves #127) --- .../ActivityPub.Client/ActivityPubClient.cs | 24 +++++++++++++---- .../Util/ActivityPubOptions.cs | 27 ++++++++++++------- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/Source/ActivityPub.Client/ActivityPubClient.cs b/Source/ActivityPub.Client/ActivityPubClient.cs index 1b473ce..896e69f 100644 --- a/Source/ActivityPub.Client/ActivityPubClient.cs +++ b/Source/ActivityPub.Client/ActivityPubClient.cs @@ -33,7 +33,10 @@ public ActivityPubClient(ActivityPubOptions apOptions, IJsonLdSerializer jsonLdS // https://stackoverflow.com/questions/47176104/c-sharp-add-accept-header-to-httpclient foreach (var mimeType in apOptions.RequestContentTypes) { - var mediaType = new MediaTypeWithQualityHeaderValue(mimeType); + var mediaType = new MediaTypeWithQualityHeaderValue(mimeType.MediaType); + if (mimeType.Profile != "") { + mediaType.Parameters.Add(new NameValueHeaderValue("profile", mimeType.Profile)); + } _httpClient.DefaultRequestHeaders.Accept.Add(mediaType); } } @@ -83,9 +86,9 @@ private async Task Get(Uri uri, Type targetType, int? maxRecursion, Canc if (!resp.IsSuccessStatusCode) throw new ApplicationException($"Request failed: got status {resp.StatusCode}"); - var mediaType = resp.Content.Headers.ContentType?.MediaType; - if (mediaType == null || !_apOptions.ResponseContentTypes.Contains(mediaType)) - throw new ApplicationException($"Request failed: unsupported content type {mediaType}"); + var contentType = resp.Content.Headers.ContentType; + if (contentType == null || !_apOptions.ResponseContentTypes.Any(expectedContentType => IsContentTypeMatch(contentType, expectedContentType))) + throw new ApplicationException($"Request failed: unsupported content type {contentType?.MediaType}"); var json = await resp.Content.ReadAsStringAsync(cancellationToken); var jsonObj = _jsonLdSerializer.Deserialize(json, targetType); @@ -100,6 +103,17 @@ private async Task Get(Uri uri, Type targetType, int? maxRecursion, Canc return obj; } + private static bool IsContentTypeMatch(MediaTypeHeaderValue actual, ActivityPubOptions.ContentType expected) { + if (!expected.MediaType.Equals(actual.MediaType)) { + return false; + } + + var actualProfile = actual.Parameters.FirstOrDefault(p => p.Name.Equals("profile"))?.Value ?? ""; + + return string.Equals(actualProfile, expected.Profile, + StringComparison.OrdinalIgnoreCase); + } + #region Dispose @@ -130,4 +144,4 @@ protected virtual void Dispose(bool disposing) private bool _disposed; #endregion -} \ No newline at end of file +} diff --git a/Source/ActivityPub.Common/Util/ActivityPubOptions.cs b/Source/ActivityPub.Common/Util/ActivityPubOptions.cs index 8a05a61..aee2f12 100644 --- a/Source/ActivityPub.Common/Util/ActivityPubOptions.cs +++ b/Source/ActivityPub.Common/Util/ActivityPubOptions.cs @@ -13,11 +13,12 @@ public class ActivityPubOptions /// This maps to the Content-Type header. /// /// - public HashSet ResponseContentTypes { get; set; } = + public HashSet ResponseContentTypes { get; set; } = [ - "application/activity+json", - "application/ld+json", - "application/json" + new ContentType("application/ld+json", "https://www.w3.org/ns/activitystreams"), + new ContentType("application/activity+json", ""), + new ContentType("application/ld+json", ""), + new ContentType("application/json", "") ]; /// @@ -25,10 +26,18 @@ public class ActivityPubOptions /// This maps to the Accept header. /// /// - public List RequestContentTypes { get; set; } = + public List RequestContentTypes { get; set; } = [ - "application/activity+json", - "application/ld+json", - "application/json" + new ContentType("application/ld+json", "https://www.w3.org/ns/activitystreams"), + new ContentType("application/activity+json", ""), + new ContentType("application/ld+json", ""), + new ContentType("application/json", "") ]; -} \ No newline at end of file + + /// + /// A Record that contains the MediaType and Profile for the HTTP Content-Type and Accept Headers + /// + /// A MIME type for the header + /// A profile to append to the MIME type, leave empty for none + public record struct ContentType(string MediaType, string Profile); +}