GET送信でもPOST送信でもエンコードタイプを省略すると「application/x-www-form-urlencoded」という方式で送られます。
これはお馴染みの「%00-%FF」のような16進にエスケープされるものです。
これは「name1=value1&name2=value2」のようにnameとvalueのペアを「=」でつないで、更にペア同士を「&」でつないでいく構造です。
このときvalue1が「&」を含む値だとそのままつなげてしまっては後で値を切り出すときに間違ってしまいます。
その間違わないための「%00-%FF」というエスケープなわけです。ちなみに「&」は「%26」にエスケープされます。


ところがこのURLエンコード方式、1バイトを「%00」みたいにエスケープしていくわけですから送信データが3倍(冗長)になってしまいます。
そこでもっと効率的に送信する方法が考え出されました。
それがマルチパートなのです。
ファイルアップロードではこのマルチパートを使うことになります。
メールの添付ファイルなどもこのマルチパートが使われています。


エンコードを施さない生のままのデータを境界線(バウンダリー)で仕切って送信します。
GET送信ではURLアドレス末尾に付加するやり方なのでこのマルチパートはGET送信ではできません。
必ずPOST送信でPOST送信用のデータ領域(ボディー部)を使って送ることになります。



ここからはアップロード画面(upload.html)からアップロード処理スクリプト(upload.php)に対して手元のファイルをマルチパートでPOST送信する流れを見ていきます。




■アップロード画面(upload.html)
<html><head>
<meta http-equiv="Content-type" content="text/html; charset=Shift_JIS">
<title>アップロード画面</title>
</head><body>
<h2>アップロード画面</h2>

<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="hidden" name="MAX_FILE_SIZE" value="1048576">
<input type="file" name="userfile">
<input type="submit" value="アップロード開始!">
</form>

</body></html>




■アップロード処理スクリプト(upload.php)
<?php

if ($name=filename('userfile', 'output')) echo "アップロード成功!<br>\n";
else echo "アップロード失敗<br>\n";



function filename($key, $name, $maxsize=0)
{
    // 正常にアップロードされたか
    if ($_FILES[$key]['error'] != UPLOAD_ERR_OK) return false;
    // サイズ上限指定あればチェック
    if ($maxsize && $maxsize < $_FILES[$key]['size']) return false;
    // 画像ファイル(png gif jpg)のみを許可する
    if (!($ext=is_img($_FILES[$key]['tmp_name']))) return false;
    $name.= ".$ext"; // ファイル名に拡張子を付加する
    // ファイル移動が成功すればファイル名を返す
    return move_uploaded_file($_FILES[$key]['tmp_name'], $name) ? $name : false;
}
function is_img($img_path="")
{
    if (!(file_exists($img_path) and $type=exif_imagetype($img_path))) return false;
    if (IMAGETYPE_GIF == $type) return 'gif';
    else if (IMAGETYPE_JPEG == $type) return 'jpg';
    else if (IMAGETYPE_PNG == $type) return 'png';
    return false;
}


?>



アップロード画面で[参照]ボタンを押しアップロードしたいファイルを指定して[アップロード開始!]ボタンを押します。 するとPHPスクリプト側では$_FILESという配列にさまざまな情報がセットされているのでそこからアップロードを許可できるファイルかどうかチェックをして問題無ければmove_uploaded_file()関数を使って一時ファイルを移動させたい場所へ移す流れになります。

<input type="hidden" name="MAX_FILE_SIZE" value="1048576">と書いた部分に着目してみます。
この数値は、アップロードするファイルの容量がこれより大きい場合に早目に処理を中止して教えてくれるようにするためのおまじないです。
当然ファイルデータを送る<input type="file" name="~">よりも必ず先に書いていないと効果は期待できません。
サーバ側でファイルデータを読み取る段階で先にMAX_FILE_SIZE値を取得してないと容量を監視できないので当然です。
さんざんアップロードの完了を待った挙げ句に容量オーバーでダメだったりしたら、「だったら先に言ってよ!」というくやしい気持ちになりますね。
そう、受付で行列に並んでいてやっと自分の番が来たときに別の受付に回される感じですかねぇ。。
それとも長年付き合ってた恋人にある日「なんか違う、別れてくれ」と言われる感じですかねぇ。。
はたまた夜遅めにスーパーに行って半額シール貼ってくれそうで貼ってくれない感じですかねぇ。。


