インターネット技術タスクフォース (IETF) M. Nottingham
Request for Comments: 9651 Cloudflare
廃止: 8941 P-H. Kamp
カテゴリ: 標準トラック The Varnish Cache Project
ISSN: 2070-1721 2024年9月

HTTPのための構造化フィールド値


概要

本書は、HTTPヘッダおよびトレイラ("Structured Fields"、"Structured Headers"、または "Structured Trailers" と呼ばれる)の定義と取り扱いを、より簡単かつ安全にすることを目的とした一連のデータ型と関連アルゴリズムについて説明します。新しいHTTPフィールドの仕様での使用を想定しています。

本書はRFC 8941を廃止します。

このメモの状態

本書はインターネット標準トラック文書です。

本書はインターネット技術タスクフォース(IETF)の成果物です。IETFコミュニティのコンセンサスを反映しています。公開審査を受け、インターネット技術審議グループ(IESG)によって出版が承認されました。インターネット標準に関する詳細はRFC 7841のセクション2を参照してください。

本書の現状、訂正情報、及びフィードバックの提供方法に関する情報はhttps://www.rfc-editor.org/info/rfc9651で入手できます。

Copyright Notice

Copyright (c) 2024 IETF Trust and the persons identified as the document authors. All rights reserved.

This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.


1. 導入

新しいHTTPヘッダ(およびトレイラ)フィールドの構文を特定することは骨の折れる作業です。Section 16.3.2 of [HTTP] に示される指針があっても、将来のフィールド作成者には多くの判断と落とし穴が存在します。

フィールドが定義されると、それぞれのフィールド値が共通に見える構文をわずかに異なる方法で扱うため、しばしば専用のパーサやシリアライザを作成する必要があります。

本書は、これらの問題に対処するために、新しいHTTPフィールド値の定義で使用する共通のデータ構造群を導入します。特に、それらの抽象的な共通モデルと、そのモデルをHTTPのヘッダおよびトレイラフィールド内で表現するための具体的なシリアライゼーションを定義します([HTTP]参照)。

"Structured Header" または "Structured Trailer" として定義されたHTTPフィールド(両方で使用可能な場合は "Structured Field")は、本仕様で定義された型を用いてその構文と基本的取り扱い規則を定義します。これにより、仕様作成者による定義と実装者による取扱いが簡素化されます。

さらに、将来のHTTPのバージョンはこれらの構造の抽象モデルの別のシリアライゼーションを定義でき、モデルを使用するフィールドを再定義することなくより効率的に送信できるようになります。

本仕様の目的は既存のHTTPフィールドの構文を再定義することではない点に注意してください;ここで説明する仕組みは、明示的にそれらを採用するフィールドでのみ使用することを意図しています。

Section 2 では、Structured Field をどのように指定するかを説明します。

Section 3 は、Structured Fields で使用できる複数の抽象データ型を定義します。

これらの抽象型は、Section 4 に記載されたアルゴリズムを用いて、HTTPフィールド値へシリアライズおよびHTTPフィールド値からのパースが可能です。

1.1. 意図的に厳格な処理

本仕様は、逐次的なアルゴリズムによって厳格なパースおよびシリアライズ動作を意図的に定義しており、定義される唯一のエラー処理は操作全体を完全に失敗させることです。

これは忠実な実装と良好な相互運用性を促進するよう設計されています。したがって、入力に対して寛容になろうとする実装は、類似する(しかし微妙に異なる可能性のある)回避策を他の実装にも実装させる圧力を生み、相互運用性を悪化させる可能性があります。

言い換えれば、厳格な処理は本仕様の意図的な特徴であり、非準拠の入力を生産者の側で早期に発見・修正させ、さもなければ発生し得る相互運用性やセキュリティの問題を回避します。

この厳格さの結果として、複数の当事者(例えば、中継や送信側の異なるコンポーネント)がフィールドを追記する場合、ある当事者の値に誤りがあるとフィールド全体の解析が失敗する可能性が高いことに注意してください。

1.2. 記法上の慣例

本書中のキーワード "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", および "OPTIONAL" は、すべて大文字で現れる場合に限り BCP 14[RFC2119] および [RFC8174] に記載された通りに解釈されます。

本書は、文字や対応するASCIIバイトを指定するために [RFC5234] の VCHAR、SP、DIGIT、ALPHA、および DQUOTE のルールを使用します。また同様の目的で [HTTP] の tchar および OWS のルールも使用します。

本書はパースおよびシリアライズの動作を指定するためにアルゴリズムを使用します。HTTPフィールドからのパース時には、実装はアルゴリズムに従うことと識別不可能な振る舞いを持たなければなりません (MUST)。

HTTPフィールドへのシリアライズについては、アルゴリズムはそれを生成するための推奨方法を定義します。出力が Section 4.2 に記載されたパースアルゴリズムによって正しく処理される限り、実装は指定された動作から逸脱してもよい (MAY) ことを許容します。


2. 新しい構造化フィールドの定義

HTTPフィールドを Structured Field として指定するには、その作成者は次を行う必要があります:

  • 本仕様への規範的な参照を明示すること。フィールドの受信者および生成者は本書の要件が適用されることを知る必要があります。

  • そのフィールドが Structured Header(ヘッダ領域でのみ使用される、一般的なケース)、Structured Trailer(トレイラ領域でのみ)、または Structured Field(両方)であるかを明示すること。

  • フィールド値の型を指定すること;List(Section 3.1)、Dictionary(Section 3.2)、または Item(Section 3.3)のいずれか。

  • フィールド値の意味論を定義すること。

  • フィールド値に対する追加の制約およびそれらの制約が破られた場合の結果を指定すること。

通常、これはフィールド定義がトップレベルの型(List、Dictionary、または Item)を指定し、次にその許容型およびそれらに対する制約を定義することを意味します。例えば、List として定義されたヘッダはすべて整数メンバを持つか、型の混在を許すかもしれません;Item として定義されたヘッダは文字列のみを許可し、さらに "Q" で始まる文字列のみや小文字のみを許す、といった具合です。同様に、内部リスト(Section 3.1.1)はフィールド定義が明示的にそれらを許可する場合にのみ有効です。

Display String 型を使用するフィールドは、許容されるUnicodeコードポイントを慎重に指定することが推奨されます。例えば、[PRECIS] のプロファイルの使用を指定するなどです。

フィールド定義はフィールド値の一部分に対してではなく、全体に対して本仕様を使用することができます。

仕様書はフィールド名を適宜 "Structured Header name"、"Structured Trailer name"、あるいは "Structured Field name" と参照することができます。同様にフィールド値を "Structured Header value"、"Structured Trailer value"、または "Structured Field value" と呼ぶことができます。

本仕様は実装がサポートする様々な構造の長さや数の最小限を定義します。多くの場合で最大サイズを指定はしませんが、仕様作成者はHTTP実装が個々のフィールドのサイズ、フィールドの総数、および/またはヘッダやトレイラ全体のサイズに対して様々な制限を課すことを認識すべきです。

2.1. 

架空の Foo-Example ヘッダフィールドは次のように指定されるかもしれません:

42. Foo-Example Header Field

Foo-Example HTTP ヘッダフィールドは、メッセージがどれだけの Foo を含むかに関する情報を伝達します。

Foo-Example は Item 構造化ヘッダフィールドです [RFC9651]。その値は MUST 整数([RFC9651] の Section 3.3.1)でなければなりません。

その値はメッセージ中の Foo の量を示し、0 から 10(両端含む)の間である MUST です;その他の値はフィールド全体を無視する原因となります。

次のパラメータが定義されています:

  • A キーが "foourl" で、その値が文字列([RFC9651] の Section 3.3.3)であり、メッセージの Foo URL を伝えるパラメータ。処理要件は以下を参照。

"foourl" は URI-reference([RFC3986] の Section 4.1)を含みます。値が有効な URI-reference でない場合、フィールド全体は MUST 無視されます。値が相対参照([RFC3986] の Section 4.2)である場合、使用前に解決([RFC3986] の Section 5)されなければなりません。

例えば:

  Foo-Example: 2; foourl="https://foo.example.com/"

2.2. エラー処理

