Add net9 Uri span escaping APIs - #595
Merged
Merged
Conversation
EscapeDataString, UnescapeDataString, TryEscapeDataString and TryUnescapeDataString over spans. Each routes through the string overload on the same target, so the escaped set matches whatever that framework's Uri.EscapeDataString produces rather than backporting net11 behaviour onto .NET Framework. That costs an intermediate string, which is the whole point of the span overloads, so it is noted on all four. Verified against net11: a failed Try reports zero chars written, an exactly sized destination succeeds, and an overlapping destination works for unescaping, which only ever shrinks. While testing, found that net9.0 and net10.0 throw ArgumentOutOfRangeException from Uri.TryUnescapeDataString instead of returning false whenever the destination is smaller than the literal text preceding the first escape sequence. Fixed in net11. The polyfill matches net11, so net8.0 and below return false. Covered by a test that asserts the throw on the two affected frameworks. API count 1105 -> 1109.
SimonCropp
added a commit
that referenced
this pull request
Sep 10, 2026
…framework (#596) The tests added in #595 asserted that Uri.TryUnescapeDataString throws on net9.0 and net10.0, which bets that neither is ever serviced. dotnet/runtime#128610 is a written backport of the fix to release/10.0 that was deferred rather than rejected, so that bet could be lost, and net10.0 is in support until 2028. The behaviour is now probed once instead. net9.0 and net10.0 accept either outcome, and the assertion that it must not throw is kept for net11.0 and for every target where the polyfill is active.
This was referenced Sep 10, 2026
This was referenced Sep 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four members, all net9:
Uri.EscapeDataString(ReadOnlySpan<char>),Uri.UnescapeDataString(ReadOnlySpan<char>),Uri.TryEscapeDataStringandUri.TryUnescapeDataString.Delegating to the string overloads is the correct choice here, not just the easy one
Uri.EscapeDataStringdoes not escape the same set of characters on .NET Framework as it does on .NET —!*'()is the well known difference. Routing the span overloads through the string overload on the same target means the two agree on every framework, which is what a caller mixing them would expect. Reimplementing RFC 3986 escaping in the polyfill would have made the span overloads disagree with the string overloads on .NET Framework, which is worse.The cost is an intermediate string, which is precisely what the span overloads exist to avoid. That is
//Note:d on all four, and falls under the "deliverability over perf" rule.Semantics verified against net11
TryreportscharsWrittenof 0. The BCL may leave the destination partially written on failure; the polyfill leaves it untouched, which no caller can depend on either way sincecharsWrittenis 0.A BCL bug on net9.0 and net10.0
Worth flagging separately.
Uri.TryUnescapeDataStringthrowsArgumentOutOfRangeExceptioninstead of returningfalsewhenever the destination is smaller than the literal text preceding the first escape sequence:abc%20defArgumentOutOfRangeExceptionfalseabc%20deffalsefalseabc%20deftrue, 7 writtentrue, 7 writtenThe boundary is exactly
destination.Length < input.IndexOf('%'); inputs with no%are unaffected, andTryEscapeDataStringdoes not have the problem. It is fixed in net11, and the polyfill behaves the way net11 does, so net8.0 and below returnfalse.This turned up because the sizing test — which runs the full destination-size matrix on every target — failed on net9.0 and net10.0 while passing on net11.0 and on the polyfill. Rather than trimming the test to hide it, the affected region is asserted explicitly by
TryUnescapeDataString_DestinationSmallerThanLiteralPrefix, which expects the throw on those two frameworks andfalseeverywhere else.Result
API count 1105 → 1109.
Solution clean in Release, Consume clean across all 22 TFMs, tests green on net11.0 (1697), net10.0 (1697), net9.0 (1697), net8.0 (1694), net462 (1659), plus PublicTests, EmbeddedTests, UnsafeTests, NoRefsTests and NoExtrasTests. net7.0 and net5.0 compile but cannot be run on this machine, as those runtimes are not installed.