文件做爲一種特殊的表單數據,經過http post請求方式提交至服務器的時候,php會生成一個$_FILES全局數組,相關的文件信息會存放在這個全局數組中。我將在這篇文章中經過一些示例代碼來闡述php下的文件上傳,而且深刻看下關於文件上傳內部的實現機制,最後簡單說下如何增強這方面的安全性!php
爲了讓客戶端的用戶可以上傳文件,咱們必須在用戶界面中提供一個表單用於提交上傳文件的請求。因爲上傳的文件是一種特殊數據,不一樣於其它的post數據,因此咱們必須給表單設置一個特殊的編碼:html
<form action="upload.php" method="POST" enctype="multipart/form-data"></form>
以上的enctype屬性,你可能不太熟悉,由於這經常會被忽略掉。可是,若是http post請求中既有常規數據,又包含文件類數據的話,這個屬性就應該顯示加上,這樣能夠提升針對各類瀏覽器的兼容性。web
接下來,咱們得向表單中添加一個用於上傳文件的字段:數組
<input type="file" name="attachment">
上述文件字段在各類瀏覽器中可能表現會有所不一樣。對於大多數的瀏覽器,上述字段都會被渲染成一個文本框加上一個瀏覽按鈕。這樣,用戶既能夠自行輸入文件的路徑到文本框中,也能夠經過瀏覽按鈕從本地硬盤上選擇所要上傳的文件。可是,在蘋果的Safari中,貌似只能使用瀏覽這種方式。固然,你也能夠自定義這個上傳框的樣式,使它看起來比默認的樣式優雅些。瀏覽器
下面,爲了更好的闡述怎麼樣處理文件上傳,舉一個完整的例子。好比,如下一個表單容許用戶向個人本地服務器上上傳附件:安全
<p>請上傳你的附件:</p> <form action="upload.php" method="POST" enctype="multipart/form-data"> <input type="file" name="attachment"> <input type="submit" value="上傳附件"> </form>
提示:能夠經過php.ini中的upload_max_filesize來設置容許上傳文件的最大值。另外,還有一個post_max_size也能夠用來設置容許上傳的最大表單數據,具體意思就是表單中各類數據之和,因此你也能夠經過設置這個字段來控制上傳文件的最大值。可是,注意後者的值必須大於前者,由於前者屬於後者的一部分表單數據。服務器
圖1. 顯示在在firefox中的上傳表單函數
當這個表單提交的時候,http請求會被髮送到upload.php。爲了顯示具體哪些信息能夠在upload.php中使用,我在upload.php將其打印出來:post
header('Content-Type: text/plain'); print_r($_FILES);
下面來作個試驗,假如我經過以上表單上傳一個本博客的logo到個人本地服務器www.360weboy.me/upload.php,看看在upload.php中會輸出什麼信息:編碼
Array ( [attachment] => Array ( [name] => boy.jpg [type] => image/jpeg [tmp_name] => D:\xampp\tmp\php1168.tmp [error] => 0 [size] => 11490 ) )
以上就是文件上傳後,在全局數組中的關於當前上傳文件的全部信息。可是,咱們是否可以保證這些信息是安全的,假如name或者其它信息被篡改過了呢?咱們時刻須要對來自客戶端的信息保持警戒!
爲了更好的理解文件上傳,咱們必須覈對下客戶端發送的http請求中到底包含了那些具體的信息。先前我上傳的附件是本博客的logo,由於是圖片,不太適合咱們作以上實驗。因此,我從新上傳一個test.text文本文件,其中具體包含了如下內容:
360weboy 360days Life Of A Web Boy
Okay。如今我上傳這個文本文件,在upload.php中會輸出:
Array ( [attachment] => Array ( [name] => test.txt [type] => text/plain [tmp_name] => D:\xampp\tmp\php51C0.tmp [error] => 0 [size] => 40 ) )
咱們再來看下相關的瀏覽器發送的http post請求(一些可選的頭部我省略了):
POST /upload.php HTTP/1.1 Host: www.360weboy.me Referer: http://www.360weboy.me/ multipart/form-data; boundary=---------------------------24464570528145 Content-Length: 234 -----------------------------24464570528145 Content-Disposition: form-data; name="attachment"; filename="test.txt" Content-Type: text/plain 360weboy 360days Life Of A Web Boy -----------------------------24464570528145--
從上面的請求格式中有幾個字段咱們要關注下的,分別是name, filename以及Content-Type.它們分別表示上傳文件框在form表單中的字段名-attachment,用戶從本地硬盤中上傳的文件名 – test.txt,以及上傳的文件格式 – text/plain(表明文本文件)。而後,咱們看到一行空行下面的,就是這個上傳文件中的具體內容。
爲了增強文件上傳中的安全性,咱們須要檢查下$_FILES全局數組中的tmp_name和size。爲了確保tmp_name指向的文件確實是剛剛用戶在客戶端上傳的文件,而不是指向的相似/etc/passwd,可使用php中的函數is_uploaded_file()來進行下判斷:
$filename = $_FILES['attachment']['tmp_name']; if (is_uploaded_file($filename)) { /* 是一個上傳的文件. */ }
某些狀況下,用戶上傳文件後,可能會將上傳成功的文件的內容顯示給用戶看下,那麼上述代碼的檢查尤爲重要。
另一個須要檢查的就是上傳文件的mime-type, 也就是上述upload.php中輸出數組的type字段。 我在第一個例子中上傳的是一個圖片,因此$_FILES['attachment']['type']的值爲’image/jpeg’。 若是打算在服務器端只接受image/png, image/jpeg, image/gif, image/x-png 以及 image/p-jpeg這些mime-type的圖片,能夠用相似下面的代碼了進行檢查(只是舉個例子,具體代碼,好比報錯等,應該遵循你的系統中的機制):
$allow_mimes = array( 'image/png', 'image/x-png', 'image/gif', 'image/jpeg', 'image/pjpeg' ); $image = $_FILES['attachment']; if(!in_array($image['type'], $allow_mimes)) { die('對不起, 你上傳的文件格式不許確;咱們只接受圖片文件.'); } // 繼續處理上傳的圖片文件
正如你看到的,咱們已經保準了文件的mime-type是符合服務器端的要求的。可是,這樣是否是就能夠防止惡意用戶上傳其它有害文件,仍是不夠的,由於這個mime-type惡意用戶是能夠假裝的。 好比用戶作了一張jpg圖片,在圖片的元數據中寫入了一些惡意的php代碼,而後保存爲後綴名爲php的文件。當這個惡意文件上傳的時候,將順利經過服務器端對於mime-type的檢查,被認爲是一張圖片,裏面的危險的php代碼將會被執行。具體的圖片的元數據相似以下:
File name : image.jpg File size : 182007 bytes File date : 2012:11:27 7:45:10 Resolution : 1197 x 478 Comment : passthru($_POST['cmd']); __halt_compiler();
咱們能夠看到,在圖片元數據的Comment字段中加入了php代碼。因此,很顯然,爲了防止相似危險狀況發生,還必須對上傳文件的擴展名進行一次必要的檢查。下面的代碼對前面的檢查Mime-type的代碼進行了增強:
$allow_mimes = array( 'image/png' => '.png', 'image/x-png' => '.png', 'image/gif' => '.gif', 'image/jpeg' => '.jpg', 'image/pjpeg' => '.jpg' ); $image = $_FILES['attachment']; if(!array_key_exists($image['type'], $allow_mimes )) { die('對不起, 你上傳的文件格式不許確;咱們只接受圖片文件.'); } // 獲取略去後綴名的文件名: $filename = substr($image['name'], 0, strrpos($image['name'], '.')); // 添加後綴名 $filename .= $allow_mimes[$image['type']]; // 繼續處理上傳的文件
經過上述的代碼,咱們確保即便上傳的圖片的元文件中包含了php代碼的話,圖片文件會被重名爲後綴名爲圖片格式的文件,因此其中的php代碼也不會被執行了。上述代碼對正常的上傳的圖片也不會有任何負面影響。
進行了上述的幾步提升安全性的檢查步驟後,若是你只是要把上傳的文件保存到一個指定的目錄中,那麼就可使用php的默認函數move_uploaded_file來實現了:
$tmp_filename = $_FILES['attachment']['tmp_name']; $filename = '/path/to/attachment.txt'; if (move_uploaded_file(tmp_filename, $filename)) { /* $temp_filename 保存在臨時目錄中的上傳文件, 而後成功將其保存到對應目錄下的attachment.txt文件中. */ }
你也許還要對上傳文件的大小進行限制,那麼你能夠經過filesize函數來獲取上傳文件的大小,進行判斷後作進一步處理,這具體就不在這將了,本身去折騰吧。
好了,關於文件上傳暫時就寫到這裏吧。但願這篇入門篇文章對你有所幫助。後續有時間再補充該主題的博文吧!