パースが失敗した場合、フィールド全体が無視されます(Section 4.2参照)。フィールド定義はこれを上書きできません。なぜなら上書きすると汎用ソフトウェアでの処理ができなくなるからです;フィールド定義は数値の範囲、文字列やトークンの形式、辞書値に許される型、リスト中の項目数などに関する追加の制約を加えることはできますが、パース全体の失敗動作自体を変更することはできません。

フィールド固有の制約が破られた場合も、フィールド全体は無視されます(そのフィールド定義が他の処理要件を明示的に定めている場合を除く)。例えば、あるヘッダフィールドが Item として定義され整数であることを要求されているにもかかわらず文字列が受信された場合、そのフィールドはそのフィールド定義が明示的に別の処理を指定していない限り無視されるべきです。

2.3. 拡張性の保持

Structured Fields は拡張可能であるように設計されています。経験から、予見できない場合でも、フィールドの許容構文や意味論を制御された方法で変更・追加する必要が生じることが多いことが示されています。

Items と Inner Lists の両方は拡張機構としてパラメータを許します;これは必要に応じて後でそれらの値をより多くの情報に対応させるために拡張できることを意味します。前方互換性を保つため、フィールド仕様は未認識のパラメータの存在をエラー条件として定義することを控えるべきです。

フィールド仕様は拡張性を保つために Item、List、または Dictionary のいずれかであることが求められます。誤って他の型(例えば Integer)として定義されたフィールドは、Items(すなわちパラメータを許す)であると見なされます。

この拡張性が将来も利用可能であることをさらに保証し、受信者が完全なパーサ実装を使用することを促すために、フィールド定義は送信者が "grease" パラメータを追加することを指定できます。仕様はあるパターンに合致するすべてのパラメータをこの目的のために予約し、それらをリクエストの一部で送信することを推奨することができます。これは受信者がパラメータを考慮しないパーサを書かないよう促すのに役立ちます。

辞書を使用する仕様は、未知のキーの存在およびその値と型を無視することを要求することで前方互換性を確保することもできます。後続の仕様は追加のキーを定義し、それらに適した制約を明記できます。

Structured Field への拡張は、定義する値に関する制約が満たされない場合、拡張を理解する受信者がフィールド値全体を無視することを要求することができます。

2.4. 拡張における新しい構造化型の使用

フィールド定義が Structured Fields の特定の RFC を参照する必要があるため、その値で使用できる型はその RFC で定義された型に限定されます。例えば、本書を参照する定義のフィールドは Date 型(Section 3.3.7)を使用できますが、RFC 8941 を参照する定義のフィールドは使用できません。後者はその仕様に基づく実装により無効(従って破棄)と見なされるからです。

この制限はフィールドへの将来の拡張にも適用されます;例えば、RFC 8941 を参照して定義されたフィールドは Date 型を使用できません。なぜなら一部の受信者がまだ RFC 8941 に基づくパーサを使用してそれを処理している可能性があるからです。

しかしながら、本書は RFC 8941 と後方互換性を持つよう設計されています;ここで定められた要件を実装するパーサは、RFC 8941 を参照する定義の有効な Structured Fields も解析できます。

Structured Fields 実装を新しい仕様(本書のような)にアップグレードすると、以前の RFC によって無効と見なされていたフィールド値の一部が処理において有効になる可能性が生じます。

例えば、フィールドインスタンスが構文的に有効な Date(Section 3.3.7)を含む場合、そのフィールド定義が Date を許容していなくともあり得ます。RFC 8941 に基づく実装はその仕様で定義されていないためそのようなフィールドインスタンスのパースに失敗します。もしその実装が本仕様へアップグレードされた場合、パースは成功することになります。場合によっては、生成された Date 値はフィールド固有のロジックによって拒否されるでしょうが、拡張パラメータのように通常無視されるフィールド内の値は検出されないかもしれず、結果としてフィールドが受理・処理される可能性があります。


3. 構造化データ型

このセクションでは、Structured Fields が使用する抽象型の概要を示し、それぞれの型がどのようにテキスト形式の HTTP フィールドへシリアライズされるかの簡単な説明と例を示します。Section 4 では、テキスト形式の HTTP フィールドからのパースおよびそこへのシリアライズの詳細を規定します。

要約すると:

  • HTTP フィールドはトップレベルで 3 種類の型に定義され得ます:リスト、辞書、および項目(Item)。

  • リストと辞書はコンテナであり、そのメンバは項目(Item)または内部リスト(Inner List、これ自体が項目の配列)になり得ます。

  • 項目(Item)と内部リストの両方はパラメータで付加情報を持てます。

3.1. リスト

リストは0個以上のメンバからなる配列で、各メンバは項目(Section 3.3)または内部リスト(Section 3.1.1)のいずれかであり、どちらもパラメータ化(Section 3.1.2)できます。

空のリストはフィールドをまったくシリアライズしないことで表されます。これはリストとして定義されたフィールドがデフォルトで空の値を持つことを意味します。

テキスト形式の HTTP フィールドとしてシリアライズされるとき、各メンバはカンマと任意の空白で区切られます。例えば、値がトークンのリストと定義されたフィールドは次のようになります:

Example-List: sugar, tea, rum

リストのメンバは、RFC 9110 Section 5.3 に従い、同じヘッダまたはトレイラセクションの複数行に分けられることがある点に注意してください。例えば、次の 2 つは等価です:

Example-List: sugar, tea, rum

および

Example-List: sugar, tea
Example-List: rum

ただし、リストの個々のメンバを行間で安全に分割することはできません。詳細はSection 4.2を参照してください。

パーサは少なくとも 1024 個のメンバを含むリストをサポートしなければなりません。フィールド仕様は必要に応じて個々のリスト値の型や基数を制約できます。

3.1.1. 内部リスト

内部リストは0個以上の項目(Section 3.3)からなる配列です。個々の項目と内部リスト自体の両方がパラメータ化(Section 3.1.2)できます。

テキスト形式の HTTP フィールドでシリアライズされるとき、内部リストは丸括弧で囲まれ、値は1つ以上の空白で区切られます。値が文字列の内部リストのリストと定義されたフィールドの例は次のようになります:

Example-List: ("foo" "bar"), ("baz"), ("bat" "one"), ()

この例の最後のメンバが空の内部リストである点に注意してください。

両レベルでパラメータを持つ内部リストのリストとして定義されたヘッダフィールドの例は次のようになります:

Example-List: ("foo"; a=1;b=2);lvl=5, ("bar" "baz");lvl=1

パーサは少なくとも 256 個のメンバを含む内部リストをサポートしなければなりません。フィールド仕様は必要に応じて個々の内部リストメンバの型や基数を制約できます。

3.1.2. パラメータ

パラメータは項目(Section 3.3)または内部リスト(Section 3.1.1)に関連付けられたキーと値の順序付けされたマップです。キーはそれが出現するパラメータの範囲内で一意であり、値は裸の項目(つまり、それ自体をパラメータ化できません;詳細は Section 3.3 を参照)です。

実装はインデックスおよびキーの両方でパラメータへアクセスできるようにしなければなりません。仕様はどちらのアクセス手段を使用してもよいとしています。

パラメータは順序付きであり、パラメータキーは大文字を含めることができない点に注意してください。

テキスト形式の HTTP フィールドでシリアライズされるとき、パラメータは項目または内部リストおよび他のパラメータとセミコロンで区切られます。例えば:

Example-List: abc;a=1;b=2; cde_456, (ghi;jk=4 l);q="9";r=w

値がブール true のパラメータ(Section 3.3.6)は、シリアライズ時にその値を省略しなければなりません。例えば、ここでは "a" パラメータが true、"b" パラメータが false です:

Example-Integer: 1; a; b=?0

この要件はシリアライズ時のみのものである点に注意してください;パーサはパラメータに true の値が現れる場合でも正しく扱う必要があります。

パーサは項目または内部リストに少なくとも 256 個のパラメータと、少なくとも 64 文字のパラメータキーをサポートしなければなりません。フィールド仕様は個々のパラメータの順序や値の型を必要に応じて制約できます。

3.2. 辞書

辞書はキーと値の順序付けられたマップで、キーは短いテキスト文字列、値は項目(Section 3.3)または項目の配列であり、どちらもパラメータ化(Section 3.1.2)できます。メンバは0個以上存在し、そのキーは辞書の範囲内で一意です。

