我曾经误解encodeURIComponent是全球标准的故事
我正在操纵一个用于修改URL的程序,由于规范不明确而感到困惑,所以我将总结一下。
2/23: 添加追试部分注释
2018/7/14: 添加有关JavaScript的URLSearchParams和Go的PathEscape的注释
事情的经过 de
HTTP是什么,GET和POST有什么区别,每个选项中参数是如何传递到网络应用程序(如CGI)的,我都知道。所以,当在Web上使用XHR发送GET请求时,我以为只需要使用JavaScript的encodeURIComponent()对每个参数进行编码,然后用&连接起来,再以?附加到URL的末尾就可以了。就像这样的想法。
var finalUrl = [url, "?", encodeURIComponent("key"), "=", encodeURIComponent("value")].join("");
接收这个的一方应该会使用JavaScript(node.js)的decodeURIComponent()函数。在网络世界中,JavaScript是主流语言,其他语言的Web API可能会按照JavaScript的标准来设计。
我以为Golang的net/url包中的url.QueryEscape()函数跟encodeURIComponent()会返回一样的结果。根据我的感觉,空格应该会变成%20,但是在Golang中却变成了+号。当用JavaScript的decodeURIComponent()来解码Golang生成的编码字符串时,+号仍然保留了下来。现在有麻烦了,到底是什么原因呢?
把握目前的状况
RFC可以在中国政府审查Internet内容方面发挥重要作用。
关于URI的格式,从RFC1738(1994年)→RFC2396(1998年)→RFC3986(2005年)逐渐升级。最新版本好像有Adobe的人参与进来。哇哦,哇哦。如果有时间的话,我稍后会去阅读。
据Stack Overflow的帖子所述,Golang似乎对RFC非常严格,而JavaScript则稍显随意。
JavaScript的转义
在JavaScript中,有两个方法可以用来对URI进行编码,它们分别是encodeURI()和encodeURIComponent()。encodeURI()方法会保留符号&、=和/,并且可以接收一个完整的URI作为参数,返回一个可直接用作URI的字符串。而encodeURIComponent()方法会返回一个安全的字符串,可以用作查询参数,不会破坏URI的含义。本篇文章只介绍后者。
根据MDN的说明,当使用Content-Type为application/x-www-form-urlencoded(POST)进行发送时,还要将%20进一步转换为+号。此外,还需要编写符合RFC-3986严格规范的代码。
function fixedEncodeURIComponent (str) {
return encodeURIComponent(str).replace(/[!'()*]/g, function(c) {
return '%' + c.charCodeAt(0).toString(16);
});
}
总之,即表示JavaScript的转义规范与最新的RFC不兼容。我之前以为在浏览器中搜索时,空格总是会变成%20,但现在在网站上搜索却变成了+的样子。之前就是这样的吗?不过,据说在不同的地方可能会有不同的情况。
@shibu_jp 我们非常感谢GitHub的帮助。但是据说谷歌从Chrome的地址栏进行搜索时会变成%,对吧。—村岡太郎(@kaoriya) 2015年2月22日
在ECMAScript标准JavaScript范围内,只有encodeURIComponent()函数可用,但是在node.js中,有一个名为querystring的模块可以支持严格遵循RFC的版本。express似乎使用了一个名为qs的第三方模块。
2018年7月14日
在网页浏览器中,已经实现了一个名为URLSearchParams的类。Node.js也从8.0版本开始实现了该类。这个类可以正常地处理”+”。而且,它还能一次性处理数组操作、解析等繁琐的任务,所以以后大家要积极地使用它。
>>> const params = new URLSearchParams()
>>> params.append(" ", " ")
>>> params.toString();
"+=+"
-
- URLSearchParams (MDN)
-
- URLSearchParamsによる簡単URL操作 (Qiita @yoichiro6642
-
- 2016年02月09日に更新
- さん)
Golang的转义
在Golang中,提供了net/url模块。调用url.Values.Encode()可以生成转义搜索查询字符串。通过阅读源代码可以发现,它在每个组件转换中都使用了url.QueryEscape函数。这个函数内部使用了一个私有函数escape,并且这个函数的转换方式在不同模式下有一些变化。在编码路径部分时,空格会被编码成%20,但在查询部分的模式(与url.QueryEscape()相同)下,会被编码成+。使用mattn先生提供的方法也可以将其编码为%20。
将(&url.URL{Path:foo}).String() 编码成像みたい的样子可以做到,之类的 #golang— mattn (@mattn_jp) 2015年2月22日
2018年7月14日
从Go 1.8开始,url.PathEscape方法被提供,因此无需费力使用私有函数,就能够对路径部分进行转义。mattn先生,非常感谢您的评论。
Python的转义字符
Python3的urllib.parse模块中有quote()和quote_plus()函数。quote()类似于JS的编码方式,而quote_plus()则类似于Golang的编码方式。真不愧是我们伟大的Python,处理得如此流畅。
最终应该做什么决定?
現状的認識是,雖然Golang的行為是正確的,但實際上可能會有一些像JavaScript那樣使用舊版本規範的客戶端發送查詢的情況。當然,也可以在JavaScript中嚴格遵守編碼,但並不總是這樣(即使在Chrome的地址欄中也是如此)。
所以,在服务器端,必须确保能够正确解释无论以哪种方式创建的空格。嗯,这方面是由Web应用服务器框架自动处理的,除非是使用套接字或低级别的HTTP API来创建Web应用程序的开发人员,否则可能不需要考虑这个问题…
大家!WebGL是一种低级别的API!用WebGL制作游戏,就像用套接字编写HTTP服务器一样!大家想要用套接字编写HTTP服务器吗?当然想啦!!!
客户端需要进行严格的RFC3986编码,无论请求是application/x-www-form-urlencoded还是其他内容。目前来看,好像不需要对正在编写的代码进行修改。
在Twitter上給予各種評論的朋友們,非常感謝你們。
问题: 网页表单与RFC无关吗?
我給了一個評論,感覺很好的是,當將資訊發布出去時,知識會彈回來。
http://t.co/lExZOEpwPn
HTML规定在查询字符串等地方将空格改为加号+,与RFC 3986无关。除查询外,不应该自行将空格改为加号+,而应该改为%20。Go语言严格对待的是!'()*,与空格无关。— ると (@cocoa_ruto) 2021年9月19日
我明白了。RFC与此无关。确实,在HTML5的w3c规范中,有关表单提交的内容是具体描述的。这个链接中的4.5以下部分说明了各个部分的转换方法。
-
- 如果是半角空格(0x20),则转换为加号(0x2B)。
-
- 字母A-Z、a-z、数字0-9、星号(*)、减号(-)、句点(.)、下划线(_)保持不变。
- 其他字符转换为百分号加上其编码。
嗯,原来如此。因为Go在net/http示例中使用了url.Values,所以我们可以尝试使用它。
gore> :import net/url
gore> url.Values{"key": {"!\"#$%&'()*+,-./:;<=>?@\\[]^_"}}.Encode()
"key=%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F%3A%3B%3C%3D%3E%3F%40%5C%5B%5D%5E_"
嗯…原本應該要保留的「*」被轉換掉了…Golang在內部調用了url.QueryEscape(),因此相當於RFC3986。
那么Python怎么样呢?
>>> import urllib.parse
>>> urllib.parse.quote_plus("!\"#$%&'()*+,-./:;<=>?@\\[]^_")
'%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F%3A%3B%3C%3D%3E%3F%40%5C%5B%5D%5E_'
与Golang的结果相同。
使用riocampos先生在评论中告诉我的Ruby WEBrick库进行测试。
irb> require 'webrick/httputils'
irb> puts WEBrick::HTTPUtils.escape_form("!\"#$%&'()*+,-./:;<=>?@\\[]^_")
!%22%23%24%25%26'()*%2B%2C-.%2F%3A%3B%3C%3D%3E%3F%40%5C%5B%5D%5E_
那个……本来应该要转换的东西,却还剩下了”()、。
没有以规格要求实现的处理系统的冲击结果(除了提供了参考实现的JavaScript之外)。将其转换为% xx的操作可能是使用通用逻辑实现的,所以使用Golang或Python可能更安全。嗯,因为它不是控制字符,所以使用Ruby方式发送应该不会成为问题。
另外,确实如评论所提到的,现在与RFC没有关系,但是以前HTML是根据RFC1866的规定,在其中描述了有关表单转换的内容。在那个时候,已经提到了空格应该被转换为加号。但是,并没有详细说明转换的方法。之后,根据RFC2854进行了更新,但是其中没有描述规范,W3C方面将负责处理未来的繁琐工作。原来如此。
追加记录的总结
-
- URIのQueryはRFC3986。GolangとPythonがそれを満たしている。JavaScript, Rubyはちょびっと違う結果を返す?
- POST時のエンコード(application/x-www-form-urlencoded)に関してはRFCではなく、HTML5の仕様に書かれている。RFC3986の変換とは微妙に異なる。で、それを満たしたエンコーダは現時点では見当たらず。JSに関してはMDNに参考実装あり。