日本語

開発者ツール · URL エンコーダーとデコーダー

Plus と %20: アプリケーション/x-www-form-urlencoded の履歴

· 背景

URLエンコーディング htmlフォーム http-standards

クエリ文字列のプラス記号エンコード スペースと URI 構文のパーセント 20 を示すフォーム送信
オリジナル ToolAcre ベクトル イラスト

フォームはスペースを + としてエンコードしますが、URI 標準では %20 となっており、その理由は歴史的なものです。この投稿では、初期の HTML フォームから今日の WHATWG 定義に至るまでの慣例をたどり、なぜそれが消えなかったのかを説明します。

Plus と %20 — フォームと URI でスペースのエンコード方法が異なる理由

GET として送信された HTML フォームは、クエリ文字列内のスペースをプラス記号としてエンコードします。 RFC 3986 に従う URL では、同じスペースが %20 になります。異なる基準に従っているため、どちらも正しいです。スペースを含むフォーム フィールドは、フォーム エンコーディングでは name=value+with+spaces になりますが、RFC 3986 では %20 になります。プラスパーセント 20 の区別は、データに適用される標準をマークします。

フォーム エンコードでは、RFC 1866 (1995)、HTML 2.0 の元のフォーム送信定義の歴史的な慣例として、スペースにプラスを使用します。 GET は、エンコードされたスペースをプラス記号として要求し、リテラル + %2B としてエンコードされたプラスを予約します。このルールは application/x-www-form-urlencoded, にのみ適用され、一般的な URI 構文には適用されません。何十億ものサーバー フレームワークがこの規則に依存するようになりました。両方をテストすると明らかな違いがわかります。フォーム モードではプラスが生成されます。 URI モードでは %20 が生成されます。 URL エンコーダーとデコーダーは、直接比較するための両方のモードを提供します。

初期の HTML フォームと GET 送信 - フォーム エンコーディングの定義方法と + が選択された理由

RFC 1866 (1995) は、スペースがプラスになり、リテラルのプラスが %2B になるフォーム送信を定義しました。これは application/x-www-form-urlencoded. RFC 3986 が一般的な URI 構文に対して指定した %20 にのみ適用されます。 2 つの標準が意図的に共存していました。

RFC 2396 は、以前の標準よりも厳密に文字セットを明確にしました。これは、URI 構造を提供する予約文字と、予約されていない文字をリテラル データとして形式化しました。標準化団体は、進化に応じてブラウザとプロキシの動作を成文化します。 RFC 3986 は、エンコード動作を変更せずに、表記法を明確にするだけで後に登場しました。現在のすべてのブラウザは UTF-8 エンコーディングで標準化されています。送信ボタンを介した HTML フォームは、スペースにプラスを付けた application/x-www-form-urlencoded 形式で送信されます。手動による URI 構築では %20 を使用します。両方の標準を理解することで、統合の予期せぬ事態を防ぐことができます。

RFC 1866 およびそれ以降の HTML 仕様 - ルールが記述された場所と、URI 構文からどのように分岐したか

WHATWG URL Standard は、URLSearchParams.toString() がスペースにプラス記号を付けた application/x-www-form-urlencoded 出力を生成することを指定します。 URL コンストラクターは、R​​FC 3986 に従ってパーセント エンコードされます。ブラウザがスペースを含む URL に移動すると、%20 がエンコードされます。 GET として送信されたフォームはプラスをエンコードします。これらの根本的に異なるツールは、異なる目的を果たします。手動 encodeURIComponent は、スペースに対して %20 (RFC 3986 スタイル) を提供します。同じ URL に送信されたフォームはプラスを送信します。フォーム送信を解析するサーバーはプラスを期待します。 %20 を受信すると、サイレント パラメーター エラーが発生します。

両方をテストすると、依存するサーバー側の前提が明らかになります。 JavaScript URLSearchParams は、安全なフォーム スタイルのエンコーディングの非表示と複雑さを提供します。 URLSearchParams を構築し、エントリを追加し、toString() を呼び出して、適切なプラスを付けて application/x-www-form-urlencoded を取得します。あるいは、encodeURIComponent を使用してクエリ文字列を構築します。 RFC 3986 %20 を取得します。アプローチを決して混合しないでください。手動プラスと encodeURIComponent を含むクエリ文字列では、あいまいさが生じます。受信者は、プラスがスペースを意味するのか、それとも文字通りのプラスを意味するのかを区別できません。標準的なアプローチは一貫して処理します。

現在の URL 標準 — application/x-www-form-urlencoded は独自のルールを持つ別個のシリアライザーとして

JSON API は通常、スペースとしてプラスを拒否し、RFC 3986 に従って %20 を期待します。 plus を送信するクライアントはサイレントに失敗し、パラメータが消失します。両方のエンコーディングを使用して API をテストすると、API がどの標準を受け入れるかがわかります。 JavaScript の URLSearchParams はフォームのエンコードを処理します。 URL エンコーダとデコーダは RFC 3986 %20 を生成します。