ともかくこのおまじないは期待ができるというだけであって、絶対的なものじゃないです。
というのも、サーバ側に送信するフォームデータはユーザが勝手に改変することも簡単にできてしまうのでこの数値をイジるだけでより大容量のファイルを送信できてしまうのは問題があるからです。
したがってMAX_FILE_SIZE値よりも優先されるupload_max_filesize値が設定ファイル(php.ini)内には存在します。
また、POST送信時にはファイルアップロード以外にもPOSTデータが存在するのでpost_max_sizeがupload_max_filesizeより大きい値に設定されていないといけません。
更に処理の際に確保できる最大メモリ値memory_limitをpost_max_sizeより大きい値で設定しないといけません。
設定値の大小関係をまとめると


memory_limit > post_max_size > upload_max_filesize | MAX_FILE_SIZE


3つの設定値の大小関係があって、超えられない壁があってフォームデータ値があるイメージです。
大容量ファイルのアップロードを許可するサーバではこられの値が大きく設定されることになります。


その他メモリは足りてるのにアップロードがタイムアウトになる場合はmax_input_time値を大きくする必要があります。
この値は実行に移る直前の入力パース処理時間の待ち設定であって、実行に移ってから処理の長引くスクリプトを強制的に止めるのは別のmax_execution_timeの方になる。



アップロードされたファイル情報はすべて$_FILESにセットされています。
userfileというフォームデータ名でアップロードした例で見ていくと、


$_FILES['userfile']['name'] ・・・送る側のファイル名
$_FILES['userfile']['type'] ・・・MIMEタイプ
$_FILES['userfile']['size'] ・・・ファイルサイズ
$_FILES['userfile']['tmp_name']・・受け取った一時ファイル名
$_FILES['userfile']['error'] ・・・ステータス値


「name」には送り手のファイル名がセットされているが、第三者からのアップロードの場合、メタ文字をエスケープしないとそのままでは使いようによっては特定のコマンドが実行されるようにファイル名を偽装してくる場合も考えられるのでこの値をファイル名にそのまま使うことはないだろう。せいぜいエンティティ変換してこういうファイル名でしたと表示するぐらいにしか使い道がない値


「type」の値は送信ファイルの拡張子を変えるだけで簡単に偽装できてしまうので一切信用できない情報である。送られてきたファイルがどういうものかはそのファイル構造を解析するしかない。


「size」の値は送られてきたデータを切り分けるときに数えたバイト数なので信用できる値である。


「tmp_name」には一時ファイル名がセットされている。この一時ファイルはmove_uploaded_file()関数で移動させておかないとスクリプト終了と同時に自動的に削除される。逆に言うと条件に合わなかったファイルならそのまま放って置けば勝手に削除してくれることになる。


「error」にはアップロードが成功したかとか、失敗したならどういうエラーかとかがセットされている。詳細は以下の表をご覧ください。


[ステータス値]

定数意味
UPLOAD_ERR_OKファイルアップロード成功
UPLOAD_ERR_INI_SIZEupload_max_filesize値オーバー
UPLOAD_ERR_FORM_SIZEMAX_FILE_SIZEフォームデータ値オーバー
UPLOAD_ERR_PARTIALアップロードデータが途切れた
UPLOAD_ERR_NO_FILEアップロードされなかった
UPLOAD_ERR_NO_TMP_DIRテンポラリフォルダがない
UPLOAD_ERR_CANT_WRITEディスクへの書き込みに失敗
UPLOAD_ERR_EXTENSION拡張モジュールにより停止