phpのアップロードで必ずチェックする設定値 (php.ini , httpd.conf)

phpでアップロードする際に気をつけるべき項目があります。
現在の設定値は下記コードで取得できます。

<?php
echo 'memory_limit = ' . ini_get('memory_limit') . "<br />";
echo 'post_max_size = ' . ini_get('post_max_size') . "<br />";
echo 'upload_max_filesize = ' . ini_get('upload_max_filesize') . "<br />";
echo 'max_execution_time = ' . ini_get('max_execution_time') . "<br />";
echo 'max_input_time = ' . ini_get('max_input_time') . "<br />";
?>

php.ini の設定

ファイルサイズの設定について

大きいファイルサイズのアップロードをする場合、必ず設定がいる項目です。

memory_limit = 500M
post_max_size = 400M
upload_max_filesize = 300M

基本的に下記の範囲の値にするので間違いないのですが、マニュアルでは、memory_limitに”一般的に”という条件がつきます。

memory_limit >= post_max_size >= upload_max_filesize

post_max_size >= upload_max_filesizeは絶対です。
ファイルのアップロードという行為がpost値に内包されるので。

memory_limitは必ずしも他の値よりも大きくなければならないということはありません。
memory_limitが他の値よりも小さい場合、それより大きサイズのファイルの処理速度が遅くなるなど弊害がありますが、空きメモリー状況によって調節してもよいです。
大きいサイズのファイルは、通常回線速度やHDD(またはSSD)のアクセス速度のほうがボトルネックになるはずです。
ただし、余りにも小さいmemory_limitは急激なレスポンス低下になります。
テストしながら適正な値に調整します。

実行時間の設定について

max_execution_time = 120

忘れがちですがPHPスクリプトの実行時間の変更も必要です。
大きいファイルサイズの場合、[ move_uploaded_file() ]などの実行時間でタイムアウトする可能性があります。
実行時間はHDD(SSD)のアクセス速度に影響されます。

php.iniで設定すると全てのファイルに影響がでますので、特定のファイル( upload.php など)だけ設定するなら下記のコードで設定できます。
ただし、レンタルサーバーなどではこのコードを書いても設定できないことがあります。

<?php
set_time_limit(120);
?>

実行時間の設定について (念のため)

max_input_time = 60

ユーザーがアップロードにかかる時間と勘違いすることが多いですが、マニュアルによると「すべてのデータを受け取ってから~」なので関係ありません。
ディフォルトのままでネックになることは、余程混んでいないと少ないと思いますが、念のためこういう設定項目があるということを覚えておく。

Apacheの設定 httpd.conf

LimitRequestBody 0

ディフォルトで無制限に設定されているが、サーバーによっては制限されている場合があります。
32ビットのapacheでは基本2GBが最大サイズ。

phpのフォームでファイルのアップロードをする2

phpでファイルをアップロードするときのやり方の続きです。
前回はアップロードする際の、送り出すHTMLを説明しましたが、今回は受け取る方のphpの説明です。

upload.php
HTMLのヘッダ、bodyは省略してます。

