ActionDispatch::Response#charsetの改善を試みた

https://github.com/rails/rails/pull/35549 を出したという話です。

Content-Type

HTTP レスポンスでは、Content-Type ヘッダを利用して MIME-Type を示せますが、このときMIME-Type 以外にも幾つかの情報を含められます。

書式としては Content-Type: mime/type; foo=bar; bar=baz みたいな感じです。

ActionDispatch::Response

ActionDispatch::Response というのは、主に Rails で HTTP レスポンスを表すために使われているクラスです。ActionDispatch::Response クラスには、上述した Content-Type の charset で指定されている値を返すメソッドとして #charset が定義されています。

text/csv

HTTP レスポンスで CSV を返すときは、Content-Type に text/csv という MIME-Type を指定することでそれを表現できます。 このとき、text/csv について述べた RFC7111 によると、他に charset と header というパラメータを任意で含められます。header というのは、その CSV のヘッダー行の有無を示すためのパラメータで、present または absent のいずれかの値を取れます。

例を挙げると、Content-Type に text/csv; charset=sjis; header=present のように指定できます。

rails/rails#35549

先述した Content-Type を持つ HTTP レスポンスで ActionDispatch::Response#charset を呼び出したとき、"sjis" が返ってきてほしいところですが、現時点で最新版の rails/rails で試すと "sjis; header=present" という値が返されることが分かりました。どうやら ;charset= 以降の部分を全て charset の値として扱う実装になっているようです。

そういう訳で、この問題に対して修正を加えたのが https://github.com/rails/rails/pull/35549 です。; と = で区切られている一連のパラメータを真面目に Hash として組み立てた上で、charset パラメータの値を調べるようにしています。

なお、HTTP のヘッダ部分の書式の仕様が示されている RFC 7231 によると、このパラメータの値部分はクォートすることも出来るようだと知人に教えてもらいましたが、クォートへの対応はこの Pull Request の主旨からは外れると思うので、とりあえずこの Pull Request では対応しませんでした。この Pull Request が merge されたら、また別途 Pull Request を出そうと思います。

追記

正規表現でのパースを提案され、そうするのであればクォート対応も同じ Pull Request に入れてしまおうということで全部入りの Pull Request に変更して、後日 merge されました。めでたしめでたし。恐らく Rails 6.0.0.beta3 辺りに組み込まれることでしょう。