Skip to content

fix: make the RSS feeds valid - #170

Draft
0xB10C wants to merge 3 commits into
mainfrom
2026-09-rss-feed-validity
Draft

0xB10C wants to merge 3 commits into
mainfrom
2026-09-rss-feed-validity

Conversation

@0xB10C

@0xB10C 0xB10C commented Sep 24, 2026

Copy link
Copy Markdown
Owner

fixes a few things with the RSS feeds:

  • Values went into the templates unescaped, so a node or network name containing &, < or > produced a document no XML parser accepts: xmlParseEntityRef: no name on a title like Unreachable nodes - Signet & Testnet. Readers reject the whole feed rather than skipping the bad item, and names come straight from the operator's config. Escaping now happens in the Display implementations so a new field cannot forget it.
  • The channel link was built as {base}?network={id}?src={feed}, with a second ? where the parameter separator belongs. Following it hands the frontend a network parameter that matches nothing, so the reader lands on the default network instead of the one the feed is about.
  • The atom:link self href appended /rss/... to a base the example config ends with a slash, advertising https://fork-observer.example.com//rss/1/forks.xml. That URL is what a reader stores and polls, and what it compares against the URL it was subscribed at. Both URLs now come from one helper that normalizes the base.

The feed templates interpolated every value raw, so a node or network
name containing an `&`, `<` or `>` produced a document no XML parser
accepts:

  feed.xml:4: parser error : xmlParseEntityRef: no name
    <title>Unreachable nodes - Signet & Testnet</title>

RSS readers reject the whole feed in that case rather than skipping the
offending item, so a single such name silently takes down all four feeds
for everyone subscribed. Node and network names come straight from the
operator's config, which makes this reachable without anything unusual
going on.

The escaping happens in the Display implementations rather than at the
call sites, so a field added later can't forget it. The five predefined
entities cover both character data and the double-quoted atom:link
attribute.
All four feeds built the channel `<link>` as

  {base}?network={id}?src={feed}

with a second `?` where the parameter separator belongs. A reader that
follows the link hands the frontend a network parameter whose value is
`1?src=forks-rss`, which matches no network, so the link lands on the
default network instead of the one the feed is about.

The four call sites built their two URLs by hand, which is how they
drifted apart in the first place, so they now go through one helper.
Writing the separator as a plain `&` is correct because the Display
implementation escapes it into `&amp;` on the way out.
The atom:link self `href` was built as `{base}/rss/...`, while the
channel `<link>` appended its query string to the base directly. Only
one of the two can be right for a given base_url, and the value the
example config ships ends in a slash, so a stock deployment advertises
itself as

  https://fork-observer.example.com//rss/1/forks.xml

The self link is what a reader stores and polls, and what it compares
against the URL it was subscribed at, so a base_url without the trailing
slash moved the problem to the channel link instead.

Trimming the trailing slash once makes both URLs independent of how the
base_url is written.
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.

1 participant