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/ に書き出しされる。デバッグする際にファイルを取り出したい場合などにファイルが見当たらないといったことになるので、注意が必要。

MariaDBでBLOBを取扱時の注意事項

BLOBでバイナリデータをPHPから書き込みするときにはまったポイントをいくつか。。。MariaDBでの発生事例だが、MySQLでも同様だと思う。

・BLOBは65535Bytesしか格納できない。BLOBよりも大きなサイズの格納が必要であれば、MEDIUMBLOB、LONGBLOBを利用する必要がある。
・BLOB型にデータをinsertするときにはバイナリデータをストリームのまま登録することはできないので、PHPであればbin2hex関数でいったん16進数に変換してinsertした後、selectするときにhex2bin関数で戻す必要がある。また、BLOB型で65,535byteを超えるデータをinsertすると先頭65,535byteだけが登録されてしまう(insert時にエラーにならない)ので、insert前にデータサイズのチェックが必要。
・パケットサイズを大きくする必要がある。max_allowed_packet という設定値だが、デフォルトでは1MBなので、設定値を超えるデータをinsertしようとするとエラーが発生する。mysqlで大きなファイルを保存するための設定 を参考にログファイルサイズの設定値も変更したほうが良いかもしれない。
・PHPの設定項目値として、php.ini でupload_max_filesize設定値を確認する。この値を超えたデータがPOSTされても、$_FILES[name][name]でファイル名は設定されるが、データは一時ディレクトリに保存されず、
$_FILES[name][tmp_name] は値がセットされない。また、環境によってはset_time_limit関数を使ってタイムアウト時刻を調整したほうが良い。

Unicodeの話

おさえておきたいUnicodeのキーワード
Unicodeは全世界の文字を収録することを目指した文字コード。

・UCSとUTFの違い
UTF-16≠UCS-2
・サロゲートペア
2Byte(最大で65,536文字/厳密には使用できない、使用しないコードポイントがあるので65,536文字より減る)では取り扱いできない(収録できない)文字数を収録するために定義した必殺技。UCS-2には存在しない。
UTF-16はUCS-2と違い、サロゲートペアが含まれると2Byteではなくなる。なので、処理がめんどい(ので対応されていないアプリケーションがある)。
・BEとLE
マルチバイト文字のコードの配置順番。
・BOM
BOM付とBOMなしがある。Windowsの標準機能であるメモ帳は文字コードをUTF-8にして保存すると必ずBOM付きになる。BOMなしの場合には、BigEndiganとして解釈する。(RFC2781)
・異体字
簡単に言うと、斉と斎と齋のような話。Unicodeに限った話ではないが、全世界の文字を収録するという意味で包摂の解釈が広い。CJK統合漢字(GB18030、Big5-2000、JIS、KSX1001)の収録基準がちょっと首をかしげるものがある。

Javaの教科書

Javaといえば、バージョン表記がJDK 8の場合には1.8となっていて、バージョン8じゃないのかと思いきや、そうではないようだ。Java Platform, Standard Edition 8の名前とバージョンにあるように、たとえば、Java SE 8というのは、名前であってバージョンではない。また、Java SEはプラットフォーム名であり、製品名ではない。なかなか奥が深い。

メモリ管理で悩むような(多重実行時にスワップするような)場合には、Oracleドキュメントを参照すると良い。

Eclipse4.4ではじめるJavaプログラミング入門Eclipse4.4Luna対応

Java SE 8に対応した書籍。
Tomcat8についての知識を少し学んでおきたいと思ったが、プログラミング入門というだけあって、肩透かしを食らった感じ。ただ、Java8からGUIがSwingがJavaFXに変わったことを知れて勉強になった。また、Androidプログラミングについても記載されているので、プログラミングの導入には非常に良い本だと感じた。

はじめてのJSP&サーブレット Eclipse 3.7 Indigo+Tomcat 7対応版 (TECHNICAL MASTER 67)

