<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="file" name="product_img[]" multiple accept="image/*" />
<button type="submit" name="submit">上傳</button>
</form>
關鍵點:name="product_img[] 陣列命名、multiple 屬性、enctype="multipart/form-data"。
<?php
// 配置
$uploadDir = __DIR__ . '/Public/Upload/';
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
$maxSize = 5 * 1024 * 1024; // 5MB
if (!is_dir($uploadDir)) {
mkdir($uploadDir, 0755, true);
}
$results = ['success' => [], 'errors' => []];
if (isset($_POST['submit'])) {
$files = $_FILES['product_img'] ?? [];
$count = count($files['name']);
for ($i = 0; $i < $count; $i++) {
$error = $files['error'][$i];
if ($error !== UPLOAD_ERR_OK) {
$results['errors'][] = "文件 {$files['name'][$i]}: 錯誤代碼 $error";
continue;
}
$tmpName = $files['tmp_name'][$i];
$origName = basename($files['name'][$i]);
$type = $files['type'][$i];
$size = $files['size'][$i];
// 1. 類型驗證(防偽造 MIME)
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$realType = finfo_file($finfo, $tmpName);
finfo_close($finfo);
if (!in_array($realType, $allowedTypes, true)) {
$results['errors'][] = "$origName: 類型不允許 ($realType)";
continue;
}
// 2. 大小驗證
if ($size > $maxSize) {
$results['errors'][] = "$origName: 超過 5MB 限制";
continue;
}
// 3. 生成安全文件名
$ext = strtolower(pathinfo($origName, PATHINFO_EXTENSION));
$newName = bin2hex(random_bytes(8)) . '_' . time() . ".$ext";
$destPath = $uploadDir . $newName;
// 4. 移動文件
if (move_uploaded_file($tmpName, $destPath)) {
$results['success'][] = [
'original' => $origName,
'stored' => $newName,
'path' => $destPath,
'size' => $size,
'type' => $realType
];
} else {
$results['errors'][] = "$origName: 移動失敗(權限/磁盤空間)";
}
}
}
header('Content-Type: application/json; charset=UTF-8');
echo json_encode($results, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
?>
| 風險 | 對策 |
|---|---|
| 腳本上傳執行 | 上傳目錄配置 php_flag engine off / SetHandler default-handler,禁用腳本執行 |
| 文件名注入 | 忽略原始文件名,使用隨機名 + 白名單後綴 |
| MIME 偽造 | 用 finfo_file 檢測真實類型,不信任 $_FILES['type'] |
| 大文件 DoS | 限制 upload_max_filesize、post_max_size、單文件/總大小 |
| 目錄遍歷 | basename()、realpath() 校驗目標路徑在允許目錄內 |