Skip to content

Markdown Feeds: Follow Accept header preferences when negotiating - #1125

Merged
dkotter merged 2 commits into
WordPress:developfrom
ekamran:shifteq/markdown-accept-q
Oct 6, 2026
Merged

dkotter merged 2 commits into
WordPress:developfrom
ekamran:shifteq/markdown-accept-q

Conversation

@ekamran

@ekamran ekamran commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

What?

Closes #1124

Makes the Markdown Feeds Accept-header negotiation follow the header's preferences instead of its first media range.

Why?

is_markdown_requested() matched text/markdown only as the first media range and never read quality values. So text/markdown;q=0.9, text/html served Markdown although HTML was preferred, text/markdown;q=0 served Markdown although the client refused it, and application/json, text/markdown served HTML although Markdown was the only acceptable type of the two. The setting's label promises Markdown "when a request prefers it". Details in #1124.

How?

  • New private prefers_markdown( string $accept ): bool in Markdown_Feeds. It walks every media range of the header, reads q (default 1, invalid values ignored), and keeps the best match for text/html and for text/markdown with the usual precedence: an exact type beats text/*, which beats */*. text/x-markdown counts as text/markdown. Types and parameters are compared lowercase with surrounding spaces ignored.
  • Markdown is served when its quality is higher than the one for HTML. On equal quality the more specific match wins (text/markdown beats text/*), then the type listed first. So text/markdown, text/html, */* serves Markdown as the released version does, text/html, text/markdown stays HTML, text/*, text/html stays HTML, */* alone, text/* alone and browser headers (text/html,...,*/*;q=0.8) keep HTML (one range matching both types has no order), and text/markdown;q=0 can no longer serve Markdown.
  • Nothing else changes: ?output_format=markdown, the sub-toggle gate, the Vary: Accept header and the discovery links are untouched. The setting stays off by default.
  • One sentence in docs/experiments/markdown-feeds.md describes the rule.

Before and after on a singular URL with the setting on:

Accept develop This PR
text/markdown Markdown Markdown
text/x-markdown Markdown Markdown
TEXT/MARKDOWN ; charset=utf-8 ; q = 1 HTML Markdown
application/json, text/markdown HTML Markdown
text/html;q=0.1, text/markdown HTML Markdown
*/*, text/html;q=0.2, text/markdown;q=0.9 HTML Markdown
text/*, text/markdown HTML Markdown
text/markdown;q=0.9, text/html Markdown HTML
text/markdown;q=0, text/html;q=0.5 Markdown HTML
text/markdown, text/html, */* Markdown Markdown
text/html, text/markdown HTML HTML
text/*, text/html HTML HTML
*/* HTML HTML
text/* HTML HTML
browser default header HTML HTML

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code and Codex
Used for: Investigation, implementation, tests, verification, and PR wording. I reviewed the reasoning and test results, and I take responsibility for the contribution.

Testing Instructions

  1. Enable the Markdown Feeds experiment and switch on "Serve Markdown when a request prefers it via the Accept header".
  2. curl -sI -H 'Accept: text/markdown;q=0.9, text/html' <post URL>: text/html with this PR, text/markdown on develop.
  3. curl -sI -H 'Accept: application/json, text/markdown' <post URL>: text/markdown with this PR, text/html on develop.
  4. curl -sI -H 'Accept: text/markdown' <post URL> still gives text/markdown; curl -sI <post URL> (*/*) and a browser still give HTML.

Automated:

  • npm run test:php -- --filter test_accept_header runs the two Accept tests, one of them a table of 21 headers. Nine rows fail on develop and all pass here.
  • The full PHP suite passes on single site and on multisite (1793 tests).
  • composer lint and PHPStan pass.

Changelog Entry

Fixed - Markdown Feeds: Accept-header negotiation now follows the header's quality values and order, so a request that prefers HTML gets HTML and one that prefers Markdown gets Markdown.

Open WordPress Playground Preview

The Accept check only looked at the first media range, so a client that
listed text/html first never got Markdown, a client that put Markdown
first with a lower quality than HTML got Markdown, and text/markdown;q=0
still served Markdown. Quality values were never read.

prefers_markdown() now parses every media range of the header, with the
usual precedence of an exact type over text/* over */*, and serves
Markdown only when its quality is higher than the one for HTML. Ties,
refusals, */* and text/* keep HTML, so browsers and curl are unchanged.
text/x-markdown counts as text/markdown, and types and parameters are
matched case-insensitively with surrounding spaces ignored.
@ekamran
ekamran requested a review from a team October 6, 2026 14:12
@ekamran
ekamran requested a review from jeffpaul as a code owner October 6, 2026 14:12
@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: ekamran <ekamran@git.wordpress.org>
Co-authored-by: dkotter <dkotter@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@codecov

codecov Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.67%. Comparing base (2cc9603) to head (a9c1abb).
⚠️ Report is 2 commits behind head on develop.

Additional details and impacted files
@@              Coverage Diff              @@
##             develop    #1125      +/-   ##
=============================================
+ Coverage      81.62%   81.67%   +0.05%     
- Complexity      3088     3103      +15     
=============================================
  Files            129      129              
  Lines          12297    12334      +37     
=============================================
+ Hits           10037    10074      +37     
  Misses          2260     2260              
Flag Coverage Δ
unit 81.67% <100.00%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

With equal quality, the more specific match now wins and then the type
listed first in the Accept header. So "text/markdown, text/html, */*"
keeps serving Markdown as the released version did, "text/html,
text/markdown" stays HTML, and "text/*, text/html" stays HTML. A wildcard
range matching both types still falls to HTML.
@dkotter dkotter added this to the 1.5.0 milestone Oct 6, 2026
@dkotter
dkotter merged commit 19d5670 into WordPress:develop Oct 6, 2026
38 of 39 checks passed
@ekamran
ekamran deleted the shifteq/markdown-accept-q branch October 6, 2026 17:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Markdown Feeds: Accept-header negotiation ignores quality values and only reads the first media range

2 participants