<?php
// upload.php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!empty($_FILES['myfile']['name'])) {
if ($_FILES['myfile']['error'] > 0) {
die("錯誤狀態:{$_FILES['myfile']['error']}");
}
$dest = "uploads/" . basename($_FILES['myfile']['name']);
if (move_uploaded_file($_FILES['myfile']['tmp_name'], $dest)) {
echo "<script>alert('上傳成功!');</script>";
} else {
die("移動檔案失敗,請檢查目錄權限");
}
} else {
echo "<script>alert('請選擇檔案!');</script>";
}
}
?>
<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="file" name="myfile">
<input type="submit" value="上傳">
</form>
$_FILES['myfile'] 中的 myfile 對應 HTML ``:
| 鍵名 | 說明 | 範例 |
|---|---|---|
| name | 用戶端原始檔名 | photo.jpg |
| type | MIME 類型 (瀏覽器提供,不可信) | image/jpeg |
| tmp_name | 伺服器暫存路徑 | /tmp/phpA1B2C3 |
| error | 錯誤代碼 (0=成功) | 0, 1, 2, 3, 4, 6, 7, 8 |
| size | 檔案大小 (位元組) | 102400 |
UPLOAD_ERR_OK = 0 // 成功 UPLOAD_ERR_INI_SIZE = 1 // 超過 php.ini upload_max_filesize UPLOAD_ERR_FORM_SIZE = 2 // 超過表單 MAX_FILE_SIZE UPLOAD_ERR_PARTIAL = 3 // 部分上傳 UPLOAD_ERR_NO_FILE = 4 // 未選擇檔案 UPLOAD_ERR_NO_TMP_DIR = 6 // 缺少暫存目錄 UPLOAD_ERR_CANT_WRITE = 7 // 寫入失敗 UPLOAD_ERR_EXTENSION = 8 // PHP 擴展阻擋
僅有 move_uploaded_file() 或 is_uploaded_file() 能確認檔案真經由 HTTP POST 上傳,防止路徑遍歷攻擊。
<?php
class SafeUploader {
private $allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
private $maxSize = 2 * 1024 * 1024; // 2MB
private $uploadDir = 'uploads/';
private $allowedExt = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
public function upload($fieldName) {
if (!isset($_FILES[$fieldName]) || $_FILES[$fieldName]['error'] !== UPLOAD_ERR_OK) {
throw new Exception('上傳失敗或無檔案: ' . ($_FILES[$fieldName]['error'] ?? '未知'));
}
$file = $_FILES[$fieldName];
// 1. MIME 類型檢查 (參考用,不可完全信任)
if (!in_array($file['type'], $this->allowedTypes)) {
throw new Exception('不允許的檔案類型: ' . $file['type']);
}
// 2. 副檔名檢查
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($ext, $this->allowedExt)) {
throw new Exception('不允許的副檔名: ' . $ext);
}
// 3. 檔案大小
if ($file['size'] > $this->maxSize) {
throw new Exception('檔案過大,最大 ' . ($this->maxSize/1024/1024) . 'MB');
}
// 4. 內容偵測 (防偽裝檔案)
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$realMime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($realMime, $this->allowedTypes)) {
throw new Exception('檔案內容類型不符: ' . $realMime);
}
// 5. 產生安全檔名
$safeName = bin2hex(random_bytes(16)) . '.' . $ext;
$destPath = $this->uploadDir . $safeName;
// 6. 確保目錄存在
if (!is_dir($this->uploadDir)) {
mkdir($this->uploadDir, 0755, true);
}
// 7. 移動檔案
if (!move_uploaded_file($file['tmp_name'], $destPath)) {
throw new Exception('檔案移動失敗');
}
return [
'original_name' => $file['name'],
'saved_name' => $safeName,
'path' => $destPath,
'size' => $file['size'],
'mime' => $realMime
];
}
}
// 使用
try {
$uploader = new SafeUploader();
$result = $uploader->upload('myfile');
echo "上傳成功: " . json_encode($result);
} catch (Exception $e) {
echo "錯誤: " . $e->getMessage();
}
?>
file_uploads = On upload_max_filesize = 10M post_max_size = 12M ; 必須 > upload_max_filesize max_file_uploads = 20 ; 單次最多檔案數 upload_tmp_dir = /tmp ; 暫存目錄需可寫 max_execution_time = 60 ; 大檔案需調大 memory_limit = 128M
掌握 $_FILES 結構、錯誤代碼、move_uploaded_file 驗證機制,並實施 MIME/副檔名/內容/大小多層檢查,是構建安全檔案上傳功能的基石。