經驗交流

關於「檔案上傳」與「WebHD」

問題說明
 PHP 能輕易地處理檔案上傳的問題,也有一堆與目錄/檔案處理相關的函數有供運用。只要規劃得宜,我們可以自行建構一套 WebHD 系統。

我的做法
 要在使用者端提供上傳介面的話,您需要 File 元件,其使用方法請參閱「表單處理」中之「File」一節之說明。
 以下就實務上可能遭遇的狀況加以說明:
一、如何限制使用者上傳的檔案大小
 PHP 預設可接受 2MB 以內的檔案,欲調整這項預設值的話,請在 php.ini 中修改 upload_max_filesize 這項設定值。
 若使用者上傳一個大小在此一預設值以內的檔案,例如:1 MB。但我們希望單一檔案都不可超過 500 KB 的話,您可以用 $_FILES['UPLOADFILE']['size'] 取得其檔案大小,再用 PHP 的判別式去拒絕他(說明:此處的 UPLOADFILE 請視 Form 中 File 的名稱而定,例如 MyPhoto)
 不過,如果使用者傳來一個大小超過系統預設值的檔案,那麼將發生程式無法處理的狀況(錯誤訊息將指稱您的 line 0 有錯誤)。為了解決此一問題,您可以調高 php.ini 中的 upload_max_filesize 設定值,或是在 Form 之中加入 MAX_FILE_SIZE 的參數(請參閱「表單處理」中之「File」一節之說明)

二、為什麼可以接收到上傳的檔名,可是檔案大小卻為 0
 當 PHP 收到來自使用者的檔案時,會先將它置於一個暫存的目錄下,同時給它一個臨時的檔名(您可以 echo $_FILES['UPLOADFILE']['tmp_name'],查看其目錄與檔名),等待您將它複製到指定的目錄下。
 如果該暫存目錄已滿,無足夠的空間可供該檔案寫入的話,就會發生「可以接收到上傳的檔名,可是檔案大小卻為 0」之狀況。假如您想改變此暫存目錄之路徑的話,請調整 php.ini 中的 upload_tmp_dir 設定值。

三、如何限制使用者上傳的檔案一定是可顯示的圖檔
 倘若您提供上傳介面,供使用者上傳個人的大頭照,由於瀏覽器只能顯示 GIF、JPEG 與 PNG 三種格式的圖檔,因此我們必須限制上傳的檔案需屬於此類的格式。
 PHP 提供一個圖形處理的函數:GetImageSize,它可用來取得圖形檔案的格式與尺寸。其傳回值是一個陣列,第一個元素是圖片的寬度,單位是像素(pixel);第二個元素是圖片的高度;第三個元素是圖片的檔案格式,其值為 1 代表 GIF 格式,2 為 JPEG/JPG 格式,3 為 PNG 格式,4 為 SWF 格式;第四個元素為圖片的高與寬字串:height=xxx width=yyy。
 只要上述函數傳回值的第三個元素(索引值 2)為空,即表示該檔不屬於 GIF、JPEG、PNG 或 SWF 之類的格式。例:
<?
$FILE_info = getimagesize($_FILES['UPLOADFILE']['tmp_name']);
if ( $FILE_info[2] == "" ) {?>
  <script language="JavaScript">
    alert("您的相片格式不正確,無法在瀏覽器上顯示,請重新選擇!");
    history.back();
  </script>
  <?
  exit;
}?>

四、萬一有兩個使用者上傳了同一檔名的檔案
 我們可以用 $_FILES['UPLOADFILE']['name'] 取得上傳檔之原始檔名,所以可以用 copy($_FILES['UPLOADFILE']['tmp_name'], "您指定的目錄".$_FILES['UPLOADFILE']['name']),將該檔以原始檔名存入您指定的目錄中。但是若有兩個使用者傳來同檔名的檔案時,會發生新檔覆蓋舊檔的情況。
 欲解決此一問題,您可以將不同使用者的檔案存放在各自不同的目錄下。或是以您自定的命名規則,重新為檔案命名,使每個檔案的檔名都各不相同;萬一使用者上傳的檔案名稱中含有中文或空白字元,這個方法也可同時改善某些瀏覽器無法正常讀取的情況。

五、有人上傳了 PHP 檔,有沒有安全性的問題
 使用者透過瀏覽器上傳的檔案,其 owner 是 nobody。假如有人上傳了一個 PHP 程式檔,那麼他就可以透過該程式異動 nobody 有權存取的檔案與目錄了。
 建議您將使用者上傳的檔案,放在一個不能執行 PHP 程式的伺服器上;欲達到這個目的,您可以使用 PHP 中 FTP 這一類的函數。另外,您也可以在 Apache 中,將提供給使用者存放檔案的目錄,設定為不執行 PHP。

六、如何刪除一個目錄或檔案
 在 nobody 有權存取的條件下,您可以使用 unlink 函數來刪除一個檔案。另外,您可以使用 rmdir 函數來刪除一個目錄,但該目錄內的所有檔案必須先被清空。

七、如何搬移一個目錄或檔案
 由於 PHP 並沒有現成的「搬移目錄/檔案」的函數可用,所以我們改以「複製+刪除」的方法,來取代「搬移」的功能。
 
經驗交流