Skip to content

fix: resolve double-encoded HTML entities in title and description - #456

Open
Zerthick wants to merge 1 commit into
Guts:mainfrom
Zerthick:fix/double-encoded-html-entities
Open

Zerthick wants to merge 1 commit into
Guts:mainfrom
Zerthick:fix/double-encoded-html-entities

Conversation

@Zerthick

Copy link
Copy Markdown

Description

Resolve double-encoded HTML entities (e.g. &, <, >) in RSS feed title and description fields.

MkDocs pre-escapes content, and then Jinja's |e filter escapes again, producing double-encoded entities. Using html.unescape() in Python before template rendering ensures |e performs a single correct escape.

Changes

  • Add html_module.unescape() for title and description in plugin.py
  • Add test fixture with special characters (& < >)
  • Add test verifying no double-encoded entities in output

Split from: #451


💡 AI Contribution Level: 8 — Based on the VisiData AI contribution scale, this was an AI-assisted contribution where the AI performed the code splitting and PR creation with minimal human guidance.

MkDocs pre-escapes content, then Jinja's |e filter escapes again,
producing &amp;amp;, &amp;lt;, &amp;gt;. Use html.unescape() in Python
before template rendering so |e performs a single correct escape.

- Add html_module.unescape() for title and description in plugin.py
- Add test fixture with special characters
- Add test verifying no double-encoded entities
@github-actions github-actions Bot added bug Something isn't working quality Tests, project resiliency, etc. labels Aug 10, 2026
@sonarqubecloud

Copy link
Copy Markdown

@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.18%. Comparing base (1b5e630) to head (ada0a8d).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #456      +/-   ##
==========================================
+ Coverage   83.16%   83.18%   +0.02%     
==========================================
  Files          11       11              
  Lines         784      785       +1     
  Branches      133      133              
==========================================
+ Hits          652      653       +1     
  Misses         86       86              
  Partials       46       46              
Flag Coverage Δ
unittests 83.18% <100.00%> (+0.02%) ⬆️

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

Files with missing lines Coverage Δ
mkdocs_rss_plugin/plugin.py 89.50% <100.00%> (+0.06%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Guts Guts left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this one.

The description part, however, introduces a regression, and the test doesn't exercise the bug:

  1. description is HTML, not text : get_description_or_abstract() returns page.content or markdown.markdown(...) output in almost every branch, and the JSON Feed publishes it as content_html. In RSS, <description> carries that HTML escaped once for XML, so &amp;lt; in the raw XML is the correct encoding of a &lt; in the HTML. Unescaping it first turns code samples into real markup. With Use `<script>` and `a < b` in code.:

    • main: readers get <p>Use <code>&lt;script&gt;</code> and <code>a &lt; b</code> in code.</p>
    • this PR: readers get <p>Use <code><script></code> and <code>a < b</code> in code.</p>

    For a docs plugin, where inline code is everywhere, that's a blocker. Please keep the unescape on title only (see inline suggestions).

  2. fixture doesn't reproduce the bug because its title is set in the front-matter, which MkDocs returns verbatim; only H1-derived titles carry entities. Same for the description meta, which |e already escapes once. The new test passes on main without the fix. I've suggested a fixture with an H1 title instead.

Please also rebase on main.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This module can be removed: it duplicates a full build already done in test_build.py, and assertNotIn("&amp;lt;") rejects the valid encoding of HTML descriptions. A single assertion in test_xml_escaping_in_author covers the title fix.

guid=page.canonical_url,
link=page_url_full,
title=page.title,
title=html_module.unescape(page.title) if page.title else None,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Short comment to reùember why only the title gets this treatment:

Suggested change
title=html_module.unescape(page.title) if page.title else None,
# MkDocs keeps HTML entities in titles extracted from the H1
title=html_module.unescape(page.title) if page.title else None,

in_page=page,
chars_count=self.config.abstract_chars_count,
abstract_delimiter=self.config.abstract_delimiter,
description=html_module.unescape(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

description is HTML (page.content / markdown.markdown() output, published as content_html in the JSON Feed): it must stay escaped once, so no unescape here.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working quality Tests, project resiliency, etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants