2026.09.23 泊まれる場所4,223 立ち寄りスポット59,693 営業中を確認したキャンプ場1,005 note を読む

Content-Typeヘッダーの正体|CSVを開いたら、ダウンロードする前のプレビューだけ文字化けしていた

問い合わせ一覧をCSVで書き出して、タブでそのまま開いた。
中身は正しいのに、日本語だけ記号の羅列になっていた。
壊れていたのはファイルではなく、応答ヘッダーのほうだった。

問い合わせフォームの一覧をCSVで書き出して、確認のつもりでブラウザのタブでそのまま開いた。
中身はちゃんと入っているのに、日本語のところだけ「豢ョ蜷阪@縺セ縺・」のような記号の羅列になっている。
保存してExcelで開き直すと、今度は普通に読める。
壊れていたのはファイルではなく、タブの中の見え方のほうだった。

ブラウザは拡張子を見ていない

てっきり「.csv」という拡張子を見て、ブラウザが表示のしかたを決めているのだと思っていた。
実際に効いているのはそこではなく、サーバーが返す応答ヘッダーの中の一行だった。

ファイルを送り返すとき、サーバーは中身のデータと一緒に「これはこういう種類のものです」という札を一枚添えている。
それがContent-Typeヘッダーで、値の形式は電子メールの添付ファイルの分類(MIMEタイプ)がそのまま流用されている。
拡張子はファイル名の飾りにすぎず、ブラウザが実際に頼りにしているのはこの札のほうだった。

文字化けの犯人は、札に書かれていなかった一言だった

CSVを返す処理を見直すと、Content-Typeは「text/csv」とだけ書かれていた。
ここに足りなかったのが「charset=UTF-8」という一言で、文字コードの指定がない状態だった。

指定がないとき、ブラウザは自分の判断で文字コードを推測してタブに描画する。
ファイルの中身は正しくUTF-8で書かれていたのに、ブラウザの推測がたまたま外れて、日本語の部分だけ別のコードとして解釈されていた。
ダウンロード自体はバイト列をそのまま保存するだけなので壊れようがなく、開き直すと正常に見えたのはそのためだった。
「text/csv; charset=UTF-8」と一言添えるだけで、タブの中の文字化けは消えた。

この札は、無視されることもある

調べていて意外だったのは、Content-Typeという札が絶対ではないという点だった。
ブラウザは中身の先頭バイトを実際にのぞき見て、札の内容と食い違っていれば自分の判断を優先することがある。
これはMIMEスニッフィングと呼ばれる挙動で、古いサイトの設定漏れを大目に見るための仕組みらしい。

ただしこれは同時に、思わぬ抜け道にもなる。
画像として置いたつもりのファイルが実は仕込まれたスクリプトで、ブラウザが中身を見て「これは実行できそうだ」と判断してしまう事故が過去に起きたと知って背筋が伸びた。
これを防ぐための札も別に用意されていて、「X-Content-Type-Options: nosniff」を付けると、ブラウザは中身を見ずに札の記載どおりに扱うようになる。

結局、見ているのは中身ではなく申告のほう

ファイルの実体は変わっていないのに、添える一言の有無だけで見え方がまるごと変わる。
普段は意識しない応答ヘッダーの中に、そんな小さいけれど効く一行が隠れていた。

いまはCSVを返すすべての箇所にcharsetを付けて回った。
拡張子を正しく付けることに気を配っていたわりに、その裏で交わされている申告の中身までは見ていなかったのだと気づいた回だった。

まる子パパ

まる子パパ

会社員。受託開発のエンジニアで、その前は寿司職人・長距離運転手・農業。 技術ブログは、キャンプの合間に踏んだバグと、AI・Web開発の運用の失敗を書いています。 このサイト自体が、開発に携わっているCMS「コンテナ」の稼働中の実例です。 このサイトについて

—

ほかにも書いています

note・スタンプ

※noteは運営者が個人で書いているものです。

技術ブログ一覧へ戻る