简体中文

开发者工具 · URL 编码器和解码器

URIError: URI malformed — 为什么decodeURIComponent会抛出异常以及如何修复它

· 工作原理

url 编码 JavaScript 错误处理

百分号后跟无效的十六进制数字会导致 URIError 异常
原始 ToolAcre 矢量图

当百分号后面没有跟两个十六进制数字,或者解码的字节无效 UTF-8 时,decodeURIComponent 会抛出异常。这篇文章展示了触发它的输入以及如何防御性解码。

崩溃的百分号——为什么“100% off”破坏了decodeURIComponent

表格收集折扣代码“100% off”。 JavaScript 将其传递给 URL 解码器中的decodeURIComponent。该函数抛出 URIError: URI malformed。百分号后面没有两个十六进制数字。这完全违反了百分比编码规则。解码URIComponent期望每个%开始一个三元组,如%20或%C3。单独的 % 是一个语法错误,它会立即停止执行并引发错误。

安全地捕获此错误可以完全防止应用程序崩溃。 URL 来自用户输入、重定向、二维码和电子邮件。打字错误经常发生。带有 URIError 的崩溃报告会告诉您要快速调查的位置。防御性解码使应用程序保持运行,并且错误日志对于调试变得有用。

两种失败 — 格式错误的十六进制转义和无效的 UTF-8 字节序列

decodeURIComponent 恰好在两种情况下抛出。第一:转义序列格式错误。后面不跟两个十六进制数字的百分比(0-9、A-F、a-f)。示例:%ZZ、%2, %2g。第二:有效的三元组(如 %E9)解码为无效的 UTF-8 字节。首先是格式错误。二是语义错误。两者都会立即抛出并停止执行。

UTF-8 对字节序列有严格的规则。字节 0x80–0xFF 仅出现在多字节序列中。单个 %E9 不能单独有效 UTF-8。该孤立字节会触发错误。格式错误很明显。语义错误很微妙,但同样真实。这两种情况都需要在生产代码中进行 try/catch 处理。

传统单字节编码 - 当 %E9 单独抛出但 %C3%A9 存活时

混乱源于网络标准的历史。旧页面使用拉丁语-1 而不是UTF-8。在拉丁语中-1, %E9 代表é。现代浏览器专门使用 UTF-8。 UTF-8 将 é 编码为 %C3%A9。现代解码器期望 UTF-8 并拒绝 %E9 作为格式错误。这是正确的行为。该错误表明源数据存在问题。

现代共识:UTF-8 无处不在。 URL 标准指定 UTF-8。当前所有浏览器都使用 UTF-8。如果您在旧系统中遇到 %E9,请捕获错误并回退到原始字符串。不要在现代代码中解码为 Latin-1 。调查数据的来源。

三个输入,三个错误消息 - 引擎在同一断弦上有何不同

浏览器引擎始终拒绝格式错误的输入,但以不同的方式拒绝单词错误。 Chrome 报告“URI 格式错误”。 Firefox 报告“格式错误的 URI 序列”。 Safari 报告“无法将未定义的对象转换为对象”。所有三个引擎都拒绝相同的输入。不同引擎或版本之间的确切消息措辞并未标准化。永远不要依赖错误文本来指导代码逻辑。

切勿对程序决策的错误消息进行字符串匹配。始终按类型捕获 URIError。该decodeUrl函数包装decodeURIComponent并提供一致的代码INVALID_PERCENT_ENCODING。这命名了确切的问题位置。它可以跨运行时运行,因为它不依赖于引擎措辞的变化。这种方法更加可靠和可维护。

安全读取异常 — try/catch, 验证和回退模式

最简单的防御模式:将decodeURIComponent 包装在try/catch. 中。如果抛出异常,请使用原始字符串或替换字符。这可以防止格式错误的输入崩溃。对于查询值,显示 URL 编码形式。对于面向用户的文本,插入替换字符。这可以防止不良输入破坏应用程序并保持稳定性。

使用正则表达式进行预验证,以提高速度和安全性。解码前检查输入是否仅包含有效的 %XX 三元组。模式 /%[0-9A-Fa-f]{2}/g 捕获有效的转义;任何不匹配的内容都是无效的。对于明显的垃圾,格式错误会快速失败。 UTF-8 错误仍然需要 try/catch. 这一起提供了针对错误的全面防御保护。

无声失败隐藏着错误——为什么盲解码与损坏的编码一样危险

微妙的风险:解码器不会抛出错误,而是默默地产生错误的文本。使用已弃用的 unescape 的旧代码会在内存中留下无效的 UTF-8 。文本在屏幕上看起来很好,直到到达严格验证 UTF-8 的系统。现代代码会抛出异常,而不是默默地损坏。异常比向下游传播的无声数据损坏更清晰、更安全。

假设用户输入格式错误。始终对通话进行换行。使用原始输入记录错误以进行调试。永远不要假设每个 % 都是有效的。拼写错误和截断会造成不完整的转义。视为数据错误,而不是逻辑错误。防御性代码能够优雅地承受错误的输入并保持系统的可靠性。

真正的工具会跳过什么 — 服务器端框架行为和错误恢复

服务器框架比浏览器更宽松地处理格式错误的编码。 Ruby、Python 和 PHP 提供了处理 URL 中无效转义的配置。有些会自动替换替换字符。其他人默默地丢弃字节。有些会像 JavaScript 一样抛出异常。实际行为因开发人员选择的框架和配置设置而异。

本文仅介绍浏览器 JavaScript 行为。如果值从服务器 API 到达,则服务器在发送之前已解码或跳过错误。服务器可以比客户端更宽容。编写 API 合约时,指定值是原始值还是预解码值。 URL 查询字符串应以百分比编码形式到达; JSON 可以预先解码。

尽早验证 — 使用 URL 编码器和解码器首先检查可疑字符串

在将可疑 URL 传递给decodeURIComponent 之前,请粘贴到 URL 编码器和解码器中。该工具显示精确的编码、发现格式错误的转义并解释错误,而不会导致应用程序崩溃。使用 %ZZ、%E9 和 100% 进行测试以查看不同的故障及其确切的错误消息。这需要几秒钟的时间并建立信心。

尽早验证,优雅地捕获错误并记录损坏的内容。防御性解码器和测试工具使应用程序保持运行和可调试。 URL 编码器和解码器将“格式错误的 URI”转换为您可以立即使用的可操作信息。将此模式应用于您自己的解码器,以提高生产环境中的弹性和可维护性。