<?php
if(!isset($_FILES['my_file'])){
	echo 'ページ遷移が不正です。';
}else{
	if($_FILES['my_file']['error'] !== UPLOAD_ERR_OK){
		
		//エラーが発生している
		if($_FILES['my_file']['error'] == UPLOAD_ERR_FORM_SIZE){
			echo 'ファイルサイズがHTMLで指定した MAX_FILE_SIZE を超えています。';
		}elseif($_FILES['my_file']['error'] == UPLOAD_ERR_NO_FILE){
			echo 'ファイルが選択されていません。';
		}else{
			echo 'その他のエラーが発生しています。';
		}
		
	}else{
		
		//ここから通常の処理

		//ユーザーが指定したファイル名
		$myfile_name = $_FILES['my_file']['name'];
		//ファイルのMIME型
		$myfile_type = $_FILES['my_file']['type'];
		//ファイルサイズ
		$myfile_size = $_FILES['my_file']['size'];
		//アップロードしたファイルが保存されている一時保存場所
		$myfile_tmp_path = $_FILES['my_file']['tmp_name'];
		
		//SQLインジェクション対策用
		$safesql_myfile_name = mysql_real_escape_string($myfile_name);
		$safesql_myfile_type = mysql_real_escape_string($myfile_type);
	
		//HTML表示用
		$safehtml_myfile_name = htmlspecialchars($myfile_name);
		$safehtml_myfile_type = htmlspecialchars($myfile_type);
		
		
		//拡張子の取得
		$tmp_ary = explode('.',$myfile_name);
		if(count($tmp_ary)>1){
		    $extension = $tmp_ary[count($tmp_ary)-1];
			
			//拡張子が半角英数字以外なら拡張子がないものとする。
			if( !preg_match("/^[0-9a-zA-Z]+$/",$extension) ) $extension='';
		}else{
			//拡張子がない場合はそのまま。Macなど。
			$extension='';
		}
		
		//SQLインジェクション対策用
		$safesql_extension = mysql_real_escape_string($extension);
		//HTML表示用
		$safehtml_extension = htmlspecialchars($extension);
			
		
		//新しいファイル名を作成する
		$new_file_name = date("Ymd-His").'-'.mt_rand().'.'.$safehtml_extension;
		
		//php側でもファイルサイズのチェックを行う。
		if($myfile_size>10485760 or $myfile_size==0)die('エラー ファイルサイズが不正です。');		

		//ファイルの保存場所
		$myfile_new_path = '/uploads';
		if(!move_uploaded_file($myfile_tmp_path,"$myfile_new_path/$new_file_name")){
			die('エラー ファイルを保存できませんでした。');
		}
		
		echo 'アップロードは成功しました。<br /><br />';
		
		echo 'ファイル名 : '.$safehtml_myfile_name.'<br />';
		echo 'MIME型 : '.$safehtml_myfile_type.'<br />';
		echo 'ファイルサイズ : '.number_format($myfile_size).' bytes<br />';
		echo '新しいファイル名 : '.$new_file_name.'<br />';

	}
}
?>

サンプル

パーツの説明

1.POSTの存在確認

if(!isset($_FILES['my_file'])){
	echo 'ページ遷移が不正です。';
}

ファイルの存在の確認をしています。HTTP POST で渡された$_FILESの存在を確認します。
連想配列の名前は前回 input で指定した名前になります。

<input type="file" name="my_file" />

今回のサンプルの場合、POSTされずに来るページではないので、die()しても構わないと思います。

2.エラーの確認

大まかなエラーの確認からしていきます。

