章 38. 檔案上傳處理

POST 方法上傳

本特性可以使會員上傳文字和二進位檔案。用 PHP 的認證和檔案動作函數,可以完全控制容許哪些人上傳以及檔案上傳後怎樣處理。

PHP 能夠接受任何來自符合 RFC-1867 標準的瀏覽器(內含 Netscape Navigator 3 及更高版本,打了修改更新的 Microsoft Internet Explorer 3 或是更高版本)上傳的檔案。

關聯的設定: 請參閱 php.inifile_uploadsupload_max_filesizeupload_tmp_dirpost_max_size 以及 max_input_time 設定選項。

請注意 PHP 也支援 PUT 方法的檔案上傳,Netscape Composer 和 W3C 的 Amaya 用戶端使用這種方法。請參閱對 PUT 方法的支援以取得更多訊息。

例子 38-1. 檔案上傳表單

可以如下建立一個特殊的表單來支援檔案上傳:

<!-- The data encoding type, enctype, MUST be specified as below -->
<form enctype="multipart/form-data" action="__URL__" method="POST">
    <!-- MAX_FILE_SIZE must precede the file input field -->
    <input type="hidden" name="MAX_FILE_SIZE" value="30000" />
    <!-- Name of input element determines name in $_FILES array -->
    Send this file: <input name="userfile" type="file" />
    <input type="submit" value="Send File" />
</form>

以上範例中的 __URL__ 應該被換掉,指向一個真實的 PHP 檔案。

MAX_FILE_SIZE 隱藏欄位(單位為位元組)必須放在檔案輸入欄位之前,其值為接收檔案的最大尺寸。這是對瀏覽器的一個建議,PHP 也會檢查此項。在瀏覽器端可以簡單繞過此設定,因此不要指望用此特性來阻擋大檔案。實際上,PHP 設定中的上傳檔案最大值是不會失效的。但是最好還是在表單中加上此項目,因為它可以避免會員在花時間等待上傳大檔案之後才發現檔案過大上傳失敗的麻煩。

注: 要確保檔案上傳表單的屬性是 enctype="multipart/form-data",否則檔案上傳不了。

全局變量 $_FILES 自 PHP 4.1.0 起存在(在更早的版本中用 $HTTP_POST_FILES 替代)。此陣列包括有所有上傳的檔案訊息。

以上範例中 $_FILES 陣列的內容如下所示。我們假設檔案上傳欄位的名稱如上例所示,為 userfile。名稱可隨意命名。

$_FILES['userfile']['name']

用戶端機器檔案的原名稱。

$_FILES['userfile']['type']

檔案的 MIME 類型,若果瀏覽器提供此訊息的話。一個例子是「image/gif」。不過此 MIME 類型在 PHP 端並不檢查,因此不要想當然認為有這個值。

$_FILES['userfile']['size']

已上傳檔案的大小,單位為位元組。

$_FILES['userfile']['tmp_name']

檔案被上傳後在服務端儲存的暫存檔案名。

$_FILES['userfile']['error']

和該檔案上傳關聯的錯誤代碼。此項目是在 PHP 4.2.0 版本中增加的。

檔案被上傳後,預設地會被儲存到服務端的預設臨時目錄中,除非 php.ini 中的 upload_tmp_dir 設定為其它的路徑。服務端的預設臨時目錄可以通過變更 PHP 運行環境的環境變量 TMPDIR 來重新設定,但是在 PHP 腳本內定通過運行 putenv() 函數來設定是不起作用的。該環境變量也可以用來確認其它的動作也是在上傳的檔案上進行的。

例子 38-2. 使檔案上傳生效

請查閱函數 is_uploaded_file()move_uploaded_file() 以取得進一步的訊息。以下範例處理由表單提供的檔案上傳。

<?php
// In PHP versions earlier than 4.1.0, $HTTP_POST_FILES should be used instead
// of $_FILES.

$uploaddir '/var/www/uploads/';
$uploadfile $uploaddir basename($_FILES['userfile']['name']);

echo 
'<pre>';
if (
move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
    echo 
"File is valid, and was successfully uploaded.\n";
} else {
    echo 
"Possible file upload attack!\n";
}

echo 
'Here is some more debugging info:';
print_r($_FILES);

print 
"</pre>";

?>

接受上傳檔案的 PHP 腳本為了決定接下來要對該檔案進行哪些動作,應該實現任何邏輯上必要的檢查。例如可以用 $_FILES['userfile']['size'] 變量來排除過大或過小的檔案,也可以通過 $_FILES['userfile']['type'] 變量來排除檔案類型和某種標準不相符合的檔案,但只把這個當作一系列檢查中的第一步,因為此值完全由用戶端控制而在 PHP 端並不檢查。自 PHP 4.2.0 起,還可以通過 $_FILES['userfile']['error'] 變量來根據不同的錯誤代碼來計劃下一步如何處理。不管怎樣,要麼將該檔案從臨時目錄中移除,要麼將其搬移到其它的地方。

若果表單中沒有選取上傳的檔案,則 PHP 變量 $_FILES['userfile']['size'] 的值將為 0,$_FILES['userfile']['tmp_name'] 將為空。

若果該檔案沒有被搬移到其它地方也沒有被改名,則該檔案將在表單請求結束時被移除。

例子 38-3. 上傳一組檔案

PHP 的 HTML 陣列特性甚至支援檔案類型。

<form action="" method="post" enctype="multipart/form-data">
<p>Pictures:
<input type="file" name="pictures[]" />
<input type="file" name="pictures[]" />
<input type="file" name="pictures[]" />
<input type="submit" value="Send" />
</p>
</form>
<?php
foreach ($_FILES["pictures"]["error"] as $key => $error) {
    if (
$error == UPLOAD_ERR_OK) {
        
$tmp_name $_FILES["pictures"]["tmp_name"][$key];
        
$name $_FILES["pictures"]["name"][$key];
        
move_uploaded_file($tmp_name"data/$name");
    }
}
?>