技術メモなど

業務や日々のプログラミングのなかで気になったことをメモしています。PHP 成分多め。

ファイルを強制ダウンロードさせるヘッダについて調べた

一般的なブラウザは、拡張子が html だったり jpg だったり pdf だったりといった表示可能なファイルを受け取ると、 問題なければそのままタブ内に表示しようとします。これをなんとかやめさせたい。何卒名前をつけて保存してほしい。
というわけでファイルのダウンロードを強制するレスポンスヘッダについて調べたところ色んな書き方があったので整理してまとめておきます。

ダウンロード形式に関わるヘッダは、 Content-Type Content-Disposition の2つがあり、
調べていると以下の3つの指定がよく使われているのが見つかります。

  • Content-Type: application/force-download
  • Content-Type: application/octet-stream
  • Content-Disposition: attachment

Content-Type: application/force-download

Content-Type の MIMEタイプに application/force-download を指定すると、ファイル形式に関係なくダウンロードを開始させられます。

<?php

header('Content-Type: application/force-download');

$file_path = './test.txt';
readfile($file_path);

一方で、force-download というMIMEタイプは正式には存在せず、IANAのMIMEタイプ一覧にも載っていません。
https://www.iana.org/assignments/media-types/media-types.xhtml

force-downloadは慣習的に使われているMIMEタイプであり、公式に定義されたものではありません。 なぜ強制ダウンロードされるかというと、Content-Type に未知のMIMEタイプを指定すると、当該ファイルを実行しようとせずダウンロードさせるというブラウザ側の仕様によります。 実際 force-download を使わず、hogefoo など適当な文字列を指定しても強制ダウンロードになります。

<?php
// 適当なMIMEタイプを指定しても強制ダウンロードになる。
header('Content-Type: application/kinoko_vs_takenoko');

$file_path = './test.txt';
readfile($file_path);

当初の要件は満たせるものの、なんとなく気持ち悪さは残ります。

Content-Type: application/octet-stream

application/force-download の代わりに application/octet-stream を指定してもファイル形式に関係なくダウンロードを開始させられます。こちらは任意のバイナリデータを表す、公式なMIMEタイプです。

application/octet-stream
これは、バイナリファイルでは既定です。これは未知のバイナリ形式のファイルを表すものであり、ブラウザーはふつう実行したり、実行するべきか確認したりしません。

https://developer.mozilla.org/ja/docs/Web/HTTP/Basics_of_HTTP/MIME_types

<?php
header('Content-Type: application/octet-stream');

$file_path = './test.txt';
readfile($file_path);

application/force-download よりはまだしもですが、本来通知すべきMIMEタイプを指定していないという意味ではハックな感じがして、 これまたなんとなく気持ち悪さは残ります。

Content-Disposition: attachment

Content-Dispositionは、コンテンツをwebページの一部として表示するかダウンロードするかを示します。inlineを指定すればwebページとして、attachmentを指定すればダウンロードファイルとして、を表します。また、filenameパラメータで、ファイルの初期名を指定できます。

<?php
header('Content-Disposition: attachment; filename="hoge.txt"');

$file_path = './test.txt';
readfile($file_path); // ファイル名 hoge.txt としてダウンロードされる。

なんとなくよさげな感じがしますね。しかしContent-DispositionはHTTP1.1標準のヘッダではありません。

HTTP/1.1: Appendices 19.5

現代のブラウザのほとんどは対応していますが、古いIEだとこのヘッダ自体を無視してしまいます。humm..

まとめ

どの方法もつつけばモヤモヤする部分はありますが、まとめると

  • 基本は、Content-Disposition: attachmentを指定する。
  • 古いブラウザに対応しなければいけないときは、Content-Type: application/octet-stream を合わせて指定する。

としておくのが良いかと思います。