URI の構成と予約文字
URI は下記のような構成をしています (RFC3986)。

各パートの区切り文字として、次のような予約文字 (reserved characters) が定義されており、例えば query 部分のデータとしてこれらの文字を含む場合は、適切にパーセントエンコーディングしてやる必要があります。
reserved = gen-delims / sub-delims
gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="sub-delims に分類された文字をエンコードすべきかは、作成するシステムによりますが、少なくとも & や = などは、query 文字列のキー&バリューの区切りに使用されるのでパーセントエンコーディングの必要があるでしょう。
一方で、URI の各パート内にそのまま含められる文字として、次のような非予約文字 (nonreserved characters) も定義されています。
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"これらは、URI のどのパートにおいてもそのまま使用できる文字です。
これらの文字もパーセントエンコーディングで表すことはできますが(例: A→%41)、無意味なエンコーディングは避けた方がよいでしょう(RFC3986 においても、そのようなパーセントエンコーディングは行うべきではないとされています)。
JavaScript による URI エンコーディング
JavaScript には、URI のパーセントエンコード(およびデコード)を行うための関数として下記のものが用意されています。
- encodeURI
- URI 全体を表す文字列の中の、マルチバイト文字などをパーセントエンコードします。URI を構成するセパレータ文字などは正しく使用している前提で動作するため、
/や?などの予約文字はエスケープされません。
- URI 全体を表す文字列の中の、マルチバイト文字などをパーセントエンコードします。URI を構成するセパレータ文字などは正しく使用している前提で動作するため、
- decodeURI
encodeURIによってエンコードされた文字列を元に戻します。
- encodeURIComponent
- 下記の文字を除くすべての文字をパーセントエンコードします: アルファベット、10進数字、
-、_、.、!、~、*、(、)。
- 下記の文字を除くすべての文字をパーセントエンコードします: アルファベット、10進数字、
- decodeURIComponent
encodeURIComponentによってエンコードされた文字列を元に戻します。
下記の表は、変換前の文字 (ch) が、encodeURI 関数と encodeURIComponent 関数によってどのようにエンコードされるかを示しています(表の生成に使用したスクリプト)。
| ch | encodeURI(ch) | encodeURIComponent(ch) |
|---|---|---|
あ | %E3%81%82 | %E3%81%82 |
(スペース) | %20 | %20 |
A | A | A |
; | ; | %3B |
, | , | %2C |
/ | / | %2F |
? | ? | %3F |
: | : | %3A |
@ | @ | %40 |
& | & | %26 |
= | = | %3D |
+ | + | %2B |
$ | $ | %24 |
# | # | %23 |
- | - | - |
_ | _ | _ |
. | . | . |
! | ! | ! |
~ | ~ | ~ |
* | * | * |
' | ' | ' |
( | ( | ( |
) | ) | ) |
[ | %5B | %5B |
] | %5D | %5D |
URI を構成する際に、パーセントエンコーディングが必要になる主なケースは、query 部分の値としてユーザ入力値を使用するケースでしょう。
例えば、ユーザが Web サイトのフォーム上で Nuts&Milk のような値を入力した場合、その値を query 部分の値として扱うには、& を %26 にエスケープしなければいけません。
const userInput = 'Nuts&Milk';
console.log(encodeURIComponent(userInput)); //=> 'Nuts%26Milk'
query 部分の文字列として、key1=val1&key2=val2&key3=val3 といった 3 対のキー&バリューで構成したい場合は、val1、val2、val3 のそれぞれの値を encodeURIComponent でエスケープしてから結合しなければならないことに注意してください。
3 対のキー&バリューを & や = で結合した後で、まとめて encodeURIComponent で変換してしまうと、セパレータ文字として扱うべき & や = までエスケープされてしまいます。
JavaScript でユーザ入力を含む query 文字列を構成する場合、下記のようなユーティリティ関数を使用できます。
// キー&バリューのオブジェクトから query 文字列を作成する
function buildQuery(params) {
const esc = encodeURIComponent;
return Object.keys(params).map(k => {
return esc(k) + '=' + esc(params[k]);
}).join('&');
}
ECMAScript 2017 の Object.entries() を使用すると、下記のように記述できます。
function buildQuery(params) {
return Object.entries(params)
.map(pair => pair.map(encodeURIComponent).join('='))
.join('&');
}
const params = {
key1: 'a&b', // ユーザー入力で構成されていると仮定
key2: 'c&d'
};
const query = buildQuery(params);
console.log(query); //=> 'key1=a%26b&key2=c%26d'
Node.js の場合
Node.js では、組み込みの querystring モジュール を使ってクエリ文字列を構築することができます。
const querystring = require('querystring');
const query = querystring.stringify({ key1: 'a&b', key2: 'c&d' });
console.log(query); //=> 'key1=a%26b&key2=c%26d'