URL エンコード / デコード

URL のパーセントエンコードとデコード。対象を成分か URL 全体かで選べ、URL の分解も表示します。

クエリパラメータ 1 つのような、単一の値向けです。/ ? : @ & = + もエスケープします。そこではこれらが構造ではなくデータだからです。

入力
出力

ここに結果が表示されます

パーセントエンコードは何のためか

URL に含めてよいのは小さな文字の集合だけです: A–Z と a–z の文字、数字、- . _ ~ のような一握りの記号、そしてアドレスに形を与える予約文字の集合 — / ? # & = : @ など数個です。それ以外は、空白からヘブライ語の単語、絵文字まで、間接的に表さねばなりません。パーセントエンコードがその表現です: 文字を UTF-8 バイトに変換し、各バイトをパーセント記号に続く 2 桁の 16 進数として書きます。空白は %20 に、א は %D7%90 になります。

予約文字が興味深い場合です。URL で合法ですが、構造的な何かを意味します: ? はクエリを始め、& はパラメータを分け、= はキーを値から分けます。それらの文字の一つが構造の一部ではなくあなたのデータの一部であるとき、それはエンコードされねばなりません — さもないと相手側のパーサーがそれを区切りとして読み、あなたの値を誤った場所で分けてしまいます。

コンポーネントか URL 全体か — リンクを壊す区別

これは URL のバグの単一で最もよくある源で、だからこのツールは推測ではなく範囲を選ぶよう求めます。

  • コンポーネント範囲は区切りもエスケープします: / ? : @ & = + $ , と # です。より大きな URL の中に置かれる一片のデータ — 検索語、リダイレクト先、トークン — に使ってください。
  • URL 全体の範囲はそれらの区切りをそのままにし、空白や非 ASCII テキストのような本当に不正なものだけをエスケープします。既に正しく構造化されていて、ただ整える必要のある完全なアドレスがあるときに使ってください。

どちらの向きでも逆にすると何かが壊れます。URL 全体をコンポーネント範囲でエンコードすると、すべてのスラッシュと疑問符が %2F と %3F になり、長く使えない一つの文字列を生みます。単一の値を URL 全体の範囲でエンコードすると、その中のアンパサンドが区切りとして生き残るので、「cats & dogs」の検索が黙って二つのパラメータになり、値の後半が消えます。

// 値が区切りを含むので、エスケープされねばなりません:
const q = 'cats & dogs'
`/search?q=${encodeURIComponent(q)}`  // /search?q=cats%20%26%20dogs
`/search?q=${encodeURI(q)}`           // /search?q=cats%20&%20dogs  ✗

// さらに良いのは、プラットフォームに組み立てさせること:
const url = new URL('https://example.com/search')
url.searchParams.set('q', 'cats & dogs')

その最後のやり方は習慣として採り入れる価値があります。URL と URLSearchParams はすべてのブラウザと Node に組み込まれていて、各部分に正しいエンコードを適用してくれます — 毎回手で正しくやるのではなく、決定をまるごと取り除きます。

なぜ空白は時々 + で時々 %20 なのか

二つの異なる仕様が働いていて、ちょうど一つの文字について食い違います。URL 全般を司る RFC 3986 は空白を %20 としてエンコードします。古い application/x-www-form-urlencoded 形式 — HTML フォームが送信するもので、だからほとんどのクエリ文字列がそう見えるもの — は空白を + としてエンコードします。

罠は、decodeURIComponent が最初の規則しか実装しないことです。hello+world を渡すと hello+world が返り、プラスもそのまま — エラーも警告もなく、ただ微妙に誤った値です。だからこのツールは「+ を空白として扱う」の選択肢を提供し、その選択肢がオフで入力にプラスが含まれるときに指摘します。

ツールの下の内訳表を動かす URLSearchParams は、フォームの規則に従い + を空白に変換することに注意してください。だから同じクエリ文字列が、手を伸ばす API によって異なるふうにデコードされえます — 意図的に、そして両方の場合で正しく、です。

覚えておく価値のある一つの帰結: プラスが本当にあなたのデータの一部なら — 電話番号、フィルター式 — それは %2B としてエンコードされねばなりません。クエリ文字列の中の文字どおりの + は、良くても曖昧で、ほとんどのパーサーに空白として読まれます。

URL を読む

絶対 URL を貼り付けると、このツールはそれを各部分に分けます。エラーメッセージと文書はその語彙を前提とするので、それぞれが何と呼ばれるかを知っておく価値があります:

  • スキーム — https、mailto、ftp。コロンの前のすべてで、残りがどう解釈されるかを決めるものです。
  • ユーザー情報 — ホストの前の任意の user:password。今も合法で、今も悪い考えです: すべてのリクエストとともに伝わり、ログ・ブラウザ履歴・リファラーヘッダーに残ります。
  • ホスト — ドメイン名か IP アドレス。その後のすべてはネットワークではなくそのサーバーが扱います。
  • ポート — たいてい省かれます。https の 443 と http の 80 が暗黙だからです。
  • オリジン — スキーム・ホスト・ポートを合わせたもの。これがブラウザのセキュリティが築かれる単位です: 同一オリジンポリシー・CORS・Cookie の範囲はすべて、パスではなくオリジンを比べます。
  • パス — ホストの後、? や # の前の部分。
  • クエリ — ? の後の、& で分けられたキー/値の対。
  • フラグメント — # の後のすべて。独特なことに、これは決してサーバーに送られません。完全にブラウザが扱います。

フラグメントの詳細は聞こえるより重要です。決してブラウザを離れないので、# の後に置いたものはサーバーのログに見えません — だから一部のシングルページアプリが歴史的にルーティングにそれを使い、決して届かなかったリクエストをデバッグするときに見る場所ではない理由です。