実装はインデックスおよびキーの両方で辞書へアクセスできるようにしなければなりません。仕様はどちらのアクセス手段を使ってもよいとしています。

リストと同様に、空の辞書はフィールドを省略することで表されます。これは辞書として定義されたフィールドがデフォルトで空の値を持つことを意味します。

通常、フィールド仕様は辞書の意味論を、個々のメンバのキーごとに許容される型を指定し、またその存在が必須か任意かを明示することで定義します。受信側は、キーが未定義または不明なメンバを無視しなければなりません(フィールド仕様が明示的にそれらを許可しない場合を除く)。

テキスト形式の HTTP フィールドとしてシリアライズされるとき、メンバはシリアライズ順に並べられ、カンマと任意の空白で区切られます。メンバキーには大文字を含めることはできません。キーと値は等号 "="(空白なし)で区切られます。例えば:

Example-Dict: en="Applepie", da=:w4ZibGV0w6ZydGU=:

この例で最後の "=" はバイト列の含有によるものである点に注意してください。詳細は Section 3.3.5 を参照してください。

値がブール true のメンバ(Section 3.3.6)は、シリアライズ時にその値を省略しなければなりません。例えば、ここでは "b" と "c" の両方が true です:

Example-Dict: a=?0, b, c; foo=bar

この要件はシリアライズ時のみのものである点に注意してください;パーサは辞書の値に true のブール値が現れる場合でも正しく扱う必要があります。

値がトークンの内部リストを持つ辞書の例:

Example-Dict: rating=1.5, feelings=(joy sadness)

項目と内部リストが混在し、一部にパラメータを持つ辞書の例:

Example-Dict: a=(1 2), b=3, c=4;aa=bb, d=(5 6);valid

辞書のメンバも同一のヘッダまたはトレイラセクション内の複数行に分割できる点に注意してください。例えば、次の2つは等価です:

Example-Dict: foo=1, bar=2

および

Example-Dict: foo=1
Example-Dict: bar=2

ただし、辞書の個々のメンバを行間で安全に分割することはできません。詳細はSection 4.2を参照してください。

パーサは少なくとも 1024 個のキー/値ペアを含む辞書と、少なくとも 64 文字のキーをサポートしなければなりません。フィールド仕様は必要に応じて個々の辞書メンバの順序や値の型を制約できます。

3.3. 項目

項目(Item)は整数(Section 3.3.1)、小数(Section 3.3.2)、文字列(Section 3.3.3)、トークン(Section 3.3.4)、バイト列(Section 3.3.5)、ブール値(Section 3.3.6)、または日付(Section 3.3.7)のいずれかです。項目は関連するパラメータ(Section 3.1.2)を持つことができます。

例えば、項目が整数と定義されたヘッダフィールドは次のようになります:

Example-Integer: 5

またはパラメータ付きで:

Example-Integer: 5; foo=bar

3.3.1. 整数

整数は -999,999,999,999,999 から 999,999,999,999,999(符号付きで最大15桁)までの範囲を持ちます。これは IEEE 754 との互換性のためです。

例えば:

Example-Integer: 42

15 桁を超える整数は、文字列(Section 3.3.3)、バイト列(Section 3.3.5)、あるいはスケーリング係数として動作する整数パラメータを使うなど、様々な方法でサポートできます。

先頭にゼロを含む整数(例:"0002", "-01")や符号付きゼロ("-0")をシリアライズすることは可能ですが、これらの差異は実装によって保持されない場合があります。

この節の本文中での読みやすさのためのカンマはワイヤ形式では無効である点に注意してください。

3.3.2. 小数

小数は整数部と小数部を持つ数値です。整数部は最大12桁、小数部は最大3桁です。

例えば、値が小数と定義されたヘッダは次のようになります:

Example-Decimal: 4.5

先頭ゼロ(例:"0002.5", "-01.334")、末尾ゼロ(例:"5.230", "-0.40")、および符号付きゼロ(例:"-0.0")でシリアライズすることは可能ですが、これらの差異は実装により保持されない場合があります。

シリアライズアルゴリズム(Section 4.1.5)は、小数部が3桁を超える入力を丸めます。別の丸め戦略が望ましい場合は、フィールド定義でシリアライズの前にそれを行うよう指定する必要があります。

3.3.3. 文字列

文字列は0個以上の印字可能なASCII文字(RFC0020 に従う、つまり %x20 から %x7E の範囲)で構成されます。これにはタブ、改行、キャリッジリターンなどは含まれません。

非ASCII文字は相互運用性の問題を引き起こすため、文字列では直接サポートされません。ほとんどのフィールド値では非ASCIIを必要としないため、例外は少ないです。

フィールド値に非ASCIIコンテンツを伝える必要がある場合は、表示用文字列(Display String、Section 3.3.8)を指定できます。

テキスト形式の HTTP フィールドでシリアライズされるとき、文字列は二重引用符で区切られ、二重引用符とバックスラッシュはバックスラッシュでエスケープされます。例えば:

Example-String: "hello world"

文字列の区切りには DQUOTE のみが使用され、シングルクォートは使用されない点に注意してください。さらに、エスケープ可能なのは DQUOTE と "\" のみであり、その他の文字に対する "\" はパース失敗を引き起こさなければなりません。

パーサはデコード後の文字列を少なくとも 1024 文字サポートしなければなりません。

3.3.4. トークン

トークンは短いテキスト単語で、アルファベット文字または "*" で始まり、その後に0個以上のトークン文字が続きます。トークン文字は RFC9110 の "token" ABNF ルールで許可される文字に ":" と "/" を追加したものと同じです。

例えば:

Example-Token: foo123/456

パーサは少なくとも 512 文字のトークンをサポートしなければなりません。

トークンは既存の HTTP フィールドのデータモデルとの互換性のために主に定義されており、いくつかの実装では追加の手順が必要になる場合があります。その結果、新しいフィールドでは文字列の使用が推奨されます。

3.3.5. バイト列

バイト列は Structured Fields で伝達できます。

テキスト形式の HTTP フィールドでシリアライズされるとき、バイト列はコロンで囲まれ、base64(RFC4648)でエンコードされます。例えば:

Example-ByteSequence: :cHJldGVuZCB0aGlzIGlzIGJpbmFyeSBjb250ZW50Lg==:

パーサはデコード後に少なくとも 16384 オクテットのバイト列をサポートしなければなりません。

3.3.6. ブール値

ブール値は Structured Fields で伝達できます。

テキスト形式の HTTP フィールドでシリアライズされるとき、ブールは先頭に "?" を付け、その後に真の場合は "1" 、偽の場合は "0" を続けて示します。例えば:

Example-Boolean: ?1

辞書(Section 3.2)およびパラメータ(Section 3.1.2)の値においては、ブール true は値を省略することで示される点に注意してください。

3.3.7. 日付

日付値は Structured Fields で伝達できます。

日付は整数に類似したデータモデルを持ち、1970-01-01T00:00:00Z からの秒の差分(うるう秒を除く、負の値も可)を表します。したがって、テキスト形式でのシリアライズは整数に似ていますが、先頭に "@" を付けて区別されます。

例えば:

Example-Date: @1659578233

パーサは年 1 から 9999 の全日を含む日付(すなわち 1970-01-01T00:00:00Z からの -62,135,596,800 から 253,402,214,400 秒の差分)をサポートしなければなりません。

3.3.8. 表示文字列

表示文字列は文字列に似ており0個以上の文字から構成されますが、文字列とは異なり Unicode スカラー値(サロゲートを除くすべての Unicode コードポイント)を許容します。

表示文字列は最終ユーザへ表示される値のために設計されており、非ASCIIコンテンツを含む必要がある場合に使用されます。正規化や同字異形(homograph)攻撃など Unicode に関する処理上およびセキュリティ上の考慮事項があるため、文字列(Section 3.3.3)やトークン(Section 3.3.4)で足りる場合は表示文字列の使用は推奨されません。

表示文字列は値に使用されている言語を示すものではない点に注意してください;必要な場合は別途(例えばパラメータで)示すことができます。

テキスト形式の HTTP フィールドでは、表示文字列は文字列と類似した形で表現されますが、非ASCII文字はパーセントエンコードされます。これを文字列と区別するために先頭に "%" が付きます。

例えば:

