RFC 3986与BIP21:如何处理查询参数中嵌套URI内的`+`字符
RFC 3986与BIP21:如何处理查询参数中嵌套URI内的
+字符 我完全理解你在开发Dart/Flutter的BIP21 URI解析器时遇到的这个困惑——不同语言的URL处理库对+的表现天差地别,确实容易让人摸不着头脑。咱们一步步拆解这个问题:
1. 为什么不同库对+的处理不一样?
这本质上是RFC 3986标准和历史遗留的表单编码传统之间的冲突:
- 根据RFC 3986,
+属于「子分隔符(sub-delims)」,是为URL各部分的分隔设计的,但如果它出现在参数值里作为数据内容,理论上不需要特殊处理(除非它被用作分隔符)。 - 但早期的
application/x-www-form-urlencoded格式(比如HTML表单提交)为了节省字符,约定用+代替%20(空格的百分编码),这个传统被很多URL库继承了下来——比如Go的url.QueryUnescape、Dart的Uri.parse、Rust的Url::parse都会默认把查询参数里的+转成空格,而严格遵循RFC 3986的库(比如Rust的percent_encoding_rfc3986)则会保留+。
2. 回到BIP21的场景:你的+是数据,不是分隔符
在你给出的例子里,+是pj参数值的一部分,属于Base45字符集(QR码专用),完全是数据内容,没有任何分隔符的作用。这时候如果库把它转成空格,直接就破坏了Base45的完整性——就像你看到的Dart解析结果那样,空格替换+后,整个参数值就失效了。
3. 结论:原始payload里的+确实应该编码成%2B
从严格规范和避免解析歧义的角度来说,这是最优解:
- 按照RFC 3986的要求,如果保留字符(比如
+)要作为数据内容出现在URL的查询参数中,必须进行百分编码。这样就能明确告诉所有解析库:这个+是数据,不是分隔符,也不是空格的替代。 - BIP21并没有修改RFC 3986的URL编码规则,所以应该遵循通用的URL编码规范来生成和解析URI。
比如你提供的原始payload,正确的编码应该是把所有+替换成%2B:
bitcoin:tb1qkfplng7sdhy7c7qjuwa7l7c8ew2w4t575vp4vq?pj=HTTPS%3A%2F%2FPAYJO.IN%2FZ55YEYZ3N0RFJ%23RK1QVWFRUJ48GT052V0VRJF9RR7R8LXYSLWJEGK0JZZ5YYP8WJ87SKH6%2BOH1QYP87E2AVMDKXDTU6R25WCPQ5ZUF02XHNPA65JMD8ZA2W4YRQN6UUWG%2BEX1Z3EKY6Q
这样无论是Dart的Uri.parse还是其他库,解析后都能得到正确的+字符,不会破坏Base45数据。
4. 解析端的兼容建议
如果你的解析器需要兼容一些可能未正确编码+的老旧URI,可以做一个针对性的兼容处理:
- 在解析
pj参数值后,检查内容是否符合Base45的字符集规则; - 如果发现有空格,且空格的位置符合Base45中
+的出现规律,可以尝试把空格转回+。
不过这属于兜底的兼容方案,最佳实践还是要求生成端正确编码+为%2B。
内容来源于stack exchange
相关产品推荐
相关产品推荐

