WebサイトのContent-Disposition検証|filename・filename*と日本語ファイル名を安全に扱う方法
WebサイトからPDFやCSVをダウンロードしたとき、保存先では意味のない英数字になったり、日本語のファイル名が文字化けしたりすることがあります。反対に、表示された名前をそのまま信用すると、パス区切り文字や危険な拡張子が保存処理へ入り込む可能性があります。原因を切り分けるには、画面のラベルやURLではなく、サーバーが返したHTTPレスポンスのヘッダー、実際に保存された名前、本文の内容を同じ記録で照合します。
この記事では、HTTPレスポンスヘッダーのContent-Disposition(RFC 6266)を対象に、filenameとfilename*を併用して日本語名を伝える方法と、MDNのContent-Disposition解説が示すブラウザ差を含む検証手順を説明します。HTMLフォームのアップロードで使うmultipart/form-dataのパートヘッダーとは検証対象が違うため、最初に境界をはっきりさせます。
安全に保存名を決める要点は、レスポンスヘッダーのattachment・filename・filename*を確認し、UTF-8の拡張値を正しく復号したうえで、パス区切り・制御文字・予約名・不適切な拡張子を保存側で除去することです。古いユーザーエージェント向けにASCIIのfilenameをフォールバックとして添え、対応ブラウザではfilename*を優先させます。最後はChrome、Firefox、Safari、Edgeなどの実機で、保存名だけでなく拡張子、MIMEタイプ、本文のハッシュまで確かめます。
このテーマはHTTPの配信検証とダウンロードの運用に関係します。ダウンロード再開の範囲と本文長を確かめる場合は、HTTP範囲リクエストの検証で扱うRange・206・416の確認も役立ちます。ただし、Rangeは取得するバイト範囲、Content-Dispositionは保存や表示の扱いに関するメタデータという違いがあります。

図はブラウザやサーバー製品が一律に採用する手順ではありません。RFCの構文と安全上の注意、MDNが整理するブラウザの挙動を、Web運用で再現できる点検順に並べています。実際の保存名はOSやブラウザの仕様で変換されるため、ヘッダーの値だけで成功と判断しないでください。
本記事のポイント
- Content-DispositionはHTTPレスポンスの表示・ダウンロード方法を伝えるヘッダーで、multipart/form-dataの入力パートとは別の仕組みです。
- 日本語名はUTF-8をパーセントエンコードしたfilename*と、ASCIIのfilenameフォールバックを併記してブラウザ差に備えます。
- 保存前に名前を復号・正規化し、パス・制御文字・予約名・拡張子を確認してから、複数ブラウザで名前と本文の一致を実機検証します。
1. Content-DispositionはHTTPレスポンスの保存方法を伝える
Content-Dispositionは、サーバーが返すHTTPレスポンスに付けるヘッダーフィールドです。RFC 6266は、レスポンスのペイロードをどう処理するかを伝え、ローカルへ保存するときのファイル名などのメタデータを追加できるものと定義しています。attachmentなら通常は保存を促し、inlineならメディアタイプに従った既定の表示を示します。名前が指定されていても、ブラウザが必ず同じダイアログや保存名を採用するとは限りません。
ここでいう対象は、PDFやCSVなどを返すHTTPレスポンスのヘッダーです。HTMLフォームの送信やファイルアップロードで使うmultipart/form-dataでは、本文の各パートにContent-Disposition: form-data; name="..."; filename="..."のようなヘッダーが現れます。これはフォームのどの項目で、入力元がどの名前を送ったかを表す情報です。RFC 6266自身も、HTTPで送られるmultipart/form-dataのペイロード本文に現れるContent-Dispositionには適用しないと明記しています。
| 確認対象 | 置かれる場所 | 主な目的 | 検証する値 |
|---|---|---|---|
| ダウンロード応答 | HTTPレスポンスのヘッダー | 表示か保存か、保存時の候補名を伝える | ステータス、Content-Type、Content-Disposition、本文 |
| フォームのアップロード | multipart/form-dataの各パート | フォーム項目名と入力元ファイル名を伝える | boundary、name、filename、本文のサイズと種類 |
| メールやMIME | MIMEメッセージのパート | メッセージ本文や添付の扱いを伝える | 利用プロトコルの仕様と受信側の処理 |
この区別をしないまま、アップロードで受け取ったfilenameの扱いをダウンロード応答へコピーすると、入力値をそのまま出力ヘッダーへ反映する設計になりがちです。アップロード元の名前は表示用の候補にすぎず、保存先の決定、公開用の名前、応答ヘッダーの組み立ては別々に検証します。
2. filenameとfilename*を併記して日本語名を伝える
RFC 6266では、filenameとfilename*が保存名を組み立てる情報を提供します。RFC 6266は当時のRFC 5987を参照していますが、そのHTTP拡張値の仕様はRFC 8187が更新しています。filename*はこの拡張値を使い、ASCIIだけでは表しにくい文字を伝えられます。古いユーザーエージェントはfilename*を理解しないことがあるため、送信側は両方を用意し、対応する受信側はfilename*を優先します。
RFC 8187の拡張値は、charset'language'value-charsの形です。文字コードは必須で、言語タグは任意です。RFC 8187の送信者はUTF-8を使い、UTF-8のバイト列をパーセントエンコードします。日本語のスペースやスラッシュ、制御文字を「見た目のまま」ヘッダーへ連結するのではなく、値を生成する専用処理で組み立ててください。
たとえば「営業報告書_2026-09.csv」を候補名にするレスポンスは、次のように書けます。1行のHTTPヘッダーとして送信し、表示上の改行を実際のヘッダー値へ入れないことが重要です。
Content-Type: text/csv; charset=utf-8
Content-Disposition: attachment; filename="sales-report-2026-09.csv"; filename*=UTF-8''%E5%96%B6%E6%A5%AD%E5%A0%B1%E5%91%8A%E6%9B%B8_2026-09.csv
ASCIIのfilenameは、古い環境でも意味が分かる安全なフォールバックにします。日本語を無理にLatin-1として扱ったり、filename側にパーセントエスケープを入れたりする方法は避けます。MDNが説明するように、filename中のパーセントエスケープをデコードするかどうかはブラウザで一貫しません。日本語を伝える役割はfilename*へ持たせます。
なお、RFC 8187はRFC 2047の「=?UTF-8?...?=」形式をこの拡張値の方式に含めていません。メールの件名などで見かけるエンコード語を、HTTPの保存名へ流用しないでください。ヘッダーを複数のライブラリが組み立てる場合は、どの層がUTF-8化とパーセントエンコードを担当するかを固定し、二重エンコードをテストで見つけます。
| ヘッダー | 確認内容 | 起きやすい誤解 |
|---|---|---|
| Content-Disposition | attachmentまたはinlineと、重複しない名前パラメーター | これだけで保存先や保存名が強制される |
| filename | ASCIIのフォールバック名、引用符、拡張子とMIMEの整合 | 日本語をそのまま入れれば全ブラウザで同じになる |
| filename* | UTF-8''と正しいパーセントエンコード、1つの値 | URLエンコードした文字列をそのままfilenameへ入れればよい |
| Content-Type | 実際の本文のメディアタイプと拡張子の整合 | 拡張子だけでファイルの種類を判定できる |
3. 保存側でパス・制御文字・拡張子を検証する
RFC 6266は、ヘッダーが示す名前を「助言的な値」として扱うよう受信側へ求めています。保存処理は、ヘッダーに書かれたパスへ直接書き込んではいけません。最後のパス要素だけを使う、保存先ディレクトリをアプリケーション側で固定する、OSごとの予約名や禁止文字を置換する、といった境界を設けます。受信した名前が安全に見えても、サーバーからの入力である限り信頼しない設計が基本です。
ここでいうサニタイズは、独自のダウンロードクライアントやサーバー側の保存処理で実装する境界です。一般のWebサイト運営者が利用者のブラウザ内部の保存処理を変更できるわけではありません。Webサイト側では、応答へ出す候補名を安全に整えてから符号化し、ブラウザが実際に採用した名前は実機テストで確認します。
最低限、次の順で名前を処理します。
- デコードと正規化を決める。
filename*をUTF-8として復号し、Unicodeの正規化、前後の空白、同じ見た目の文字の扱いを方針化します。復号に失敗した値は、推測して修復するより安全なASCII名へフォールバックします。 - パスの意味を消す。スラッシュ、バックスラッシュ、ドットだけの要素、親ディレクトリ表記、制御文字、改行、NULを除外します。RFC 6266が例示するように、フォルダー名を信用せず、最後の要素だけを候補にする方法もあります。
- OSとシェルの特殊性を避ける。
.、..、~、デバイス名、先頭や末尾の空白、表示を混乱させる文字列を置換します。保存名をシェルコマンドへ連結せず、APIの引数として渡します。 - 拡張子を本文と照合する。受信した拡張子を無条件に採用せず、宣言されたContent-Type、マジックナンバーやパーサーの検査、許可された拡張子を照合します。RFC 6266は、拡張子をメディアタイプの判断に使う受信者へ、安全な拡張子を使うよう注意しています。
ここで「危険な文字を除く」は、単に記号を削る正規表現だけを意味しません。例えば、report.pdf.exeの途中にある.pdfだけを見てPDFと誤認したり、右から左へ表示する文字によって画面上の名前を誤認したりする可能性があります。名前、拡張子、本文の形式を別のフィールドで扱い、保存後に利用者へ見せる値も再度エスケープします。実行可能な拡張子を許可する必要がある場合は、保存ディレクトリの実行権限と隔離を含めて設計します。
ファイルの中身を検査する処理と、ダウンロードを開始させるHTTPヘッダーの処理も分離します。Content-Dispositionの値を整えただけで、本文が安全になったり、マルウェアが無害化されたりするわけではありません。ウイルススキャン、文書パーサー、アクセス制御、ログ記録はそれぞれの目的で実施します。
4. 保存名の検証は4段階で実機まで確かめる
実装後は、次の4段階を一つのテストケースにします。入力名、日本語名、長い名前、空白、絵文字、予約名、二重拡張子、壊れたパーセントエンコードを用意し、応答ヘッダーと保存結果を同じIDで結びます。
- 応答を確認する。HEADだけに頼らず、実際のGETでステータス、Content-Type、Content-Disposition、Content-Lengthまたは転送完了、ETagを保存します。CDN経由とオリジン直結で値が変わるなら、どの経路を利用者が通るかを固定して比較します。
curl -sS -D headers.txt -o downloaded.bin https://example.test/files/reportのように、ヘッダーと本文を分けて取得すると再検証しやすくなります。 - 名前を符号化する。送信側は安全なASCIIフォールバックを作り、表示したいUnicode名をUTF-8のバイト列へ変換して
filename*へ入れます。パーセントエンコードはRFC 8187のvalue-charsに従い、UTF-8''の区切り、スペースの%20、小文字・大文字の16進表記を実装間で統一します。受信側テストでは一度だけ復号されることも確認します。 - 危険な文字を除く。復号後の値からパス区切り、制御文字、前後の空白、予約名、不要なドット、シェルで意味を持つ文字を除去または置換します。拡張子はContent-Typeや本文の検査結果と照合し、許可外なら安全な固定拡張子へ切り替えるか、保存を止めて利用者へ知らせます。
- 実機で保存する。Chrome、Firefox、Safari、Edgeの最新版と、実際に利用するOSで、直接リンク、別タブ表示、HTMLの
download属性、認証付きURL、CDN経由を試します。保存ダイアログの候補名、実際の名前、拡張子、本文のバイト数、ハッシュを記録し、ブラウザが名前を置換した場合も失敗と決めつけず、要件を満たすかを判定します。
ブラウザの挙動には差があります。MDNは、filename*を理解するブラウザではfilenameより優先されること、同じ生成元のURLではChromeやFirefox 82以降がHTMLのdownload属性をContent-Disposition: inlineより優先する場合があることを説明しています。Safariを含むすべての環境で同じ優先順位になるとは限らないため、ヘッダーの検証とリンクのUIテストを分けます。
テスト記録には、URL、リクエストの認証状態、Acceptヘッダー、Content-Dispositionの生値、復号後の候補名、実際の保存名、MIMEタイプ、サイズ、SHA-256、ブラウザとOS、実施日時を残します。名前だけ一致して本文が違うケースや、本文は同じでも拡張子が不適切なケースを、別々の失敗として扱えるようにします。
| ケース | 合格条件 | 切り分ける問題 |
|---|---|---|
| 日本語名の直接ダウンロード | 候補名が読め、本文のハッシュが期待値と一致する | filename*の復号、OSの禁止文字、文字コード |
| filename*非対応を想定した環境 | ASCIIフォールバックで内容を識別できる | filenameの引用符、空白、危険な拡張子 |
| inlineとdownload属性 | 表示・保存の要件を環境ごとに説明できる | ブラウザの優先順位、同一生成元、リンク実装 |
| 認証付き・CDN経由 | 同じヘッダー、本文、保存名の関係を証跡で追える | キャッシュ、署名URL、ヘッダーの書き換え |
5. 配信経路と運用記録を一つの検証表にする
Content-Dispositionを正しく生成しても、CDN、リバースプロキシ、ストレージ、アプリケーションのどこかがヘッダーを削除・置換すれば、利用者が受け取る値は変わります。オリジンのログだけを見て成功とせず、公開URLから実際に返ったヘッダーを確認します。条件付きGETやCDNキャッシュの再検証を切り分けるときは、ETag・Last-Modified・304の照合手順も参照し、ヘッダーの世代と本文の世代を混同しないようにします。
変更管理では、保存名のポリシー、許可する拡張子、フォールバック名の形式、対象ブラウザ、担当者、次回レビュー日を台帳に残します。新しい帳票を追加するときだけでなく、ファイル保管先、CDN、リンク形式、認証方式、文書生成ライブラリを変更したときも、同じ4段階のテストを再実施します。HTTPの圧縮やVaryを経路ごとに確認する場合は、WebサイトのHTTP圧縮検証と同じく、要求条件・応答ヘッダー・本文を一組で保存します。
ファイルを公開する運用では、名前が読めることだけを成果にしないことが大切です。利用者が保存したファイルを開けるか、想定外の場所へ書き込まれないか、内容と拡張子が一致するか、配信経路の変更後も同じ結果になるかを確認します。更新責任者、承認、廃止、旧URLの扱いまで含めた運用では、ページや資料のライフサイクルも記録してください。
よくある質問
Content-Dispositionはmultipart/form-dataのfilenameと同じですか?
同じ名前のパラメーターが使われますが、この記事の対象はHTTPレスポンスのヘッダーです。multipart/form-dataでは、本文を区切る各パートにフォーム項目名やアップロード元のファイル名を記録します。ダウンロード応答のattachment、filename*、保存先の安全性を、そのままアップロード処理の仕様だと解釈しないでください。
filenameとfilename*は両方指定すべきですか?
複数のユーザーエージェントへ対応する必要があるなら、ASCIIのfilenameをフォールバックとして添え、UTF-8で表したfilename*を併記する方法が基本です。RFC 6266は両方を理解する受信側がfilename*を選ぶことを推奨しています。ただし、実際のブラウザがどの値を使ったかは、保存結果を確認してください。
filename*の日本語が文字化けしたら、filenameに日本語を入れれば直りますか?
必ず直るとは限りません。filenameへ日本語を直接入れると、環境によって解釈が変わり、古い実装や中継で壊れる可能性があります。UTF-8とRFC 8187の拡張値を使ったfilename*を生成し、ASCIIのフォールバック名は別に用意します。送信側の二重エンコードと、受信側の復号失敗も記録してください。
Content-Dispositionに安全なファイル名を書けば、パストラバーサルは防げますか?
防げません。RFC 6266が示すように、受信側は名前を助言として扱い、保存先を固定し、パス要素や制御文字、予約名を処理する必要があります。サーバーが返す名前をクライアントが保存するときも、アップロードで受け取った名前をサーバーが再利用するときも、入力値をそのままファイルパスへ連結しないでください。
inlineなら必ずブラウザ内に表示され、attachmentなら必ず保存ダイアログが出ますか?
必ずではありません。RFC 6266はinlineを既定処理、attachmentを保存の促しとして説明しますが、実際のブラウザ、メディアタイプ、リンクのdownload属性、認証やポリシーの影響を受けます。対象ブラウザとOSで、直接アクセスとリンク操作の両方をテストしてください。
ファイル名のテストで名前が一致すれば合格ですか?
不十分です。保存名、拡張子、Content-Type、本文の実バイト数、ハッシュ、保存場所を合わせて確認します。名前が正しくても本文が別の版、HTMLのエラーページ、圧縮表現のままということがあります。検証記録にブラウザ、OS、CDN経路、取得日時を残すと、同じ現象を再現できます。
資料や帳票の保存名、拡張子、CDN経由の応答を個別に点検し、ブラウザで再現できる検証表へ整えたい場合は、ファネルAiへご相談ください。対象URLと利用者の保存環境を確認し、配信・保存・運用の改善点を整理します。