Example-DisplayString: %"This is intended for display to %c3%bcsers."

表示文字列を扱う際の追加のセキュリティ考慮事項については、Section 6 を参照してください。


4. HTTPにおける構造化フィールドの扱い

このセクションでは、Section 3 で定義された抽象型を、テキスト形式の HTTP フィールド値やそれらと互換性のある他のエンコーディング(例えば、HPACK ([HPACK]) による圧縮前の HTTP/2 [HTTP/2] 内での表現など)にシリアライズおよびパースする方法を定義します。

4.1. 構造化フィールドのシリアライズ

本仕様で定義された構造が与えられたとき、HTTP フィールド値で使用可能な ASCII 文字列を返します。

  1. 構造が Dictionary または List であり、その値が空(すなわちメンバを持たない)である場合は、フィールドをまったくシリアライズしない(すなわち field-name と field-value の両方を省略する)ものとします。

  2. 構造が List である場合、output_string を構造に対して「リストのシリアライズ」(Section 4.1.1) を実行した結果とします。

  3. それ以外で構造が Dictionary である場合、output_string を構造に対して「辞書のシリアライズ」(Section 4.1.2) を実行した結果とします。

  4. それ以外で構造が Item である場合、output_string を構造に対して「項目のシリアライズ」(Section 4.1.3) を実行した結果とします。

  5. それ以外の場合、シリアライズは失敗します。

  6. ASCII エンコーディング [RFC0020] を用いて output_string をバイト配列に変換して返します。

4.1.1. リストのシリアライズ

入力として (member_value, parameters) タプルの配列 input_list が与えられたとき、HTTP フィールド値で使用可能な ASCII 文字列を返します。

  1. output を空の文字列とします。

  2. input_list の各 (member_value, parameters) について:

    1. member_value が配列である場合、(member_value, parameters) に対して「内部リストのシリアライズ」(Section 4.1.1.1) を実行した結果を output に追加します。

    2. それ以外の場合、(member_value, parameters) に対して「項目のシリアライズ」(Section 4.1.3) を実行した結果を output に追加します。

    3. input_list にさらに member_values が残っている場合:

      1. "," を output に追加します。

      2. 単一の SP を output に追加します。

  3. output を返します。

4.1.1.1. 内部リストのシリアライズ

inner_list として (member_value, parameters) タプルの配列、list_parameters としてパラメータが与えられたとき、HTTP フィールド値で使用可能な ASCII 文字列を返します。

  1. output を "(" の文字列とします。

  2. inner_list の各 (member_value, parameters) について:

    1. (member_value, parameters) に対して「項目のシリアライズ」(Section 4.1.3) を実行した結果を output に追加します。

    2. inner_list にさらに値が残っている場合、単一の SP を output に追加します。

  3. ")" を output に追加します。

  4. list_parameters に対して「パラメータのシリアライズ」(Section 4.1.1.2) を実行した結果を output に追加します。

  5. output を返します。

4.1.1.2. パラメータのシリアライズ

各メンバが param_key と param_value を持つ順序付き Dictionary input_parameters が与えられたとき、HTTP フィールド値で使用可能な ASCII 文字列を返します。

  1. output を空の文字列とします。

  2. input_parameters の各 param_key とその param_value について:

    1. ";" を output に追加します。

    2. param_key に対して「キーのシリアライズ」(Section 4.1.1.3) を実行した結果を output に追加します。

    3. param_value がブール true でない場合:

      1. "=" を output に追加します。

      2. param_value に対して「裸の項目のシリアライズ」(Section 4.1.3.1) を実行した結果を output に追加します。

  3. output を返します。

4.1.1.3. キーのシリアライズ

input_key が与えられたとき、HTTP フィールド値で使用可能な ASCII 文字列を返します。

  1. input_key を ASCII 文字列の列に変換します。変換に失敗した場合、シリアライズを失敗させます。

  2. input_key に lcalpha、DIGIT、"_"、"-"、"."、または "*" 以外の文字が含まれている場合、シリアライズを失敗させます。

  3. input_key の最初の文字が lcalpha または "*" でない場合、シリアライズを失敗させます。

  4. output を空の文字列とします。

  5. input_key を output に追加します。

  6. output を返します。

4.1.2. 辞書のシリアライズ

各メンバが member_key と (member_value, parameters) タプルを持つ順序付き Dictionary input_dictionary が与えられたとき、HTTP フィールド値で使用可能な ASCII 文字列を返します。

  1. output を空の文字列とします。

  2. input_dictionary の各 member_key とその (member_value, parameters) について:

    1. メンバの member_key に対して「キーのシリアライズ」(Section 4.1.1.3) を実行した結果を output に追加します。

    2. member_value がブール true の場合:

      1. parameters に対して「パラメータのシリアライズ」(Section 4.1.1.2) を実行した結果を output に追加します。

    3. それ以外の場合:

      1. "=" を output に追加します。

      2. member_value が配列である場合、(member_value, parameters) に対して「内部リストのシリアライズ」(Section 4.1.1.1) を実行した結果を output に追加します。

      3. それ以外の場合、(member_value, parameters) に対して「項目のシリアライズ」(Section 4.1.3) を実行した結果を output に追加します。

    4. input_dictionary にさらにメンバが残っている場合:

      1. "," を output に追加します。

      2. 単一の SP を output に追加します。

  3. output を返します。

4.1.3. 項目のシリアライズ

bare_item として項目、item_parameters としてパラメータが与えられたとき、HTTP フィールド値で使用可能な ASCII 文字列を返します。

  1. output を空の文字列とします。

  2. bare_item に対して「裸の項目のシリアライズ」(Section 4.1.3.1) を実行した結果を output に追加します。

  3. item_parameters に対して「パラメータのシリアライズ」(Section 4.1.1.2) を実行した結果を output に追加します。

  4. output を返します。

4.1.3.1. 裸の項目のシリアライズ

input_item が与えられたとき、HTTP フィールド値で使用可能な ASCII 文字列を返します。

  1. input_item が整数である場合、input_item に対して「整数のシリアライズ」(Section 4.1.4) を実行した結果を返します。

  2. input_item が小数である場合、input_item に対して「小数のシリアライズ」(Section 4.1.5) を実行した結果を返します。

  3. input_item が文字列である場合、input_item に対して「文字列のシリアライズ」(Section 4.1.6) を実行した結果を返します。

  4. input_item がトークンである場合、input_item に対して「トークンのシリアライズ」(Section 4.1.7) を実行した結果を返します。

  5. input_item がバイト列である場合、input_item に対して「バイト列のシリアライズ」(Section 4.1.8) を実行した結果を返します。

  6. input_item がブール値である場合、input_item に対して「ブール値のシリアライズ」(Section 4.1.9) を実行した結果を返します。

  7. input_item が日付である場合、input_item に対して「日付のシリアライズ」(Section 4.1.10) を実行した結果を返します。

  8. input_item が表示文字列である場合、input_item に対して「表示文字列のシリアライズ」(Section 4.1.11) を実行した結果を返します。

  9. それ以外の場合、シリアライズを失敗させます。

4.1.4. 整数のシリアライズ

input_integer が与えられたとき、HTTP フィールド値で使用可能な ASCII 文字列を返します。

  1. input_integer が -999,999,999,999,999 から 999,999,999,999,999 の範囲内の整数でない場合、シリアライズを失敗させます。

  2. output を空の文字列とします。

  3. input_integer が 0 より小さい(ただし 0 と等しくない)場合、"-" を output に追加します。

  4. input_integer の数値を 10 進で、10 進数字のみを用いて表現したものを output に追加します。

  5. output を返します。

4.1.5. 小数のシリアライズ

input_decimal が与えられたとき、HTTP フィールド値で使用可能な ASCII 文字列を返します。

  1. input_decimal が小数でない場合、シリアライズを失敗させます。

  2. input_decimal の小数点以下に 3 桁を超える有効数字がある場合、小数点以下 3 桁に丸めます。最終桁は最も近い値に丸め、等距離の場合は偶数へ丸めます。

  3. 丸め後に小数点左側の有効数字が 12 桁を超える場合、シリアライズを失敗させます。

  4. output を空の文字列とします。

  5. input_decimal が 0 より小さい(ただし 0 と等しくない)場合、"-" を output に追加します。

  6. input_decimal の整数部分を 10 進で(10 進数字のみを用いて) output に追加します。ゼロであれば "0" を追加します。

  7. "." を output に追加します。

  8. input_decimal の小数部分がゼロである場合、"0" を output に追加します。

  9. それ以外の場合、input_decimal の小数部分の有効数字を 10 進で(10 進数字のみを用いて) output に追加します。

  10. output を返します。

4.1.6. 文字列のシリアライズ

input_string が与えられたとき、HTTP フィールド値で使用可能な ASCII 文字列を返します。

  1. input_string を ASCII 文字列の列に変換します。変換に失敗した場合、シリアライズを失敗させます。

  2. input_string に %x00-1f または %x7f-ff(すなわち VCHAR または SP に含まれない文字)が含まれている場合、シリアライズを失敗させます。

  3. output を DQUOTE の文字列とします。

  4. input_string の各文字 char について:

    1. char が "\" または DQUOTE の場合:

      1. "\" を output に追加します。

    2. char を output に追加します。

  5. DQUOTE を output に追加します。

  6. output を返します。

4.1.7. トークンのシリアライズ

input_token が与えられたとき、HTTP フィールド値で使用可能な ASCII 文字列を返します。

  1. input_token を ASCII 文字列の列に変換します。変換に失敗した場合、シリアライズを失敗させます。

  2. input_token の最初の文字が ALPHA または "*" でない、あるいは残りの部分に tchar、":"、"/" に含まれない文字がある場合、シリアライズを失敗させます。

  3. output を空の文字列とします。

  4. input_token を output に追加します。

  5. output を返します。

4.1.8. バイト列のシリアライズ

input_bytes が与えられたとき、HTTP フィールド値で使用可能な ASCII 文字列を返します。

  1. input_bytes がバイト列でない場合、シリアライズを失敗させます。

  2. output を空の文字列とします。

  3. ":" を output に追加します。

  4. [RFC4648] の規定に従い input_bytes を base64 エンコードした結果(以下の要件を考慮)を output に追加します。

  5. ":" を output に追加します。

  6. output を返します。

エンコードされたデータは [RFC4648] の規定に従って "=" でパディングされている必要があります。

同様に、エンコードデータは可能であれば pad ビットをゼロにすることが [RFC4648] の推奨ですが、実装上不可能な場合は例外とできます。

4.1.9. ブール値のシリアライズ

input_boolean が与えられたとき、HTTP フィールド値で使用可能な ASCII 文字列を返します。

  1. input_boolean が真偽値でない場合、シリアライズを失敗させます。

  2. output を空の文字列とします。

  3. "?" を output に追加します。

  4. input_boolean が true の場合、"1" を output に追加します。

  5. input_boolean が false の場合、"0" を output に追加します。

  6. output を返します。

4.1.10. 日付のシリアライズ

input_date が与えられたとき、HTTP フィールド値で使用可能な ASCII 文字列を返します。

  1. output を "@" とします。

  2. input_date に対して「整数のシリアライズ」(Section 4.1.4) を実行した結果を output に追加します。

  3. output を返します。

4.1.11. 表示文字列のシリアライズ

Unicode コードポイントの列 input_sequence が与えられたとき、HTTP フィールド値で使用可能な ASCII 文字列を返します。

  1. input_sequence が Unicode コードポイントの列でない場合、シリアライズを失敗させます。

  2. byte_array を input_sequence に UTF-8 エンコーディング(Section 3 of [UTF8])を適用した結果とします。エンコードが失敗した場合、シリアライズを失敗させます。

  3. encoded_string を "%" に続けて DQUOTE を含む文字列とします。

  4. byte_array の各バイトについて:

    1. バイトが %x25 ("%")、%x22 (DQUOTE)、または %x00-1f や %x7f-ff の範囲にある場合:

      1. "%" を encoded_string に追加します。

      2. encoded_byte を byte に対して base16 エンコード(Section 8 of [RFC4648])した結果とし、アルファベット文字があれば小文字に変換します。

      3. encoded_byte を encoded_string に追加します。

    2. それ以外の場合、バイトを ASCII 文字としてデコードし、その結果を encoded_string に追加します。

  5. DQUOTE を encoded_string に追加します。

  6. encoded_string を返します。

[UTF8] は U+D800 から U+DFFF(サロゲート)間のコードポイントのエンコードを禁止しています。入力にそれらが含まれる場合、シリアライズは失敗します。

4.2. 構造化フィールドの解析

受信実装が Structured Fields であることが分かっている HTTP フィールドを解析する際には注意が必要です。相互運用性やセキュリティの問題を引き起こし得るさまざまなエッジケースが存在するためです。このセクションでは、そのためのアルゴリズムを規定します。

選択されたフィールドの field-value を表すバイト配列 input_bytes(そのフィールドが存在しない場合は空)と field_type("dictionary"、"list"、または "item" のいずれか)が与えられたとき、解析されたフィールド値を返します。

  1. input_bytes を ASCII 文字列 input_string に変換します。変換に失敗した場合、解析を失敗させます。

  2. input_string の先頭の SP をすべて破棄します。

  3. field_type が "list" の場合、output を input_string に対して「リストの解析」(Section 4.2.1) を実行した結果とします。

  4. field_type が "dictionary" の場合、output を input_string に対して「辞書の解析」(Section 4.2.2) を実行した結果とします。

  5. field_type が "item" の場合、output を input_string に対して「項目の解析」(Section 4.2.3) を実行した結果とします。

  6. input_string の先頭の SP をすべて破棄します。

  7. input_string が空でない場合、解析を失敗させます。

  8. そうでなければ output を返します。

入力となる input_bytes を生成する際、パーサは同じセクション(ヘッダまたはトレイラ)内でフィールド名と大文字小文字を区別せずに一致するすべてのフィールド行を、Section 5.2 of [HTTP] に従ってカンマで区切った単一の field-value に結合しなければなりません。これによりフィールド値全体が正しく処理されることが保証されます。

リストと辞書については、トップレベルのデータ構造の個々のメンバが複数のフィールドインスタンスにまたがって分割されない限り、フィールドのすべての行を正しく連結する効果があります。両型の解析アルゴリズムは、これらが行結合に使用され得るため、タブ文字を許容します。

複数のフィールド行に分割された文字列は予測不能な結果をもたらします。なぜなら、1つ以上のカンマ(および任意の空白)がパーサが出力する文字列の一部になるからです。連結が上流の中継によって行われる可能性があるため、その結果はシリアライザやパーサの制御下にない場合があります。

トークン、整数、小数、およびバイト列は、挿入されたカンマが解析を失敗させるため、複数のフィールド行にまたがって分割することはできません。

パーサは、あるフィールド行がそのフィールドとして解析できない場合に、複数行にわたるフィールド値の処理を失敗させることができます。例えば、sf-string と定義された Example-String フィールドを処理する解析は、次のようなフィールドセクションの処理で失敗しても許容されます:

Example-String: "foo
Example-String: bar"

解析が失敗した場合、フィールド値全体は無視されなければなりません(すなわち、そのフィールドがセクション内に存在しなかったかのように扱う)、あるいは代わりに完全な HTTP メッセージを不正として扱わなければなりません。これは相互運用性と安全性を向上させるために意図的に厳格にしているものであり、Structured Fields を使用するフィールド仕様はこの要求を緩和することを許されません。

この要件は、そのフィールドを解析していない実装には適用されないことに注意してください。例えば、中継はフォワードする前に失敗するフィールドをメッセージから削除する必要はありません。

4.2.1. リストの解析

ASCII 文字列 input_string が与えられたとき、(item_or_inner_list, parameters) タプルの配列を返します。input_string は解析された値を削除するように変更されます。

  1. members を空の配列とします。

  2. input_string が空でない間:

    1. input_string に対して「項目または内部リストの解析」(Section 4.2.1.1) を実行した結果を members に追加します。

    2. input_string の先頭の OWS を破棄します。

    3. input_string が空であれば、members を返します。

    4. input_string の最初の文字を消費します。それが "," でなければ、解析を失敗させます。

    5. input_string の先頭の OWS を破棄します。

    6. input_string が空であれば、末尾のカンマがあるので解析を失敗させます。

  3. 構造化データが見つからなかった場合、members(空)を返します。

