PHPでzipファイルをダウンロードさせる仕様で取得したzipファイルが壊れている原因とは・・・?

ob_end_clean 関数を呼び出力バッファをクリアしないとUTF-8 BOM付きのPHPソースが実行された場合BOM(Byte Order Mark)が付加されてしまい、zipファイルが正しく生成されないことになってしまう。

実証コード

<?php
	$zip = new ZipArchive();
	$temp_dir = sys_get_temp_dir();
	$zip_name = 'test.zip';
	if (!$zip->open($temp_dir . $zip_name, ZIPARCHIVE::CREATE)){
		exit();
	}
	$zip->addFile('test.txt', '/hoge/test.txt');
	$zip->close();
	header('Content-Type: application/zip');
	header('Content-Length: '.filesize($temp_dir . $zip_name));
	header('Content-Disposition: attachment; filename="'.$zip_name.'"');
	readfile($temp_dir . $zip_name);
?>

ダウンロードしたファイルを解凍(Windowsのzip解凍アプリケーションによってはエラーになって書庫が参照できない)
$ unzip test.zip
Archive: test.zip
warning [test.zip]: 3 extra bytes at beginning or within zipfile
(attempting to process anyway)
warning: stripped absolute path spec from /hoge/test.txt
inflating: hoge/test.txt

$ od -tx1z -Ax test.zip
000000 ef bb bf 50 4b 03 04 14 00 00 00 08 00 69 72 48  >...PK........irH<
000010 4e 07 19 91 61 09 00 00 00 09 00 00 00 0e 00 00  >N...a...........<
000020 00 2f 68 6f 67 65 2f 74 65 73 74 2e 74 78 74 cb  >./hoge/test.txt.<
000030 c8 4f 4f cd 00 62 2e 00 50 4b 01 02 00 00 14 00  >.OO..b..PK......<
000040 00 00 08 00 69 72 48 4e 07 19 91 61 09 00 00 00  >....irHN...a....<
000050 09 00 00 00 0e 00 00 00 00 00 00 00 00 00 00 00  >................<
000060 00 00 00 00 00 00 2f 68 6f 67 65 2f 74 65 73 74  >....../hoge/test<
000070 2e 74 78 74 50 4b 05 06 00 00 00 00 01 00 01 00  >.txtPK..........<
000080 3c 00 00 00 35 00 00 00 00 00                    ><...5.....<
00008a

先頭にある ef bb bf がBOMである。この例では、readfileをする前に、 ob_end_clean 関数 を呼ぶことでBOMを出力しなくなる。

<?php
        $zip = new ZipArchive();
        $temp_dir = sys_get_temp_dir();
        $zip_name = 'test.zip';
        if (!$zip->open($temp_dir . '/' . $zip_name, ZIPARCHIVE::CREATE)){
                exit();
        }
        $zip->addFile('test.txt', '/hoge/test.txt');
        $zip->close();
        header('Content-Type: application/zip');
        header('Content-Length: '.filesize($temp_dir . '/' . $zip_name));
        header('Content-Disposition: attachment; filename="'.$zip_name.'"');
        ob_end_clean();
        readfile($temp_dir . '/' . $zip_name);
?>

以下のようにエラーが発生しないようになった。

$ unzip test.zip
Archive: test.zip
warning: stripped absolute path spec from /hoge/test.txt
inflating: hoge/test.txt

関連した話として、RedHatEnterpriseLinuxもしくはCentOSではバージョン7よりSystemdのPrivateTmpという機能が有効になっている。sys_get_temp_dir()ではデフォルトでは、/tmp/以下のディレクトリが取得できるが、実際には/tmp/systemd-private-xxxxx-httpd.service-xxxx/tmp/ に書き出しされる。デバッグする際にファイルを取り出したい場合などにファイルが見当たらないといったことになるので、注意が必要。

カテゴリーPHP

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です