Skip to content

Fix DownloadHeader for real servers - #8

Merged
capjan merged 2 commits into
mainfrom
fix/download-header
Sep 26, 2026
Merged

capjan merged 2 commits into
mainfrom
fix/download-header

Conversation

@capjan

@capjan capjan commented Sep 26, 2026

Copy link
Copy Markdown
Owner

Summary

HttpChannelExt.DownloadHeader threw a FormatException for practically every real server, so it never worked outside of tests with hand-made dictionaries. TryDownloadHeader swallowed the exception and returned false.

Cause

The header dictionary was built with v.Value.ToString() on an IEnumerable<string>, which yields "System.String[]". HttpHeader then parsed that text as the Date header and threw.

Changes

  • Read the values of the headers instead of the type name; several values of one header are joined with ", ".
  • Also read the content headers. Content-Length, Content-Type and Last-Modified are part of Content.Headers, not of the response headers, so these properties were never set.
  • HttpHeader looks header names up case-insensitively (HTTP/2 sends them in lower case). RawDictionary is still the dictionary that was passed in.
  • Dispose the response.

The public API is unchanged.

Tests

  • DownloadHeaderTest and HttpHeaderTest use a fake server, no internet needed.
  • HttpChannelTest gets two tests against a real server (Category=Network, non-blocking in CI).
  • Tests that use the process-wide SharedHttpClient run in one xunit collection (SharedHttpClientCollection) so they cannot race.

Verification

  • Without the fix, the two real-server tests fail (FormatException, TryDownloadHeader returns false); with the fix they pass
  • 270/270 tests pass on net10.0 and net8.0 (local, macOS), 25 repeated runs without a failure
  • Build with analyzers as errors: 0 warnings

Not part of this PR

DownloadToString ignores its encoding parameter and does not check the status code. Both change results, so they are left for the network work package of the 13.0 plan.

DownloadHeader threw a FormatException for practically every server. It built the header dictionary
with `v.Value.ToString()` on an IEnumerable<string>, which yields "System.String[]", and HttpHeader then
tried to parse that as the Date header. TryDownloadHeader swallowed the exception and returned false.

- read the values of the headers instead of the type name; join several values with ", "
- also read the content headers (Content-Length, Content-Type, Last-Modified, ...); they are not part
  of the response headers, so these properties were never set
- look header names up case-insensitively in HttpHeader (HTTP/2 sends them in lower case);
  RawDictionary still is the dictionary that was passed in
- dispose the response

Tests
- DownloadHeaderTest and HttpHeaderTest use a fake server, no internet needed
- HttpChannelTest gets two tests against a real server (network trait)
- tests that use the process-wide SharedHttpClient run in one xunit collection so they cannot race
AddHeaders joined every header with several values with ", ". That is not valid for Set-Cookie
(RFC 6265, section 3; RFC 9110, section 5.3): a comma can be part of a cookie, e.g. in
"Expires=Wed, 21 Oct 2015 07:28:00 GMT", so the boundary between two cookies got lost.

- several Set-Cookie values are joined with the new HttpHeader.SetCookieSeparator (a line feed, which
  can never be part of a header value); this applies to SetCookie and to RawDictionary
- add HttpHeader.SetCookies, one entry per cookie (a new member of the class, IHttpHeader is unchanged)
- every other header with several values is still joined with ", "
@capjan
capjan merged commit 1986d8c into main Sep 26, 2026
4 checks passed
@capjan
capjan deleted the fix/download-header branch September 26, 2026 00:36
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