Skip to content

fix(rss): keep empty path segments when resolving feed links - #2698

Open
kevin (kevin9327) wants to merge 2 commits into
microsoft:mainfrom
kevin9327:fix/rss-keep-empty-path-segments
Open

kevin (kevin9327) wants to merge 2 commits into
microsoft:mainfrom
kevin9327:fix/rss-keep-empty-path-segments

Conversation

@kevin9327

Copy link
Copy Markdown
Contributor

Fixes #2468.

Problem

_resolve_url() used urllib.parse.urljoin(), whose relative-path merge drops empty path segments (python/cpython#84774). For example, exports/2026//report.csv against https://example.com/bucket/feed.xml resolved to .../exports/2026/report.csv, which can be a different object on path-sensitive servers and object stores. Repeated slashes in the base URL (including xml:base) were collapsed the same way.

Change

A small resolver, _join_url(), in _rss_converter.py follows RFC 3986 §5.2 (merge + remove_dot_segments) for the one case urljoin gets wrong: a relative or absolute-path reference against a hierarchical base with a scheme. Everything else goes to urljoin unchanged: empty references, scheme-less or non-hierarchical bases (mailto:, urn:), network-path (//host) references, and references with another scheme. The ValueError fallback in _resolve_url() is unchanged. Once the upstream fix lands, this helper can be removed.

Tests (tests/test_rss.py)

  • End-to-end RSS conversion (<a href> and <img src>): empty segments in the reference, in the base path, mixed with .., and in absolute-path references with a query or fragment. 3 of the 4 cases fail on main.
  • All RFC 3986 §5.4 normal and abnormal examples (41 cases). http:g is left out because we keep urljoin's non-strict reading.
  • Parity with urljoin for the delegated cases.

Verification

  • tests/test_rss.py: 217 passed. Full suite (excluding network or service tests): 949 passed, 12 skipped.
  • Differential fuzz against urljoin (200,000 random base/reference pairs, including dot segments, queries, fragments, //host, other schemes, and non-hierarchical bases): 0 differences when no empty segment is involved. The only differences (16,139) are cases with empty segments, where the new result keeps them.
  • black --check clean.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reviewed head c45c3a2. One correctness regression needs fixing before merge: a network-path reference with leading whitespace loses its authority and is rewritten onto the feed's host.

from urllib.parse import urljoin
from markitdown.converters._rss_converter import _join_url

base = "https://feeds.example.org/news/feed.xml"
reference = " //cdn.example.org/book"
print(urljoin(base, reference))   # https://cdn.example.org/book
print(_join_url(base, reference)) # https://feeds.example.org/book

urlsplit() removes leading whitespace and recognizes ref.netloc, but the delegation condition checks the original reference.startswith("//"). With a space, tab, or newline prefix, that check is false; reconstruction then uses base.netloc and discards cdn.example.org. I also reproduced this through MarkItDown.convert_stream() with RSS description HTML containing <a href=" //cdn.example.org/book">Book</a> and <img src=" //cdn.example.org/cover.png" alt="Cover">: both URLs change to feeds.example.org. This breaks existing links and images even when no repeated slash is involved.

Please delegate references with a parsed authority (ref.netloc) to urljoin, and add whitespace-prefixed network-path cases to the parity and public API coverage. In a temporary probe, that delegation restored all three prefixes while retaining exports//report.csv unchanged.

Validation on macOS / Python 3.12.15: the six additional checks (space/tab/newline through the resolver and public API) pass with main's resolver at 4cc9fa1 and fail on this exact PR head. The existing RSS suite passes all 217 tests, pre-commit run --all-files passes, and git diff --check passes. The full package run was interrupted before completion; other Python versions and Windows were not tested. There were no earlier review findings or inline comments when I checked.

Signed-off-by: J Djs <5299031+kevin9327@users.noreply.github.com>
@kevin9327

Copy link
Copy Markdown
Contributor Author

Addressed in 9b6e59c: _join_url now delegates whenever ref.netloc is set (after urlsplit strips leading whitespace), so a whitespace-prefixed network-path like //cdn.example.org/book keeps its authority via urljoin. Added space/tab/newline coverage through the resolver and MarkItDown.convert_stream(). Ready for another look.

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.

RSS/Atom: urljoin collapses significant repeated slashes in resolved links

2 participants