DEBUG.WIKI
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
腾讯云;让你的想法跑起来;轻量服务器 · 建站 / Docker / API;查看产品 →
PanstarCloud:部署应用,接入 AI,云服务器与 AI 网关

Uri.TryUnescapeDataString 缓冲区不足,却抛出 ArgumentOutOfRangeException

目标 Span 短于百分号之前的普通前缀时,.NET 10.0.12 会先越界。按输入长度准备空间,或补上前缀容量检查,保留 false 契约。

调用 Uri.TryUnescapeDataString 时,目标 Span 太短,本来准备处理 false,程序却先抛了 ArgumentOutOfRangeException。若输入在第一个百分号前还有普通文本,应检查这一段前缀是否已经长于目标缓冲区。

.NET 10.0.12 的实现仍存在这条越界路径。API 文档规定,目标放不下结果时应返回 false;这里不是“Try 方法通常也可能抛异常”就能解释过去的正常容量检查行为。

最小触发形态如下:

using System;

Uri.TryUnescapeDataString("Prefix %20".AsSpan(), Span<char>.Empty, out _);

第一个 % 前有七个字符,目标长度却为零。实现尚未判断空间是否足够,就先按前缀长度切目标 Span,因而抛异常。把 %20 移到开头会改变这条路径,所以“另一个输入返回 false”不能排除问题。

大小可控时,按输入长度准备空间

百分号反转义不会让字符数超过输入长度。对于已经通过应用长度限制的小型输入,可以准备同等长度的独立数组,调用后只使用实际写入部分:

using System;

static string DecodeComponent(string input)
{
    ArgumentNullException.ThrowIfNull(input);
    char[] buffer = new char[input.Length];
    if (!Uri.TryUnescapeDataString(input.AsSpan(), buffer, out int written))
    {
        throw new InvalidOperationException("The decode buffer was too small.");
    }
    return new string(buffer, 0, written);
}

调用前仍由业务入口限制输入大小,不能让不受限外部字符串直接决定内存分配。上例不使用大尺寸 stackalloc,也没有池化数组归还和清理问题;若项目采用 ArrayPool,应保留原有租用、异常归还及敏感内容清理规则。

这里的长度理由针对解码后的字符,不是说任意输入都合法。这个 API 不负责证明整条 URI 符合业务约束,也不等同于表单解码;不能据此把 + 当成空格,或在后续路径中再次反转义。

必须保留调用方小缓冲区时,先挡住前缀切片

若调用契约就是“容量不足时返回 false”,可在受影响运行时外层补上缺失的前缀检查:

using System;

static bool TryDecodeComponent(
    ReadOnlySpan<char> input, Span<char> destination, out int written)
{
    int firstPercent = input.IndexOf('%');
    if (firstPercent >= 0 && destination.Length < firstPercent)
    {
        written = 0;
        return false;
    }
    return Uri.TryUnescapeDataString(input, destination, out written);
}

这只补本题已经定位的容量分支,不承诺屏蔽 API 的所有其他异常。其他输入检查、内存重叠约束和错误处理继续遵守原接口。返回 false 后不要读取 destination 中的残留内容,只有成功结果的 written 区间才应交给下游。

正式升级也要看实际发布版本的实现。主分支已经在切片前检查前缀长度,但不能把合入记录直接当成 .NET 9、10 的回补证明;至少在 10.0.12 的正式源码中,这个检查仍缺失。若目标运行时有新的维护版本,用其正式源码和同一输入确认,不为了绕过这一个分支直接切换整套运行时大版本。

复查时覆盖百分号在开头、在普通前缀之后、没有百分号、空输入,以及恰好够用和不足的目标空间。原异常应消失,容量不足按约定返回 false,足够空间的结果与预期字符一致。保留新增包装函数及调用点的版本差异;将来移除临时检查前,同样复核部署机器的实际运行时,不能只看编译所用 SDK。