Java SE 8には対応していないが、サーブレットに関する記述が多く、Tomcatについての知識を学ぶ上では良書のように感じた。参考程度だが、JettyやGlassFishについての話題についても触れられてている。少し古いが、Java 最近のアプリケーションサーバー事情(2015年)が参考になる。

詳解 Tomcat

Tomcat8に対応したオライリー社の本。Javaアプリケーションサーバの管理の視点で詳しく知る上では、この本を購入するしかないのかもしれない。書籍を読まれた方のレビュー書籍「詳解Tomcat」を読んでが参考考になる。

設計の仕事ばかりしていると、実務から離れてしまうので少しはAndroidプログラミング等行って手を動かすようにしないといけないなと痛感。。。。

 

UMLの教科書

市の図書館にあった蔵書で借りてみた。

かんたん UML入門 [改訂2版]

入門書といえど、わりと難しいUML入門書。
ただ、漠然としか分かっていなかったUMLと設計のあるべき姿が学べるので良い本だと思う。

GoFの23パターン(オブジェクト指向における再利用のためのデザインパターン)が設計のあるべき姿として紹介されているが、具体的な内容の記述はない。賛否両論あるようだが、解説されている矢沢久雄の早わかりGoFデザインパターンを参考にして理解を深めてみたい。

HTTP_RequestをHTTP_Request2に書き換える

PHP 5.4以降でE_STRICTによるメッセージが出るようになったこととHTTP_Requestがデサポになっていることから、ようやくHTTP_RequestをHTTP_Request2に書き換えてみることにした。
参考にしたサイトは[php][pear]HTTP_Request2のサンプル#1 : うえちょこ@ぼろぐ
互換性があるライブラリと思いきや、かなり非互換。
コンストラクタから違う。上記参考サイトとあわせて下記参考にしてみてください。
HTTP_Request
HTTP_Request HTTP_Request( [string $url = ”], [array $params = array()])
HTTP_Request2
HTTP_Request2 __construct( [string|Net_Url2 $url = null], [string $method = self::METHOD_GET], [array $config = array()])

WSHでハッシュ値計算にSHA-1アルゴリズムを利用する

WSHはもう時代遅れなのかもしれない。
ただ、Windows 2000以降のOSにてデフォルトで動作するWSHは魅力的だ。
ところでハッシュ値を算出する方法がなかなか見つからなかったが、MD5とSHA-1アルゴリズムを利用しているソースが紹介されていたので、実際に使用してみるとすぐに利用できた。
papakingの日記
SHA-1もMD5もハッシュ値が衝突する脆弱性が指摘されており、中間者攻撃に利用されるリスクがある。ただ、SHA-256などは.NET Frameworkの特定バージョン以上をインストールしないと利用できないという問題がある。

CentOS 5.5にPHP 5.2をインストールする

CentOS 5.5のリポジトリでは、PHP 5.1.6までしかインストールできない。
PHPならびに対応するmemcachedのライブラリのインストール方法について「CentOS5.2にPHP5.2.6とmemcachedをインストールする」に記載があるので、こちらを利用するとよい。
ちなみに追加したリポジトリは次の通り。/etc/yum.repos.d/CentOS-Testing.repo として保存するとよい。
[c5-testing]
name=CentOS-5 Testing
baseurl=http://dev.centos.org/centos/$releasever/testing/$basearch/
enabled=1
gpgcheck=1
gpgkey=http://dev.centos.org/centos/RPM-GPG-KEY-CentOS-testing
includepkgs=php*
priority=1

俺のコードのどこが悪い?

俺のコードのどこが悪い?―コードレビューを攻略する40のルール
コードレビューに関する分野の書籍は珍しく先月発売された新書で、書店で平積みされていたので思わず手にとって買ってしまった。実際に読んでみると、どうすればコーディングミスを減らしたり、保守性を上げられるかについて、技術的なアプローチだけでなく仕掛けづくりについても書かれており、新人が読むための教育書という位置づけだけでなく、レビュアーも参照すべき一冊である。