Web APIを開発していると、HTTPのヘッダについてRFCにおける規約を確認しなきゃいけない場面がたまにあるので、今回調べたことをまとめた。
任意のUS-ASCII 任意の印字可能なUS-ASCII)
●obs-text
●%x80-FF (US-ASCII以外のレンジ)
●ただし、obs-text(及びobs-fold)は廃止された文法である
なので、
HTTP/1.1のRFC
HTTP/1.1のRFCといえば、長らくRFC2616であったが、2014年にRFC7230〜7239が発行され、2616は廃止された。 ●RFC2616 ●ハイパーテキスト転送プロトコル -- HTTP/1.1 ●RFC7230〜RFC2739 ●HTTP/1.1 — RFC 7230 〜 7235 — 日本語訳 両者の変更点については、RFC 723xの付録に記述されているので参照のこと。Content-MD5が廃止されたり、ちょいちょい面白い。文章としても723xの方が分かりやすくなっているので、一度目を通しておくことをお勧めする。 ●HTTP/1.1 が更新された | The Long Wait ●あたらしいHTTPの話をしよう // Speaker Deck ●LWSとHTTPヘッダインジェクション | MBSD BlogRFC的に、ヘッダの名前と値って何が使えるんでしたっけ?
RFC 7230のABNFによるとheader-field = field-name ":" OWS field-value OWS field-name = token field-value = *( field-content / obs-fold ) field-content = field-vchar [ 1*( SP / HTAB / field-vchar ) field-vchar ] field-vchar = VCHAR / obs-text obs-fold = OWS CRLF 1*( SP / HTAB ) ; obsolete line folding ; see Section 3.2.4かつ、 ●token ●"!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA ●(VCHARからセパレータを抜いたもの) ●VCHAR ●%x21-7E (
箇所 | 利用可能文字種 |
---|---|
ヘッダ名(field-name) | token (アルファベット、数字、一部の記号) |
ヘッダ値(firld-value) | |
となる。念のためUS-ASCII 印字可能なUS-ASCII(%x21-7E)は以下の表の21-7Eだ。 (man asciiより)
00 nul 01 soh 02 stx 03 etx 04 eot 05 enq 06 ack 07 bel
08 bs 09 ht 0a nl 0b vt 0c np 0d cr 0e so 0f si
10 dle 11 dc1 12 dc2 13 dc3 14 dc4 15 nak 16 syn 17 etb
18 can 19 em 1a sub 1b esc 1c fs 1d gs 1e rs 1f us
20 sp 21 ! 22 " 23 # 24 $ 25 % 26 & 27 '
28 (29) 2a * 2b + 2c , 2d - 2e . 2f /
30 0 31 1 32 2 33 3 34 4 35 5 36 6 37 7
38 8 39 9 3a : 3b ; 3c < 3d = 3e > 3f ?
40 @ 41 A 42 B 43 C 44 D 45 E 46 F 47 G
48 H 49 I 4a J 4b K 4c L 4d M 4e N 4f O
50 P 51 Q 52 R 53 S 54 T 55 U 56 V 57 W
58 X 59 Y 5a Z 5b [ 5c \ 5d ] 5e ^ 5f _
60 ` 61 a 62 b 63 c 64 d 65 e 66 f 67 g
68 h 69 i 6a j 6b k 6c l 6d m 6e n 6f o
70 p 71 q 72 r 73 s 74 t 75 u 76 v 77 w
78 x 79 y 7a z 7b { 7c | 7d } 7e ~ 7f del
ヘッダに日本語を突っ込みたいんですけどどうにかならないですか?
RFC2616時代の見解はStudying HTTPさんが詳しい。 ●http://www.eonet.ne.jp/~h-hash/header.html#Abstract ●http://www.eonet.ne.jp/~h-hash/header.html#Utf8InParam RFC2616では、field-valueはTEXT(制御文字以外のOCTET)が使えるが、TEXTでUS-ASCII外の文字を使っていいのは、RFC2047形式でエンコードされた時のみである。2047形式というのはこういうやつ。 要するに生のUTF-8なんかはヘッダ値に載せてはいけないのだ。 でもそれだとContent-Disposition
でのファイル名指定とかでこまるよね、ということでRFC5987でUTF-8をエンコードするための拡張が定義されている。だけどこれは、それこそContent-Disposition
ヘッダなどのみで使える、=つなぎのパラメータに対して適用するものなので、いつでも使えるわけではない。
例 Rails - Content-Disposition の日本語問題 - Qiita より
Content-Disposition: attachment; filename*=UTF-8''foo-%c3%a4-%e2%82%ac.html一方、RFC7230ではどうなのかというと 新たなヘッダ値の構文は︵中略︶通例的に,US-ASCII 文字の範囲に拘束される。 より広範囲の文字を必要とするヘッダは、[RFC5987]にて定義されるものなどの,符号化方式を利用できる。 RFC 7231 — HTTP/1.1: Semantics and Content ︵日本語訳︶ 他には、 歴史的に,HTTP は、 ISO-8859-1 charset [ISO-8859-1] のテキストによるヘッダ内容を許容し、他の charset のサポートは, [RFC2047] 符号化方式の利用を通してのみ許容してきた。 実施においては、大部分の HTTP ヘッダ値は﹇ US-ASCII charset [USASCII] のサブセット ﹈のみを利用している。 新たに定義されるヘッダは、そのヘッダ値を,US-ASCII オクテットに制限するべきである。 受信者は、﹇ ヘッダ内容 内の他のオクテット︵ obs-text ︶ ﹈を,不透明なデータとして扱うべきである。 RFC 7230 — HTTP/1.1: Message Syntax and Routing ︵日本語訳︶ などとある。RFC2616との違いは、利用可能な符号化方式がRFC2047に限定されず、RFC5987など、他のエンコードが認められていることくらいで、基本的にUS-ASCIIオクテットに制限﹁すべき﹂とのことだ。とはいえ既存の符号化手法は利用箇所が限られているし、どうしても︵独自の︶ヘッダでマルチバイトを送りたいなら、適当にパーセントエンコーディングなどで送ればいいという感じだろうか。
RFC的に、複数の同名ヘッダってどう扱うのが正解?
こういうやつ。X-Foo: Bar X-Foo: BazRFC7230 Section 3.2.2 によると、 ●受信者は、複数の同名ヘッダがある時、ヘッダ値を現れた順にカンマで結合した一つのヘッダとして扱ってよい。 ●送信者は、カンマ区切りで結合してもよい時以外、複数の同名ヘッダを送信してはならない。 ●ただしSet-Cookieヘッダは同じレスポンスに複数回現れることが多く、カンマ結合すると意味論が変わってしまうので例外である。
RFC的に、ヘッダの区切りの改行ってCRLF(\r\n)でしたよね
そうとも限らない。実際にはCRやLF単体がヘッダ区切り文字として認識されている。 ●RFCはLF単体をヘッダ区切り文字として認めている ●多くのブラウザはRF単体を区切り文字として認めている 参考 LWSとHTTPヘッダインジェクション | MBSD BlogRFC的に、まさかヘッダの値って改行できないですよね?
実は、RFC2616ではLWSという仕様を使うことで、複数行に渡るヘッダ値を表現できた。 以下の例は、﹁LWS(改行+スペースまたはタブ)﹂により、2行に渡るX-Fooヘッダ値を表現できる。X-Foo: Foo
[sp] X-Bar: Bar
だが、〜IE11などのブラウザはLWSをそのように扱わず、2行目を別のヘッダとして扱うため、レスポンス分割攻撃などが可能であった。そのような問題があったため、RFC7230では、LWSの仕様はobs-foldに改められ、(message/httpメディアタイプを除き)非推奨になった。
●送信者は、obs-foldに合致するfield-valueを生成してはならない
●受信者は、400 Bad Requestを応答しリクエストを却下するか、obs-foldを一個以上のSP(空白文字)で置換しなくてはならない
●UAは、obs-foldを一個以上のSP(空白文字)で置換しなくてはならない
結論‥できなくなりました