Skip to content

fix(http): decode HTML entities in filenames in one pass - #1651

Merged
monkeyWie merged 2 commits into
GopeedLab:mainfrom
ChandraChaan:fix/unescape-entities-upstream
Nov 12, 2026
Merged

monkeyWie merged 2 commits into
GopeedLab:mainfrom
ChandraChaan:fix/unescape-entities-upstream

Conversation

@ChandraChaan

Copy link
Copy Markdown
Contributor

unescapeHTMLEntities in internal/protocol/http/helper.go applies its replacements while ranging over a map. Go randomizes map iteration order and the replacements are chained, so the result depends on which entity is replaced first.

For a filename that was HTML-escaped once, such as a&lt;b.txt sent as a&amp;lt;b.txt in a Content-Disposition header, the result is either a&lt;b.txt (correct) or a<b.txt (when &amp; is replaced before &lt;, the text is decoded twice). It varies from run to run. The same applies to &amp;quot;, &amp;gt;, &amp;#39; and &amp;apos;.

This replaces the map loop with a single strings.NewReplacer, which scans left to right once, so every entity is decoded exactly once and the result is deterministic. TestUnescapeHTMLEntities gets two cases for the escaped-ampersand inputs, and each case now runs 200 times, so an order-dependent implementation fails reliably.

Verified on my fork (test workflow, "Test check" job running go test):

On that fix run, Test webview integration (macOS) was cancelled before it ran, so I haven't seen it pass; every other job succeeded. The first attempt of the test-only run stalled on the apt install step, so I cancelled it and re-ran it; the link above is the re-run.

unescapeHTMLEntities applied its replacements while ranging over a map,
so the order changed from run to run. An escaped ampersand such as
"a&amp;lt;b.txt" came out as "a&lt;b.txt" or, when &amp; was replaced
first, as the double-decoded "a<b.txt".

Replace all entities in a single pass with strings.NewReplacer and
cover the escaped-ampersand cases in TestUnescapeHTMLEntities, running
each case repeatedly so an order-dependent implementation fails.
@monkeyWie

Copy link
Copy Markdown
Member

Nice catch on the map-iteration nondeterminism — the 200x loop in the tests plus the fail/pass CI links on your fork make this a textbook bug fix. Verified locally: all 7 subtests pass with the fix and the two new cases reliably fail against the old implementation. Merging, thanks @ChandraChaan!

@codecov

codecov Bot commented Nov 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 73.53%. Comparing base (89dfbe4) to head (e282a59).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1651      +/-   ##
==========================================
- Coverage   73.55%   73.53%   -0.02%     
==========================================
  Files         106      106              
  Lines       16894    16884      -10     
==========================================
- Hits        12426    12416      -10     
  Misses       3168     3168              
  Partials     1300     1300              

☔ 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.

@monkeyWie monkeyWie left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Verified locally — all subtests pass, and the new cases reliably fail against the old implementation.LGTM

@monkeyWie
monkeyWie merged commit 6cd9550 into GopeedLab:main Nov 12, 2026
17 checks passed
@monkeyWie monkeyWie added the bug Something isn't working label Nov 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants