Skip to content

Delete WebHeaderEncoding.cs and replace usages with Encoding.Latin1#125592

Merged
EgorBo merged 3 commits intomainfrom
copilot/unable-rat
Mar 19, 2026
Merged

Delete WebHeaderEncoding.cs and replace usages with Encoding.Latin1#125592
EgorBo merged 3 commits intomainfrom
copilot/unable-rat

Conversation

Copy link
Contributor

Copilot AI commented Mar 15, 2026

Deletes WebHeaderEncoding.cs entirely — its stated justification (Latin-1 being slow for byte→string, best-fit-mapping issues for string→byte) is outdated. All call sites now use Encoding.Latin1 directly.

Description

The WebHeaderEncoding class provided custom unsafe Latin-1 byte↔char conversion helpers. As noted in review, Encoding.Latin1 handles both directions correctly today, making the custom implementation unnecessary.

Deleted src/libraries/Common/src/System/Net/WebHeaderEncoding.cs and its <Compile> reference from System.Net.HttpListener.csproj.

Replaced all call sites across 3 files:

  • HttpListener.Windows.cs: WebHeaderEncoding.GetString(bytes, 0, bytes.Length)Encoding.Latin1.GetString(bytes)
  • HttpListenerResponse.Windows.cs: Collapsed GetByteCount + new byte[] + GetBytes into single Encoding.Latin1.GetBytes(str) calls (5 occurrences)
  • HttpListenerResponse.Managed.cs: WebHeaderEncoding.GetBytes(str)Encoding.Latin1.GetBytes(str)

Updated test: For chars > U+00FF (e.g. U+1234), Encoding.Latin1 produces ? (replacement character) instead of truncating to the low byte. This only affects already-invalid inputs that were silently corrupting data before.

AllowUnsafeBlocks remains in the HttpListener .csproj — other files in the project still require it.

Original prompt

remove unsafe code from WebHeaderEncoding using local changes.

[Chronological Review] 1. User requested removal of unsafe code from WebHeaderEncoding.cs, initially asking for Vector128.Widen-based replacement 2. Explored the file, found two unsafe methods: GetString (byte→char with unrolled-by-8 pointer loop) and GetBytes (char→byte with pointer loop) 3. Found the file is only consumed by System.Net.HttpListener project 4. Implemented Vector128.Widen/Narrow replacement with MemoryMarshal.Cast and Vector128.Create/CopyTo on Spans 5. Build failed - missing System.Runtime.Intrinsics reference in csproj 6. Added ProjectReference for System.Runtime.Intrinsics to HttpListener csproj 7. Build succeeded, all 693 tests passed 8. Created a benchmark file for performance validation 9. User reverted changes and asked for simpler approach: plain Span access without MemoryMarshal or Unsafe.X, keeping same 8-per-iteration unrolling 10. Implemented simple span-based version with while loop unrolled by 8 11. Build succeeded, all 693 tests passed 12. User asked to convert the while loop into a for loop 13. Converted while to for loop with lastBlockStart = source.Length - 7

[Intent Mapping]

  • Original: "remove unsafe code from WebHeaderEncoding.cs, replace unrolled loop with Vector128.Widen"
  • Revised: "just convert to plain Span access (without MemoryMarshal or Unsafe.X) same 8 per iteration"
  • Latest: "please convert that while into for loop"

[Technical Inventory]

  • Repository: C:\prj\runtime-main2 (dotnet/runtime, targeting .NET 11)
  • Build system: .\build.cmd -subset libs.ref+libs.src -project ...
  • Test system: .\build.cmd -subset libs.tests -project ... -test
  • SDK: 11.0.100-preview.3.26161.119 (requires repo build script, not plain dotnet build)
  • File: Common shared source linked into System.Net.HttpListener

[Code Archaeology]

  • WebHeaderEncoding.cs: internal static class with GetString (byte[]→string), GetBytes (string→byte[]), GetByteCount
  • System.Net.HttpListener.csproj: only consumer of WebHeaderEncoding.cs, still has AllowUnsafeBlocks (other files need it)
  • Benchmark created at C:\Users\egorb.copilot\session-state\697f2cf1-dac7-4a8a-aad4-80fae26ba20e\files\benchmark.cs

[Progress Assessment]

  • DONE: Removed all unsafe code from WebHeaderEncoding.cs
  • DONE: Converted to plain Span-based code
  • DONE: Converted while to for loop
  • DONE: Build passes, 693 tests pass

[Context Validation]

  • The csproj Intrinsics reference was reverted (confirmed clean via git diff)
  • The file no longer uses any unsafe, MemoryMarshal, or Intrinsics APIs
  • AllowUnsafeBlocks remains in csproj because 18+ other files still use unsafe

[Recent Commands Analysis]

  • Last command: edit to convert while loop to for loop with lastBlockStart = source.Length - 7
  • Result: File updated successfully
  • No build/test run after this last edit
1. Conversation Overview: - Primary Objectives: User requested: "please remove unsafe code from C:\prj\runtime-main2\src\libraries\Common\src\System\Net\WebHeaderEncoding.cs". Initially wanted Vector128.Widen replacement, then revised to: "just convert to just plain Span access (without MemoryMarshal or Unsafe.X) same 8 per iteration". Final request: "please convert that while into for loop". - Session Context: Started with ambitious Vector128 SIMD approach, hit build issues (missing Intrinsics reference), resolved them, user reverted and asked for simpler plain-Span approach, then refined loop style. - User Intent Evolution: Vector128+MemoryMarshal → plain Span only → while→for loop refinement
  1. Technical Foundation:

    • Repository: dotnet/runtime at C:\prj\runtime-main2, targeting .NET 11 (SDK 11.0.100-preview.3.26161.119)
    • Build: Must use .\build.cmd not dotnet build (global.json requires preview SDK not installed globally)
    • Build command: .\build.cmd -subset libs.ref+libs.src -project <csproj>
    • Test command: .\build.cmd -subset libs.tests -project <test-csproj> -test
    • The file is a Common shared source file linked via <Compile Include="$(CommonPath)System\Net\WebHeaderEncoding.cs" /> — only consumed by System.Net.HttpListener
  2. Codebase Status:

    • src/libraries/Common/src/System/Net/WebHeaderEncoding.cs:
      • Purpose: Latin-1 byte↔char conversion helper for HTTP headers (1:1 mapping U+0000-U+00FF ↔ 0x00-0xFF)
      • Current State: All unsafe code removed. Uses plain Span indexing. Last edit converted while→for loop but has not been build-tested after this final edit.
      • Key methods: GetString(byte[], int, int) → string (unrolled-by-8 for loop over Spans), GetBytes(string, int, int, byte[], int) → void (simple span for loop), GetBytes(string) → byte[], GetByteCount(string) → int
      • Current code uses: ReadOnlySpan<byte> source = state.bytes.AsSpan(state.byteIndex, buffer.Length) with int lastBlockStart = source.Length - 7 and for (; i < lastBlockStart; i += 8) unrolled pattern
    • `src/librarie...

Created from Copilot CLI via the copilot delegate command.


📍 Connect Copilot coding agent with Jira, Azure Boards or Linear to delegate work to Copilot in one click without leaving your project management tool.

@EgorBo
Copy link
Member

EgorBo commented Mar 15, 2026

@MihuBot

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR removes unsafe pointer-based implementations from WebHeaderEncoding by rewriting the byte↔char conversion paths using Span/ReadOnlySpan, keeping the existing loop unrolling pattern for GetString.

Changes:

  • Replaced unsafe pointer loop in WebHeaderEncoding.GetString with a span-based unrolled for loop.
  • Replaced unsafe pointer loop in WebHeaderEncoding.GetBytes with span slicing + indexed copy.
  • Made the string.Create lambda static to avoid captures.

@MihaZupan
Copy link
Member

I would consider deleting this file while you're at it. Its stated justification for existence seems to be outdated / wrong

// we use this static class as a helper class to encode/decode HTTP headers.
// what we need is a 1-1 correspondence between a char in the range U+0000-U+00FF
// and a byte in the range 0x00-0xFF (which is the range that can hit the network).
// The Latin-1 encoding (ISO-88591-1) (GetEncoding(28591)) works for byte[] to string, but is a little slow.
// It doesn't work for string -> byte[] because of best-fit-mapping problems.
internal static class WebHeaderEncoding

Switching it to Latin1 would only change behavior if the inputs weren't already in the [0, 255] range, in which case we'd already be corrupting data.

Copilot AI changed the title [WIP] Remove unsafe code from WebHeaderEncoding Remove unsafe code from WebHeaderEncoding Mar 15, 2026
Copilot AI requested a review from EgorBo March 15, 2026 23:24
@EgorBo
Copy link
Member

EgorBo commented Mar 16, 2026

I would consider deleting this file while you're at it. Its stated justification for existence seems to be outdated / wrong

// we use this static class as a helper class to encode/decode HTTP headers.
// what we need is a 1-1 correspondence between a char in the range U+0000-U+00FF
// and a byte in the range 0x00-0xFF (which is the range that can hit the network).
// The Latin-1 encoding (ISO-88591-1) (GetEncoding(28591)) works for byte[] to string, but is a little slow.
// It doesn't work for string -> byte[] because of best-fit-mapping problems.
internal static class WebHeaderEncoding

Switching it to Latin1 would only change behavior if the inputs weren't already in the [0, 255] range, in which case we'd already be corrupting data.

@copilot please address this feedback

Co-authored-by: EgorBo <523221+EgorBo@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 17, 2026

I would consider deleting this file while you're at it. Its stated justification for existence seems to be outdated / wrong

https://github.com/dotnet/runtime/blob/c38f37a3a1c39ca921fe156d4729...

Deleted WebHeaderEncoding.cs and replaced all call sites with Encoding.Latin1. The only behavioral change is for chars > U+00FF in GetBytes: they now encode to ? (Latin1 replacement character) instead of being truncated to their low byte — updated the test accordingly. Build passes, all 662 tests pass. See 36fa1da.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • 192
    • Triggering command: /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet exec --runtimeconfig System.Net.HttpListener.Tests.runtimeconfig.json --depsfile System.Net.HttpListener.Tests.deps.json /home/REDACTED/.nuget/packages/microsoft.dotnet.xunitconsoleREDACTED/2.9.3-beta.26163.109/build/../tools/net/xunit.console.dll System.Net.HttpListener.Tests.dll -xml testResults.xml -nologo -notrait category=OuterLoop -notrait category=failing ndor/bin/chmod eading.Tasks.dllchmod (dns block)
    • Triggering command: /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet exec --runtimeconfig System.Net.HttpListener.Tests.runtimeconfig.json --depsfile System.Net.HttpListener.Tests.deps.json /home/REDACTED/.nuget/packages/microsoft.dotnet.xunitconsoleREDACTED/2.9.3-beta.26163.109/build/../tools/net/xunit.console.dll System.Net.HttpListener.Tests.dll -xml testResults.xml -nologo -notrait category=OuterLoop -notrait category=failing (dns block)
  • []
    • Triggering command: /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet exec --runtimeconfig System.Net.HttpListener.Tests.runtimeconfig.json --depsfile System.Net.HttpListener.Tests.deps.json /home/REDACTED/.nuget/packages/microsoft.dotnet.xunitconsoleREDACTED/2.9.3-beta.26163.109/build/../tools/net/xunit.console.dll System.Net.HttpListener.Tests.dll -xml testResults.xml -nologo -notrait category=OuterLoop -notrait category=failing ndor/bin/chmod eading.Tasks.dllchmod (dns block)
    • Triggering command: /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet exec --runtimeconfig System.Net.HttpListener.Tests.runtimeconfig.json --depsfile System.Net.HttpListener.Tests.deps.json /home/REDACTED/.nuget/packages/microsoft.dotnet.xunitconsoleREDACTED/2.9.3-beta.26163.109/build/../tools/net/xunit.console.dll System.Net.HttpListener.Tests.dll -xml testResults.xml -nologo -notrait category=OuterLoop -notrait category=failing (dns block)
  • doesntexist
    • Triggering command: /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet exec --runtimeconfig System.Net.HttpListener.Tests.runtimeconfig.json --depsfile System.Net.HttpListener.Tests.deps.json /home/REDACTED/.nuget/packages/microsoft.dotnet.xunitconsoleREDACTED/2.9.3-beta.26163.109/build/../tools/net/xunit.console.dll System.Net.HttpListener.Tests.dll -xml testResults.xml -nologo -notrait category=OuterLoop -notrait category=failing ndor/bin/chmod eading.Tasks.dllchmod (dns block)
    • Triggering command: /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet /home/REDACTED/work/runtime/runtime/artifacts/bin/testhost/net11.0-linux-Debug-x64/dotnet exec --runtimeconfig System.Net.HttpListener.Tests.runtimeconfig.json --depsfile System.Net.HttpListener.Tests.deps.json /home/REDACTED/.nuget/packages/microsoft.dotnet.xunitconsoleREDACTED/2.9.3-beta.26163.109/build/../tools/net/xunit.console.dll System.Net.HttpListener.Tests.dll -xml testResults.xml -nologo -notrait category=OuterLoop -notrait category=failing (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title Remove unsafe code from WebHeaderEncoding Delete WebHeaderEncoding.cs and replace usages with Encoding.Latin1 Mar 17, 2026
@EgorBo
Copy link
Member

EgorBo commented Mar 18, 2026

@MihaZupan is that what you meant? Seems to be passing CI

@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @karelz, @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Copy link
Member

@MihaZupan MihaZupan left a comment

Choose a reason for hiding this comment

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

is that what you meant?

Yes, thank you.

Looks like we even had a test for "I want this particular flavor of data corruption" 😄

@EgorBo
Copy link
Member

EgorBo commented Mar 19, 2026

/ba-g "Build osx-x64 Debug Mono_MiniJIT_LibrariesTests is stuck (10h)"

@EgorBo EgorBo merged commit 2066293 into main Mar 19, 2026
77 of 87 checks passed
@EgorBo EgorBo deleted the copilot/unable-rat branch March 19, 2026 17:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants