RFC的に、HTTPヘッダってどんな値を使えるんでしたっけ?のメモ

Web APIを開発していると、HTTPのヘッダについてRFCにおける規約を確認しなきゃいけない場面がたまにあるので、今回調べたことをまとめた。

HTTP/1.1のRFC

HTTP/1.1のRFCといえば、長らくRFC2616であったが、2014年にRFC7230〜7239が発行され、2616は廃止された。

両者の変更点については、RFC 723xの付録に記述されているので参照のこと。Content-MD5が廃止されたり、ちょいちょい面白い。文章としても723xの方が分かりやすくなっているので、一度目を通しておくことをお勧めする。

RFC的に、ヘッダの名前と値って何が使えるんでしたっけ?

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 (任意のUS-ASCII 任意の印字可能なUS-ASCII)
  • obs-text
    • %x80-FF (US-ASCII以外のレンジ)
    • ただし、obs-text(及びobs-fold)は廃止された文法である

なので、

箇所 利用可能文字種
ヘッダ名(field-name) token (アルファベット、数字、一部の記号)
ヘッダ値(firld-value) US-ASCII文字 印字可能なUS-ASCII

となる。念のため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さんが詳しい。

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: Baz

RFC7230 Section 3.2.2 によると、

  • 受信者は、複数の同名ヘッダがある時、ヘッダ値を現れた順にカンマで結合した一つのヘッダとして扱ってよい。
  • 送信者は、カンマ区切りで結合してもよい時以外、複数の同名ヘッダを送信してはならない。
  • ただしSet-Cookieヘッダは同じレスポンスに複数回現れることが多く、カンマ結合すると意味論が変わってしまうので例外である。

RFC的に、ヘッダの区切りの改行ってCRLF(\r\n)でしたよね

そうとも限らない。実際にはCRやLF単体がヘッダ区切り文字として認識されている。

  • RFCはLF単体をヘッダ区切り文字として認めている
  • 多くのブラウザはRF単体を区切り文字として認めている

参考 LWSとHTTPヘッダインジェクション | MBSD Blog

RFC的に、まさかヘッダの値って改行できないですよね?

実は、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(空白文字)で置換しなくてはならない

結論:できなくなりました

ちなみにGoのnet/httpはLWSにどう対応しているか?

HTTPリクエストの送信、HTTPレスポンスの送信において、ヘッダのfield-valueに含まれる改行文字(CR, LF)が各々スペースに置換されている。(http.Header.WriteSubset() at net/http/header.go) HTTPリクエストの受信時は?→LWSを処理する実装は見当たらないので単に無視されるようだ。(RFC7230の指示とは違うが) 行頭文字がSPであるヘッダがやってきた時にどうなるか?気になるので後で調べる

RFC的に、クライアントから変なヘッダを受け取ったサーバーはどうすればいいの?

PUTについては記述がある

[ PUT 要請内に受信された,認識できないヘッダ ]は、無視する(すなわち,リソース状態の一部として保存しない)べきである。 RFC 7231 — HTTP/1.1: Semantics and Content (日本語訳)

2015/8/4 追記

ヘッダ名に利用可能な文字種について修正しました。