Repository navigation
Content-negotiate by Accept quality, not JSON-only - #3601
Merged
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
Updates ActivityPub content negotiation to correctly handle real-world Accept headers (notably Mastodon’s JSON types plus low-q HTML fallback), so federation requests receive JSON again while browsers still get HTML.
Changes:
- Replace the previous “JSON-only” Accept-header check with a
q-awareaccept_prefers_json()helper and wire it intoQuery::is_activitypub_request(). - Update cache integrations (Surge + LiteSpeed) and related tests to follow the new negotiation behavior.
- Add a changelog entry describing the federation lookup fix.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
includes/functions-request.php |
Introduces accept_prefers_json() helper for quality-aware content negotiation. |
includes/class-query.php |
Switches ActivityPub request detection to use accept_prefers_json() on the raw Accept header. |
integration/surge-cache-config.php |
Aligns Surge cache bucketing with the new Accept-header negotiation helper. |
integration/class-litespeed-cache.php |
Simplifies LiteSpeed RewriteCond to approximate “JSON preferred” based on first media type. |
tests/phpunit/tests/includes/class-test-functions-request.php |
Updates unit tests/data provider to cover q-aware Accept parsing behavior (including Mastodon-style headers). |
tests/phpunit/tests/integration/class-test-litespeed-cache.php |
Updates integration test to compare htaccess Accept matching against accept_prefers_json(). |
.github/changelog/fix-accept-quality-negotiation |
Adds patch-level changelog entry for Mastodon/Fediverse lookup regression fix. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Suppressed comments (4)
docs/how-to/caching.md:96
- The LiteSpeed manual
.htaccesssnippet uses a regex that matches anyapplication/ld+jsonwhose parameters containactivitystreams, which can diverge fromaccept_prefers_activitypub()for non-W3C profile URIs. Tighten the snippet to requirewww.w3.org/ns/activitystreamsto match the plugin logic.
RewriteCond %{HTTP:Accept} ^[\s,]*(application/activity\+json|application/ld\+json[^,]*activitystreams) [NC]
RewriteRule ^ - [E=Cache-Control:vary=%{ENV:LSCACHE_VARY_VALUE}+isjson]
docs/how-to/caching.md:75
- The Nginx
mapexample regex matches anyapplication/ld+jsonwhose parameters containactivitystreams, even if the profile is not the W3C ActivityStreams namespace. Tighten it to requirewww.w3.org/ns/activitystreamsso the doc example aligns withaccept_prefers_activitypub().
"~*^[\s,]*(application/activity\+json|application/ld\+json[^,]*activitystreams)" "json";
tests/phpunit/tests/integration/class-test-litespeed-cache.php:149
- This test suite doesn’t currently cover the case where
application/ld+jsonhas a non-W3C profile URI that still contains the substringactivitystreams(e.g.https://example.com/activitystreams). The current.htaccessregex would match such a header, butaccept_prefers_activitypub()would not, so adding an explicit false case here helps prevent cache-bucket drift.
'application/json' => false,
'application/ld+json' => false,
'application/ld+json; profile="https://example.com/ns"' => false,
docs/how-to/caching.md:60
- The Varnish example regex matches any
application/ld+jsonwhose parameters containactivitystreams, even if the profile is not the W3C ActivityStreams namespace. That can mis-bucket caches compared to the plugin’saccept_prefers_activitypub()logic. Tightening the regex to requirewww.w3.org/ns/activitystreamskeeps the example aligned with the plugin.
This issue also appears in the following locations of the same file:
- line 75
- line 95
if (req.http.Accept ~ "(?i)^[\s,]*(application/activity\+json|application/ld\+json[^,]*activitystreams)") {
hash_data("activitypub");
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposed changes:
9.2.0 tightened content negotiation to treat a request as ActivityPub only when the
Acceptheader was JSON and nothing else. Mastodon (and other software) sendtext/html;q=0.1as a low-priority fallback alongside the ActivityPub types, so that check stopped recognizing them and served the HTML page instead, which broke lookups and federation.This replaces it with a quality-aware, ActivityPub-specific check,
accept_prefers_activitypub():Acceptmedia types byq(ties broken by the order they appear) and looks at the winner.application/activity+json, orapplication/ld+jsoncarrying the ActivityStreams 2.0 profile (matched without the scheme, sohttp://andhttps://both work). Plainapplication/json, bareapplication/ld+json, and other*+jsontypes are not ActivityPub.q=0refuses a type and is ignored.So Mastodon (ActivityPub at
q=1,text/htmlatq=0.1) gets ActivityPub again; a browser (text/htmlatq=1) gets HTML.The Surge cache drop-in calls the same helper. The LiteSpeed
.htaccessrule and the Varnish/Nginx doc examples can't compare quality values, so they approximate by matching when the first media type is ActivityPub, which equals the q-value result for every real client.Other information:
Testing instructions:
curl -H 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams", application/activity+json, text/html;q=0.1' <post-url>returnsapplication/activity+json(Mastodon's exact header).