エンコードがしないことについて最後の注意。パーセントエンコードはテキストを URL の中で安全に運べるようにします。それはサニタイズではなく、セキュリティの制御でもありません。値をエンコードしても、HTML・SQL・シェルコマンドに差し込んで安全になるわけではなく、信頼できない入力をデコードすると、エンコード形式が隠していた文字 — パス区切り、ヌルバイト — が現れることがあります。値が何であるかを、それがどう運ばれるかのエンコードとは別に検証してください。

入力したアドレスは、常に送られるアドレスとは限らない

URL はどこかへ行く前に正規化され、その書き換えは静かです。このツールは入力と異なるときはいつでもその結果を示します。その文字列 — 入力したものではなく — がサーバーに届き、ログに現れるものだからです:

HTTPS://Example.COM:443\a\b     入力
https://example.com/a/b         送信

そこで四つの別々の規則が発動しました。スキームとホストは小文字化されます。どちらも大文字小文字を区別しないからです。ポートは 443 が https の既定なので落とされます。バックスラッシュは前向きスラッシュになり、これは Windows 形式のパスを書く人を驚かせる互換性の規則です。そして空のパスは単一のスラッシュになっていたでしょう。どれもエラーではありませんが、二つの URL の等価性を比べたり、一つを許可リストに照合したりするなら、正規化された形を比べなければ、誤った答えを得ます。

非 ASCII 文字を含むホスト名も書き換えられ、xn-- で始まる ASCII 形式 — punycode と呼ばれます — になります。このツールは両方向を示します: 読める名前と、実際に伝わるものです。これは飛ばさず見る価値があります。二つの異なる Unicode 名が画面で同一に見えながら — ラテン文字の「a」とキリル文字の「а」は別の文字です — まったく異なる punycode を生むからです。ASCII 形式を比べることが、そうした対を見分ける唯一の当てになる方法です。

決して正規化されないものが一つ: 繰り返されるクエリパラメータです。?tag=a&tag=b と書くのは完全に合法で、標準はそれが何を意味するか言わないので、あらゆるスタックが独自の答えを選びました。PHP は最後の値を保ち、Express はそれらを配列に集め、多くのフレームワークと URLSearchParams.get は最初を取ります。このツールは、注釈なしに二度並べるのではなく繰り返されるキーに印を付けます。結果として生じるバグ — あるサービスで動き別のサービスで消える値 — は、読んで見つけるのが本当に難しいからです。

よくある質問

コンポーネント範囲と URL 全体の範囲の違いは何ですか?
コンポーネント範囲は区切り / ? : @ & = + もエスケープし、URL の中に置かれる単一の値に正しいものです。URL 全体の範囲はそれらをそのままにします。そこではアドレスの部分を分けるからです。URL 全体にコンポーネント範囲を使うと、すべてのスラッシュが %2F になり、使えない一つの文字列を生みます。
デコードしたテキストにまだ + が含まれるのはなぜですか?
decodeURIComponent が RFC 3986 に従い、そこでは + がただのプラスだからです。フォーム送信とほとんどのクエリ文字列はフォームエンコードを使い、そこでは + が空白を意味します。テキストがどちらかから来たなら「+ を空白として扱う」をオンにしてください。
本当にプラスであるプラスをどうエンコードしますか?
%2B と書きます。クエリ文字列の文字どおりの + はほとんどのパーサーに空白として読まれるので、本当にあなたのデータの一部であるプラス — 例えば電話番号の中 — はエスケープされねばなりません。
URL 全体をエンコードすべきですか、それとも部分だけですか?
部分だけ、できれば手ではなく: URL と URLSearchParams でアドレスを組み立ててください。各コンポーネントに正しいエンコードを適用します。組み立てた URL を後からエンコードすることが、ほとんどの二重エンコードのバグの源です。
内訳が、貼り付けたのと違う URL を示すのはなぜですか?
それが送られるものだからです。URL は使う前に正規化されます: スキームとホストは小文字化され、既定のポートは落とされ、バックスラッシュはスラッシュに、空のパスはスラッシュに、非 ASCII のホストは punycode になります。URL を比べたり許可リストに照合したりするなら、生のテキストではなくこれらの正規化された形を比べてください。
example.com/path に内訳が現れないのはなぜですか?
絶対 URL ではないからです — スキームがないので、識別すべきホストがありません。このツールはこちらで推測しません: example.com:8080 は既に、スキームが example.com でパスが 8080 の有効な絶対 URL なので、黙って https:// を前置すると、妥当に見えて誤った内訳を生みえます。自分でスキームを加えれば、部分が現れます。
二重エンコードとは何ですか?
既にエンコードされた値をエンコードすることで、%20 が %2520 になります — パーセント記号自体がエスケープされます。たいてい、ページに文字どおりの %20 の並びが現れる形で表れます。一度デコードして、まだエスケープが見えるか確認してください。見えれば、二度エンコードされたのです。
値をエンコードすると安全になりますか?
いいえ。パーセントエンコードは安全性ではなく転送についてのものです。エンコードされた値は依然それが何であったかそのもので、HTML・SQL・シェルに届く前に、同じ検証と、文脈に応じた同じエスケープが必要です。
フラグメントがサーバーに送られないのはなぜですか?
設計によるものです: # の後のすべてはブラウザだけが扱い、決してリクエストに現れません。だからサーバーのログに見えず、歴史的にクライアント側のルーティングに使われた理由です。
私が貼り付けたものはどこかに送られますか?
いいえ。エンコード・デコード・URL の内訳はすべて、ブラウザ自身の組み込み関数を使ってお使いの端末で動きます。貼り付けた内容がそれを離れることはありません。