4.2.1.1. 項目または内部リストの解析

ASCII 文字列 input_string が与えられたとき、(item_or_inner_list, parameters) タプルを返します。item_or_inner_list は単一の裸の項目か、(bare_item, parameters) タプルの配列のどちらかです。input_string は解析された値を削除するように変更されます。

  1. input_string の最初の文字が "(" である場合、input_string に対して「内部リストの解析」(Section 4.2.1.2) を実行した結果を返します。

  2. それ以外の場合、input_string に対して「項目の解析」(Section 4.2.3) を実行した結果を返します。

4.2.1.2. 内部リストの解析

ASCII 文字列 input_string が与えられたとき、(inner_list, parameters) タプルを返します。inner_list は (bare_item, parameters) タプルの配列です。input_string は解析された値を削除するように変更されます。

  1. input_string の最初の文字を消費します。それが "(" でなければ解析を失敗させます。

  2. inner_list を空の配列とします。

  3. input_string が空でない間:

    1. input_string の先頭の SP を破棄します。

    2. input_string の最初の文字が ")" である場合:

      1. input_string の最初の文字を消費します。

      2. input_string に対して「パラメータの解析」(Section 4.2.3.2) を実行した結果を parameters とします。

      3. (inner_list, parameters) タプルを返します。

    3. input_string に対して「項目の解析」(Section 4.2.3) を実行した結果を item とします。

    4. item を inner_list に追加します。

    5. input_string の最初の文字が SP または ")" でない場合、解析を失敗させます。

  4. 内部リストの終端が見つからなかったため、解析を失敗させます。

4.2.2. 辞書の解析

ASCII 文字列 input_string が与えられたとき、値が (item_or_inner_list, parameters) タプルである順序付けられたマップを返します。input_string は解析された値を削除するように変更されます。

  1. dictionary を空の順序付けマップとします。

  2. input_string が空でない間:

    1. input_string に対して「キーの解析」(Section 4.2.3.3) を実行した結果を this_key とします。

    2. input_string の最初の文字が "=" である場合:

      1. input_string の最初の文字を消費します。

      2. input_string に対して「項目または内部リストの解析」(Section 4.2.1.1) を実行した結果を member とします。

    3. それ以外の場合:

      1. value をブール true とします。

      2. input_string に対して「パラメータの解析」(Section 4.2.3.2) を実行した結果を parameters とします。

      3. member を (value, parameters) タプルとします。

    4. dictionary が既に this_key を含んでいる場合(文字ごとに比較)、その値を member で上書きします。

    5. それ以外の場合、this_key を key として member を dictionary に追加します。

    6. input_string の先頭の OWS を破棄します。

    7. input_string が空であれば、dictionary を返します。

    8. input_string の最初の文字を消費します。それが "," でなければ解析を失敗させます。

    9. input_string の先頭の OWS を破棄します。

    10. input_string が空であれば、末尾のカンマがあるので解析を失敗させます。

  3. 構造化データが見つからなかった場合、dictionary(空)を返します。

重複する辞書キーが見つかった場合、最後のインスタンス以外は無視される点に注意してください。

4.2.3. 項目の解析

ASCII 文字列 input_string が与えられたとき、(bare_item, parameters) タプルを返します。input_string は解析された値を削除するように変更されます。

  1. input_string に対して「裸の項目の解析」(Section 4.2.3.1) を実行した結果を bare_item とします。

  2. input_string に対して「パラメータの解析」(Section 4.2.3.2) を実行した結果を parameters とします。

  3. (bare_item, parameters) タプルを返します。

4.2.3.1. 裸の項目の解析

ASCII 文字列 input_string が与えられたとき、裸の項目を返します。input_string は解析された値を削除するように変更されます。

  1. input_string の最初の文字が "-" または DIGIT の場合、input_string に対して「整数または小数の解析」(Section 4.2.4) を実行した結果を返します。

  2. input_string の最初の文字が DQUOTE の場合、input_string に対して「文字列の解析」(Section 4.2.5) を実行した結果を返します。

  3. input_string の最初の文字が ALPHA または "*" の場合、input_string に対して「トークンの解析」(Section 4.2.6) を実行した結果を返します。

  4. input_string の最初の文字が ":" の場合、input_string に対して「バイト列の解析」(Section 4.2.7) を実行した結果を返します。

  5. input_string の最初の文字が "?" の場合、input_string に対して「ブール値の解析」(Section 4.2.8) を実行した結果を返します。

  6. input_string の最初の文字が "@" の場合、input_string に対して「日付の解析」(Section 4.2.9) を実行した結果を返します。

  7. input_string の最初の文字が "%" の場合、input_string に対して「表示文字列の解析」(Section 4.2.10) を実行した結果を返します。

  8. それ以外の場合、項目の型が認識されないため、解析を失敗させます。

4.2.3.2. パラメータの解析

ASCII 文字列 input_string が与えられたとき、値が裸の項目である順序付けられたマップを返します。input_string は解析された値を削除するように変更されます。

  1. parameters を空の順序付けマップとします。

  2. input_string が空でない間:

    1. input_string の最初の文字が ";" でない場合、ループを終了します。

    2. input_string の先頭から ";" を消費します。

    3. input_string の先頭の SP を破棄します。

    4. input_string に対して「キーの解析」(Section 4.2.3.3) を実行した結果を param_key とします。

    5. param_value をブール true とします。

    6. input_string の最初の文字が "=" である場合:

      1. input_string の先頭の "=" を消費します。

      2. input_string に対して「裸の項目の解析」(Section 4.2.3.1) を実行した結果を param_value とします。

    7. parameters が既に param_key を含んでいる場合(文字ごとに比較)、その値を param_value で上書きします。

    8. それ以外の場合、param_key をキーとして param_value を parameters に追加します。

  3. parameters を返します。

重複するパラメータキーが見つかった場合、最後のインスタンス以外は無視される点に注意してください。

4.2.3.3. キーの解析

ASCII 文字列 input_string が与えられたとき、キーを返します。input_string は解析された値を削除するように変更されます。

  1. input_string の最初の文字が lcalpha または "*" でない場合、解析を失敗させます。

  2. output_string を空の文字列とします。

  3. input_string が空でない間:

    1. input_string の最初の文字が lcalpha、DIGIT、"_"、"-"、"."、または "*" のいずれでもない場合、output_string を返します。

    2. input_string の最初の文字を消費し、それを char とします。

    3. char を output_string に追加します。

  4. output_string を返します。

4.2.4. 整数または小数の解析

ASCII 文字列 input_string が与えられたとき、整数または小数を返します。input_string は解析された値を削除するように変更されます。

注: このアルゴリズムは整数(Section 3.3.1)と小数(Section 3.3.2)の両方を解析し、対応する構造を返します。

  1. type を "integer" とします。

  2. sign を 1 とします。

  3. input_number を空の文字列とします。

  4. input_string の最初の文字が "-" であれば、それを消費して sign を -1 に設定します。

  5. input_string が空であれば、空の整数があるので解析を失敗させます。

  6. input_string の最初の文字が DIGIT でない場合、解析を失敗させます。

  7. input_string が空でない間:

    1. input_string の最初の文字を消費し、それを char とします。

    2. char が DIGIT であれば、それを input_number に追加します。

    3. そうでなく、type が "integer" で char が "." である場合:

      1. input_number が 12 文字を超えている場合、解析を失敗させます。

      2. それ以外の場合、char を input_number に追加し、type を "decimal" に設定します。

    4. それ以外の場合、char を input_string の先頭に戻し、ループを抜けます。

    5. type が "integer" で input_number が 15 文字を超える場合、解析を失敗させます。

    6. type が "decimal" で input_number が 16 文字を超える場合、解析を失敗させます。

  8. type が "integer" の場合:

    1. output_number を input_number を整数として解析した結果の Integer とします。

  9. それ以外の場合:

    1. input_number の最後の文字が "." の場合、解析を失敗させます。

    2. input_number の "." の後の文字数が 3 を超える場合、解析を失敗させます。

    3. output_number を input_number を小数として解析した結果の Decimal とします。

  10. output_number を output_number と sign の積とします。

  11. output_number を返します。

4.2.5. 文字列の解析

