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

unescapeHTMLEntities applied its replacements while ranging over a map,
so the order changed from run to run. An escaped ampersand such as
"a<b.txt" came out as "a<b.txt" or, when & 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.
This commit is contained in:
Chandra ObulaReddy authored and GitHub committed 2026-11-12 09:23:13 +08:00
1 parent 89dfbe4c45
commit 6cd95507fd
2 files changed
+28 -18

No files matched your search

+16 -3
View File
@@ -125,13 +125,26 @@ func TestUnescapeHTMLEntities(t *testing.T) {
in: "normal.txt",
want: "normal.txt",
},
{
name: "escaped ampersand before entity name is decoded once",
in: "a&amp;lt;b.txt",
want: "a&lt;b.txt",
},
{
name: "escaped ampersand before quote entity is decoded once",
in: "a&amp;quot;b.txt",
want: "a&quot;b.txt",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := unescapeHTMLEntities(tt.in)
if got != tt.want {
t.Errorf("unescapeHTMLEntities() = %q, want %q", got, tt.want)
// Run repeatedly so that an order-dependent implementation fails reliably.
for i := 0; i < 200; i++ {
got := unescapeHTMLEntities(tt.in)
if got != tt.want {
t.Fatalf("unescapeHTMLEntities() = %q, want %q (run %d)", got, tt.want, i)
}
}
})
}