HTML フォームの送信では、エンコードが自動的に処理されます。どのルールが適用されるかはサーバー フレームワークによって決まります。 Rails、Django、PHP はすべて、受信したフォーム データ内のプラスを自動的にスペースとして扱います。ただし、同じエンドポイントのクエリ文字列を手動で構築することは非常に重要です。プラスをアップロードすると曖昧さが生じます。仕様への準拠と実際のサーバーの動作は若干異なります。エンドポイントがどの標準を期待しているかを文書化します。両方のエンコード スタイルをテストします。防御的なコードは両方を適切に処理します。

実際の例: 同じフォーム フィールドがクエリ文字列とリクエスト本文として表示されます。ある場所には + があり、別の場所には %20 が付いています。

JavaScript URLSearchParams はフォーム エンコーディングを適用します。スペースは %20 ではなくプラスになります。 new URLSearchParams({q: "hello world"}) は、「q=hello%20world」ではなく、「q=hello+world」を生成します。これは、特に JavaScript に組み込まれた歴史的な application/x-www-form-urlencoded ルールです。ただし、この文字列を生のクエリとして新しい URL に渡すと、プラスはプラスのままになります。 URLSearchParams のみがスペースとしてデコードします。コンストラクターは、見たものに忠実です。プラス記号の違いにより、関数を誤って混合すると一般的なバグが発生します。

URL コンストラクターと encodeURIComponent は別のツールです。 encodeURIComponent は、予約されていない文字、数字、および - _ を除くほとんどすべてをエンコードします。 ! ~ * ' ( )。コンテキストを前提としません。 URL コンストラクターは実際の URL を解析し、コンポーネントごとに WHATWG ルールを適用します。 encodeURIComponent は、「hello/world"」を「hello%2Fworld」に変換します。新しい URL では、スラッシュがパス区切り文字として認識されます。入力は同じですが、出力は異なります。部分を連結して URL を構築する場合は、encodeURIComponent を使用します。完全な URL または部分的な URL には、URLSearchParams または URL コンストラクターを使用します。

修正できない理由 — 何十年にもわたって現在の動作に依存してきたサーバーとクライアント

パーセント エンコーディング ルールは、RFC 1738 (1994) から RFC 2396 (1998) を経て、RFC 3986 (2005) に進化しました。世代ごとに曖昧さが明確になりました。 RFC 1738 は保守的で、初期の Web では文字サポートが制限されていたため、文字を安全ではないものとして扱いました。デプロイメントは UTF-8 で標準化され、実装は一貫したものになりました。その後の標準では、システム全体で安全であることが証明された文字に対する制限が緩和されました。現代のコンセンサス: どこでも UTF-8。標準化団体は下位互換性を厳しく維持しています。修復には世界規模の調整が必要だが、30年も経てば不可能だ。 2 つの標準が意図的に共存しています。

plus と %20 の両方を使用してテストすると、サーバーの前提条件が明らかになります。サーバー ログには、クライアントが送信した内容が表示されます。フォームではプラスを使用します。手動 URL は %20 を使用します。コンテキストに基づいて選択し、API ドキュメントに従ってください。

これでカバーされないもの — multipart/form-data および JSON ボディ

両方のエンコーディングをテストすると、サーバーの動作が明らかになります。 a+b を双方向に送信します。多くの実稼働サーバーはフォームのエンコーディングを想定しています。新しい API では %20 が必要です。選択は受信者の期待によって決まります。 URLSearchParams はフォームのエンコードを処理します。 encodeURIComponent は RFC エンコードを処理します。

エンコード方式を決して組み合わせないでください。 encodeURIComponent %2B でエンコードされて URLSearchParams に渡された値は、%252B として二重エンコードされます。 1 回デコードすると、プラスではなく %2B が生成されます。文字はプラス記号ではなく、リテラルのパーセント 2 6 文字列になります。ビルドプロセスの中間ステップを確認してください。エンコードは値ごとに 1 回だけ行われます。パイプラインでどのエンコード標準が使用されているかを文書化します。プラス、スペース、アンパサンドなどの特殊文字を使用してテストします。

要点: 2 つの標準、どちらもコンテキスト内で正しい — URL エンコーダーとデコーダーが、スペースに %20 を含む RFC 3986 フォームを提供する方法。これにより、どちらを見ているのかがわかります。

プラス対 20 の分割は修正すべきバグではありません。これは、個別の問題を異なる方法で解決する標準の歴史的な成果です。修正するには世界規模の調整が必要ですが、30 年後には不可能です。標準化団体は遡ってウェブを破壊することはありません。 RFC 3986、フォーム ルール、ブラウザ URL の構築には、それぞれ標準と理由があります。 RFC 1866 フォーム エンコーディングと RFC 3986 URI エンコーディングは、異なる層で機能します。標準を意識してエンコードしてください。現実的なペイロードに対してテストします。

コンテキストに応じてエンコードを選択します。フォームは HTML 標準に従って plus を使用します。手動 URI は、RFC 3986 に従って %20 を使用します。 API はどれを期待するかを指定します。ドキュメントに従うか、両方をテストしてください。 URL エンコーダとデコーダは RFC 3986 を示します。フォームのエンコードが必要ですか? URLSearchParams がそれを行います。ツールはエンコーディングを混合しません。標準を理解することで予期せぬ事態を防ぐことができます。レイヤー間のエンコーディングの不一致により、微妙なパラメータの損失、切り捨て、データ破損が発生します。どちらの標準も、その分野では正しいものです。意図的に申請し、文書化してください。