ASCII 文字列 input_string が与えられたとき、引用解除された文字列を返します。input_string は解析された値を削除するように変更されます。

  1. output_string を空の文字列とします。

  2. input_string の最初の文字が DQUOTE でない場合、解析を失敗させます。

  3. input_string の最初の文字を破棄します。

  4. input_string が空でない間:

    1. input_string の最初の文字を消費し、それを char とします。

    2. char がバックスラッシュ ("\") の場合:

      1. input_string が空であれば、解析を失敗させます。

      2. input_string の最初の文字を消費し、それを next_char とします。

      3. next_char が DQUOTE または "\" でない場合、解析を失敗させます。

      4. next_char を output_string に追加します。

    3. そうでなく、char が DQUOTE である場合、output_string を返します。

    4. そうでなく、char が %x00-1f または %x7f-ff の範囲にある場合(すなわち VCHAR または SP に含まれない場合)、解析を失敗させます。

    5. それ以外の場合、char を output_string に追加します。

  5. 閉じる DQUOTE を見つけずに input_string の終わりに到達したため、解析を失敗させます。

4.2.6. トークンの解析

ASCII 文字列 input_string が与えられたとき、トークンを返します。input_string は解析された値を削除するように変更されます。

  1. input_string の最初の文字が ALPHA または "*" でない場合、解析を失敗させます。

  2. output_string を空の文字列とします。

  3. input_string が空でない間:

    1. input_string の最初の文字が tchar、":"、または "/" に含まれない場合、output_string を返します。

    2. input_string の最初の文字を消費し、それを char とします。

    3. char を output_string に追加します。

  4. output_string を返します。

4.2.7. バイト列の解析

ASCII 文字列 input_string が与えられたとき、バイト列を返します。input_string は解析された値を削除するように変更されます。

  1. input_string の最初の文字が ":" でない場合、解析を失敗させます。

  2. input_string の最初の文字を破棄します。

  3. input_string の終端までに ":" 文字が存在しない場合、解析を失敗させます。

  4. b64_content を input_string の先頭から最初に現れる ":" 文字を含まない部分まで消費した結果とします。

  5. input_string の先頭の ":" を消費します。

  6. b64_content が ALPHA、DIGIT、"+", "/", "=" に含まれない文字を含む場合、解析を失敗させます。

  7. 必要に応じてパディングを合成しつつ、[RFC4648] に従って b64_content を base64 デコードした結果を binary_content とします。base64 デコードに失敗した場合、解析は失敗します。

  8. binary_content を返します。

base64 の実装の中には "=" で適切にパディングされていないエンコードデータの拒否を許さないものがあるため、パーサは "=" のパディングが存在しない場合に失敗すべきではありません(設定が不可能な場合を除く)。

また、base64 の実装の中には非ゼロの pad ビットを含むエンコードデータの拒否を許さないものがあるため、パーサは非ゼロの pad ビットが存在する場合に失敗すべきではありません(設定が不可能な場合を除く)。

本仕様は [RFC4648] の Sections 3.1 および 3.3 の要件を緩和するものではありません。したがって、パーサは base64 アルファベット外の文字やエンコードデータ内の改行に対しては失敗しなければなりません。

4.2.8. ブール値の解析

ASCII 文字列 input_string が与えられたとき、ブール値を返します。input_string は解析された値を削除するように変更されます。

  1. input_string の最初の文字が "?" でない場合、解析を失敗させます。

  2. input_string の最初の文字を破棄します。

  3. input_string の最初の文字が "1" に一致する場合、その文字を破棄し、true を返します。

  4. input_string の最初の文字が "0" に一致する場合、その文字を破棄し、false を返します。

  5. どの値にも一致しないため、解析を失敗させます。

4.2.9. 日付の解析

ASCII 文字列 input_string が与えられたとき、日付を返します。input_string は解析された値を削除するように変更されます。

  1. input_string の最初の文字が "@" でない場合、解析を失敗させます。

  2. input_string の最初の文字を破棄します。

  3. input_string に対して「整数または小数の解析」(Section 4.2.4) を実行した結果を output_date とします。

  4. output_date が小数である場合、解析を失敗させます。

  5. output_date を返します。

4.2.10. 表示文字列の解析

ASCII 文字列 input_string が与えられたとき、Unicode コードポイントの列を返します。input_string は解析された値を削除するように変更されます。

  1. input_string の最初の 2 文字が "%" に続いて DQUOTE でない場合、解析を失敗させます。

  2. input_string の最初の 2 文字を破棄します。

  3. byte_array を空のバイト配列とします。

  4. input_string が空でない間:

    1. input_string の最初の文字を消費し、それを char とします。

    2. char が %x00-1f または %x7f-ff の範囲にある場合(すなわち VCHAR または SP に含まれない場合)、解析を失敗させます。

    3. char が "%" である場合:

      1. octet_hex を input_string から 2 文字消費した結果とします。2 文字が存在しない場合、解析を失敗させます。

      2. octet_hex が %x30-39 または %x61-66(0-9 または 小文字 a-f)以外の文字を含む場合、解析を失敗させます。

      3. octet を hex デコードした結果とします(Section 8 of [RFC4648])。

      4. octet を byte_array に追加します。

    4. char が DQUOTE である場合:

      1. byte_array を UTF-8 としてデコードした結果を unicode_sequence とします。デコードに失敗した場合、解析を失敗させます(Section 3 of [UTF8] に準拠)。

      2. unicode_sequence を返します。

    5. それ以外で、char が "%" でも DQUOTE でもない場合:

      1. char を ASCII エンコーディングでバイトに変換した結果を byte とします。

      2. byte を byte_array に追加します。

  5. 閉じる DQUOTE を見つけずに input_string の終わりに到達したため、解析を失敗させます。


5. IANA に関する考慮事項

IANA は "Hypertext Transfer Protocol (HTTP) Field Name Registry" に次の注記を追加しました:

"Structured Type" 列は、該当する場合にフィールドの型(RFC 9651 に従う)を示し、"Dictionary"、"List"、または "Item" のいずれかになります。

フィールド名が ALPHA または "*" 以外の文字で始まる場合、それは Structured Fields のトークンとして表現できないため、それを参照するフィールド値へのマッピングと互換性がない可能性があることに注意してください。

レジストリに新しい列 "Structured Type" が追加されました。

Table 1 に列挙された既存のレジストリエントリそれぞれについて、示された Structured Type も追加されています。

表 1: 既存フィールド
フィールド名 Structured Type
Accept-CH List
Cache-Status List
CDN-Cache-Control Dictionary
Cross-Origin-Embedder-Policy Item
Cross-Origin-Embedder-Policy-Report-Only Item
Cross-Origin-Opener-Policy Item
Cross-Origin-Opener-Policy-Report-Only Item
Origin-Agent-Cluster Item
Priority Dictionary
Proxy-Status List

6. セキュリティに関する考慮事項

Structured Fields で定義される多くの型のサイズには上限が設定されていません。その結果、非常に大きなフィールドが(リソース消費を狙った)攻撃経路になり得ます。多くの HTTP 実装は、そのような攻撃を軽減するために、個々のフィールドやヘッダ/トレイラセクション全体のサイズに制限を設けています。

新しい HTTP フィールドを注入する能力を持つ当事者が、Structured Field の意味を変更することが可能です。状況によってはこれによりパースが失敗することがありますが、すべてのケースで確実に失敗させることはできません。

Display String 型は、未サニタイズのまま任意の Unicode コードポイントを含む可能性があります。たとえば、未割当のコードポイント、制御文字(NUL を含む)、非文字などを含むかもしれません。したがって、Display Strings を消費するアプリケーションは、表示前に信頼できないコンテンツをフィルタリングまたはエスケープする等の対策を検討する必要があります。詳細は [PRECIS] および [UNICODE-SECURITY] を参照してください。

7. 参考文献

7.2. 情報的参考文献

[HPACK]
Peon, R. and H. Ruellan, “HPACK: Header Compression for HTTP/2”, RFC 7541, DOI 10.17487/RFC7541, May 2015, <https://www.rfc-editor.org/info/rfc7541>.
[HTTP/2]
Thomson, M., Ed. and C. Benfield, Ed., “HTTP/2”, RFC 9113, DOI 10.17487/RFC9113, June 2022, <https://www.rfc-editor.org/info/rfc9113>.
[IEEE754]
IEEE, “IEEE Standard for Floating-Point Arithmetic”, IEEE Std 754-2019, DOI 10.1109/IEEESTD.2019.8766229, ISBN 978-1-5044-5924-2, July 2019, <https://ieeexplore.ieee.org/document/8766229>.
[PRECIS]
Saint-Andre, P. and M. Blanchet, “PRECIS Framework: Preparation, Enforcement, and Comparison of Internationalized Strings in Application Protocols”, RFC 8264, DOI 10.17487/RFC8264, October 2017, <https://www.rfc-editor.org/info/rfc8264>.
[RFC5234]
Crocker, D., Ed. and P. Overell, “Augmented BNF for Syntax Specifications: ABNF”, STD 68, RFC 5234, DOI 10.17487/RFC5234, January 2008, <https://www.rfc-editor.org/info/rfc5234>.
[RFC7493]
Bray, T., Ed., “The I-JSON Message Format”, RFC 7493, DOI 10.17487/RFC7493, March 2015, <https://www.rfc-editor.org/info/rfc7493>.
[RFC8259]
Bray, T., Ed., “The JavaScript Object Notation (JSON) Data Interchange Format”, STD 90, RFC 8259, DOI 10.17487/RFC8259, December 2017, <https://www.rfc-editor.org/info/rfc8259>.
[UNICODE-SECURITY]
Davis, M. and M. Suignard, “Unicode Security Considerations”, Unicode Technical Report #36, September 2014, <https://www.unicode.org/reports/tr36/tr36-15.html>.
最新版は <https://www.unicode.org/reports/tr36/> にあります。

Appendix A. よくある質問

A.1. なぜJSONではないのか?

以前の Structured Fields の提案は JSON に基づいていました [RFC8259]。しかし、HTTP フィールドに適した形に制約すると、送信者と受信者が特定の追加処理を実装する必要がありました。

例えば、JSON には大きな数値や重複メンバを持つオブジェクトに関する仕様上の問題があります。これらの問題を避けるための助言(例:[RFC7493])はありますが、それらに依存することはできません。

同様に、JSON の文字列はデフォルトで Unicode 文字列であり、比較などにおいて相互運用性の問題を引き起こす可能性があります。実装者に不要な非ASCIIコンテンツを避けるよう助言することはできますが、それを強制するのは困難です。

別の例として、JSON は任意の深さにコンテンツをネストできます。結果として必要となるメモリの確保が(組み込みや制約のあるサーバ環境などで)不適切な場合があるため、何らかの形で制限する必要があります。しかし既存の JSON 実装にはそのような制限がなく、仮に制限を指定しても、あるフィールド定義がそれを破る必要に迫られる可能性があります。

JSON が広く採用・実装されているため、そのような追加制約をすべての実装に課すのは難しく、一部のデプロイではそれらを強制できず、相互運用性を損なう可能性があります。簡潔に言えば、見た目が JSON の場合、人々はフィールド値に対して JSON パーサ/シリアライザを使いたくなってしまいます。

Structured Fields の主な目標が相互運用性の向上と実装の簡素化であるため、これらの懸念は専用のパーサとシリアライザを必要とするフォーマットへ導きました。

加えて、多くの参加者が、HTTP フィールド内で JSON が "見た目に合わない" と感じていた点もありました。


Appendix B. 実装ノート

この仕様の汎用的な実装は、トップレベルの serialize (Section 4.1) と parse (Section 4.2) 関数を公開するべきです。これらは関数である必要はなく、たとえば各トップレベル型に対するメソッドを持つオブジェクトとして実装されることもあり得ます。

相互運用性のため、汎用実装は完全でありアルゴリズムに厳密に従うことが重要です;詳細は Section 1.1 を参照してください。これを助けるため、共通のテストスイートがコミュニティによって維持されています:<https://github.com/httpwg/structured-field-tests>。

実装者は、辞書(Dictionaries)とパラメータ(Parameters)が順序を保持するマップであることに留意すべきです。これらのデータ型の順序が意味を持たないフィールドもありますが、必要なアプリケーションで利用できるように順序情報を公開するべきです。

同様に、実装はトークン(Tokens)と文字列(Strings)の区別を保持することが重要です。多くのプログラミング言語では他の型にうまくマッピングされる組み込み型が存在しますが、これらの型を分離して扱うためにラッパーの "token" オブジェクトを作成したり、関数のパラメータで区別を明示することが必要になるかもしれません。

シリアライズアルゴリズムは、すべての場合において Section 3 で定義されたデータ型に厳密に限定されるよう定義されているわけではありません。例えば、小数(Decimals)はより広い入力を受け取り許容される値へ丸めるよう設計されています。

実装は各型に定義された最小限を満たす範囲で、さまざまな構造のサイズを制限して構いません。構造が実装の上限を超えた場合、その構造のパースまたはシリアライズは失敗します。


Appendix C. ABNF

このセクションでは、Augmented Backus-Naur Form (ABNF) 表記法を用いて Structured Fields の期待される構文を示します [RFC5234]。ただし、すべての要件をとらえられるわけではないため、これを用いて構文検証を行うことはできません。

このセクションは非規範的です。パースアルゴリズムと ABNF の間に不一致がある場合は、規定されたアルゴリズムが優先されます。

sf-list       = list-member *( OWS "," OWS list-member )
list-member   = sf-item / inner-list

inner-list    = "(" *SP [ sf-item *( 1*SP sf-item ) *SP ] ")"
                parameters

parameters    = *( ";" *SP parameter )
parameter     = param-key [ "=" param-value ]
param-key     = key
key           = ( lcalpha / "*" )
                *( lcalpha / DIGIT / "_" / "-" / "." / "*" )
lcalpha       = %x61-7A ; a-z
param-value   = bare-item

sf-dictionary = dict-member *( OWS "," OWS dict-member )
dict-member   = member-key ( parameters / ( "=" member-value ))
member-key    = key
member-value  = sf-item / inner-list

sf-item   = bare-item parameters
bare-item = sf-integer / sf-decimal / sf-string / sf-token
            / sf-binary / sf-boolean / sf-date / sf-displaystring

sf-integer       = ["-"] 1*15DIGIT
sf-decimal       = ["-"] 1*12DIGIT "." 1*3DIGIT
sf-string        = DQUOTE *( unescaped / "%" / bs-escaped ) DQUOTE
sf-token         = ( ALPHA / "*" ) *( tchar / ":" / "/" )
sf-binary        = ":" base64 ":"
sf-boolean       = "?" ( "0" / "1" )
sf-date          = "@" sf-integer
sf-displaystring = "%" DQUOTE *( unescaped / "\" / pct-encoded )
                   DQUOTE

base64       = *( ALPHA / DIGIT / "+" / "/" ) *"="

unescaped    = %x20-21 / %x23-24 / %x26-5B / %x5D-7E
bs-escaped   = "\" ( DQUOTE / "\" )

pct-encoded  = "%" lc-hexdig lc-hexdig
lc-hexdig = DIGIT / %x61-66 ; 0-9, a-f

Appendix D. RFC 8941 からの変更点

この "Structured Field Values for HTTP" 規格の改訂では、次の変更が行われました:

  • Date 構造化型を追加しました。 (Section 3.3.7)

  • 新しい Structured Fields の定義において ABNF の使用を推奨することをやめました。 (Section 2)

  • ABNF を情報的な付録に移動しました。 (Appendix C)

  • "Hypertext Transfer Protocol (HTTP) Field Name Registry" に "Structured Type" 列を追加しました。 (Section 5)

  • パース失敗時の取り扱いを洗練しました。 (Section 4.2)

  • Display String 構造化型を追加しました。 (Section 3.3.8)


謝辞

本仕様の作成に際して詳細なフィードバックと丁寧な考察を提供してくれた Matthew Kerwin に深く感謝します。

また、Ian Clelland、Roy Fielding、Anne van Kesteren、Kazuho Oku、Evert Pot、Julian Reschke、Martin Thomson、Mike West、および Jeffrey Yasskin の貢献にも感謝します。


著者の連絡先

Mark Nottingham
Cloudflare
Prahran, VIC
Australia
EMail: mnot@mnot.net
URI: https://www.mnot.net/
Poul-Henning Kamp
The Varnish Cache Project
EMail: phk@varnish-cache.org