概要
いくつかの言語でパーセントエンコーディングの処理においてスペースの置き換え先がプラス記号か %20
であるかどうかを調べました。
URL のクエリ文字列や application/x-www-form-urlencoded
のボディを求めるためとき、HTML5 や URL Standard の仕様ではスペースはプラス記号 (+) に変換されることが求められます。
一方でパーセントエンコーディングの明確な仕様がなかったことから、HTTP サーバーは +
と %20
の両方をスペースとして扱われます。サーバーサイドの言語では application/x-www-form-urlencoded
のためのエンコーディングメソッドもしくはオプションが提供されず、RFC 3986 の仕様にもとづいたエンコーディングメソッドで代用することがあります。
多くの場合、スペースの置き換え先が (+
) かパーセントエンコーディング (%20
) か意識する必要はありませんが、OAuth の場合、問題になります。
OAuth 1.0 の場合、スペースに対してパーセントエンコーディング (RFC 3986) が要求されるために、プラス記号にエンコーディングする方法を採用したライブラリではエラーになります。Twitter はパーセントエンコーディングについての解説記事を公開しています。
JavaScript
URLSearchParams
は URL Standard にもとづきます。スペースはプラス記号に置き換わります。
const params = new URLSearchParams();
params.append("msg", "hello world");
console.log("msg=hello+world" === params.toString());
URLSearchParams
は %20
もスペースとして扱います。解析した結果は次のとおりです。
const params2 = new URLSearchParams("msg=hello+world");
console.log("hello world" === params2.get("msg"));
const params3 = new URLSearchParams("msg=hello%20world");
console.log("hello world" === params3.get("msg"));
encodeURIComponent
の場合、スペースを %20
に置き換えます。
console.log("hello%20world" === encodeURIComponent("hello world"));
スペースをプラス記号に置き換えたいのであれば、replace
の呼び出しを追加します。
const ret = encodeURIComponent("hello world").replace(/%20/g, '+')
console.log("hello+world" === ret);
decodeURIComponent
は %20
をスペースに置き換えますが、プラス記号は置き換えません。
console.log("hello world" === decodeURIComponent("hello%20world"));
console.log("hello+world" === decodeURIComponent("hello+world"));
Node.js
Node.js v7.0 からサポートされるようになった URLSearchParams
を優先的に使うほうがよいでしょう。v7.0 以前の以前の方法を示します。
標準の querystring.stringify
モジュールはスペースを %20
に置き換えます。
const querystring = require('querystring');
const ret = querystring.stringify({ msg: "hello world" });
console.log("msg=hello%20world" === ret);
querystring.parse
は +
と %20
の両方をスペースに置き換えます。
const querystring = require("querystring");
console.log("hello world" === querystring.parse("msg=hello+world")["msg"]);
console.log("hello world" === querystring.parse("msg=hello%20world")["msg"]);
RFC 3986 の仕様にもとづいたエンコーディングが必要であれば、ljharb/qs
を導入します。
const qs = require("qs");
console.log("msg=hello%20world" === qs.stringify({ msg: "hello world" }));
console.log("hello world" === qs.parse("msg=hello+world")["msg"]);
console.log("hello world" === qs.parse("msg=hello%20world")["msg"]);
Python 3
urllib
を使います。urlencode
はデフォルトでスペースを +
に置き換えます。
>>> from urllib.parse import urlencode
>>> urlencode({"msg": "hello world"})
'msg=hello+world'
quote_via=quote
を指定すれば、スペースは %20
に置き換わります。
>>> from urllib.parse import urlencode, quote
>>> urlencode({"msg": "hello world"}, quote_via=quote)
'msg=hello%20world'
parse_qs
と parse_qsl
の両方とも +
と %20
をスペースに置き換えます。
>>> from urllib.parse import parse_qs
>>> parse_qs("msg=hello+world")
{'msg': ['hello world']}
>>> parse_qs("msg=hello%20world")
{'msg': ['hello world']}
Go
net/url
モジュールを使います。url.Values
型の Encode
はスペースを +
に置き換えます。ParseQuery
は +
と %20
をスペースに置き換えます。
package main
import (
"fmt"
"net/url"
)
func main() {
v := url.Values{}
v.Set("msg", "hello world")
fmt.Println(v.Encode())
// msg=hello+world
m, _ := url.ParseQuery("msg=hello+world")
fmt.Println(m["msg"][0])
// hello world
m2, _ := url.ParseQuery("msg=hello%20world")
fmt.Println(m2["msg"][0])
}
QueryEscape
がスペースを +
に変換するのに対して PathEscape
は %20
に変換します。さらに PathUnescape
は +
をスペースに変換しません。
func main() {
fmt.Println("QueryEscape")
fmt.Println(url.QueryEscape("hello world"))
// hello+world
fmt.Println(url.QueryUnescape("hello+world"))
// hello world
fmt.Println(url.QueryUnescape("hello%20world"))
// hello world
fmt.Println()
fmt.Println("PathEscape")
fmt.Println(url.PathEscape("hello world"))
// hello%20world
fmt.Println(url.PathUnescape("hello+world"))
// hello+world
fmt.Println(url.PathUnescape("hello%20world"))
// hello world
}