if($_FILES['my_file']['error'] !== UPLOAD_ERR_OK){

$_FILES[‘my_file’][‘error’] で自動的にエラーを確認してくれるので楽チンです。
エラーがない場合は UPLOAD_ERR_OK もしくは 0 。ある場合はエラーコードを返します。

if($_FILES['my_file']['error'] == UPLOAD_ERR_FORM_SIZE){

UPLOAD_ERR_FORM_SIZE 。もしくは 2 。そのままですが、ファイルサイズがHTMLで指定した MAX_FILE_SIZEを超えた場合の値。MAX_FILE_SIZEはユーザーが自由に変更できるので、あくまでも目安に。

}elseif($_FILES['my_file']['error'] == UPLOAD_ERR_NO_FILE){

同じくそのまま。値は 4 。ファイルが指定されていないです。
【1.存在確認】と違い、間違って押してしまう場合があるので、明示してあげるのが親切。

}else{
    echo 'その他のエラーが発生しています。';
}

上記2つ以外にもエラーコードがありますが、頻度が少ないので一括で処理。
その他のエラーコードはマニュアルを見て下さい。

3.$_FILESで取得できる値

$myfile_name = $_FILES['my_file']['name'];
$myfile_type = $_FILES['my_file']['type'];
$myfile_size = $_FILES['my_file']['size'];
$myfile_tmp_path = $_FILES['my_file']['tmp_name'];

それぞれファイル名、MIMEタイプ、サイズ、一時保存場所です。

$safesql_myfile_name = mysql_real_escape_string($myfile_name);
$safesql_myfile_type = mysql_real_escape_string($myfile_type);
$safehtml_myfile_name = htmlspecialchars($myfile_name);
$safehtml_myfile_type = htmlspecialchars($myfile_type);

SQLインジェクションやXSS対策のために汚染されていない値を作っておく。
ファイル名、MIMEタイプは汚染されている可能性があります。
サイズ、一時保存場所の値は安全ですが、念のため同様にサニタイズした方がいいと思います。
→一時保存場所は /var/phptmp など。サイズは整数値。
またSQLに関しては必ずプリペアドステートメントをするべきです。

4.拡張子の取得

いろいろな方法があると思いますが、ここでは[ . ](ピリオド)で分割し、配列の最後の値(-1)を取得しています。

$tmp_ary = explode('.',$myfile_name);
if(count($tmp_ary)>1){
    $extension = $tmp_ary[count($tmp_ary)-1];
}else{
    $extension='';
}

MACなどで拡張子がない場合もありますので、ピリオドがない場合は拡張子なしとしています。

if( !preg_match("/^[0-9a-zA-Z]+$/",$extension) ) $extension='';

拡張子が半角英数字以外は拡張子をないものとしてます。
MACで拡張子がなく、[ プレゼン資料.2013.04.02修正バージョン ]のようなファイル名も考えられ、これに対応します。ここで拡張子の桁数を確認してもいいですが、拡張子はシビアになる項目でもないです。
(そもそもユーザー指定なので信用せず単なる目安と考える)

$safesql_extension = mysql_real_escape_string($extension);
$safehtml_extension = htmlspecialchars($extension);

ユーザーから送信された拡張子は安全ではないのでサニタイズしています。

5.新しいファイル名の作成

ユーザー指定のファイル名だと何かと不都合が多いので、サーバー側で指定します。
ユーザー指定だと保存フォルダがカオスになり、当然重複も発生します。

$new_file_name = date("Ymd-His").'-'.mt_rand().'.'.$safehtml_extension;

ファイル名には時間を指定していますが、全く同時にアップロードも考えられますので、ランダムな値を付加しています。マイクロ秒などでもほとんど問題ないと思いますが、ランダムな値のほうがお勧めです。
拡張子は$safehtml_extensionを利用しました。

※マイクロ秒などでは操作によって同時が考えられる。

6.ファイルサイズのチェック

if($myfile_size>10485760 or $myfile_size==0)die('エラー ファイルサイズが不正です。');

php側でもファイルサイズのチェックは必須です。
MAX_FILE_SIZEの値は信用出来ません。
また、念のためファイルサイズが0の場合もチェックしています。

7.ファイルの移動

if(!move_uploaded_file($myfile_tmp_path,"$myfile_new_path/$new_file_name")){
	die('エラー ファイルを保存できませんでした。');
}

move_uploaded_fileで一時保存場所からファイルを移動します。
アップロードするファイルの場所はフルパスで指定したほうがいいです。

phpのフォームでファイルのアップロードをする1

phpでファイルをアップロードするときのやり方です。

基本的にhtmlのフォームからデータを投げて(submitして)、phpで受け取るという処理です。
同じページで処理しても構いませんが、ここでは単純化するためにindex.html→upload.phpという流れにします。
内容は最低限必要な要素。

index.html

<html>
<head>
<title>ファイルのアップロード STEP1</title>
</head>
<body>
<form action="./upload.php" method="POST" 
enctype="multipart/form-data" accept-charset="utf-8">
<input type="hidden" name="MAX_FILE_SIZE" value="10485760" />
<!--1024*1024*10 = 10MB-->
<input type="file" name="my_file" />
<input type="submit" value="ファイルの転送開始する" />
</form>
</body>
</html>

サンプル

パーツの説明

form action=””

データをアップロードするアドレスです。
ここではupload.php

form enctype=”multipart/form-data”

パートごとにデータを送りますという宣言。
これがないとfileタイプのデータを送れない。

form accept-charset=”utf-8″

文字コードの指定。
ファイル名ではまるので指定したほうが無難、というか必須。最近はほぼ全てutf-8が標準。
サーバーの環境に合わせる。

input type=”hidden” name=”MAX_FILE_SIZE” value=””

ユーザー側ブラウザにファイルのアップロードの最大サイズを指定する。
セキュリティ上は全く当てにならないが、ブラウザ側で最大サイズ以上を弾いてくれたりすることがある。
単位はバイト。

input type=”file” name=”your_file”

ファイルを選択するボタン。
複数あっても良い。

input type=”submit”

サブミットボタン。
単に「submi」というよりも、ユーザーにハッキリとアップロードすることが分かるようにした方が親切。

サーバーの設定など

基本的のどこのサーバーでも、サーバー側の設定でアップロードできるファイルサイズが決まっています。
ややこしいので別記事に。