首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 文件上傳完整實現:後綴驗證、隨機命名、安全加固一站式方案

     編輯:PHP 綜合知識

項目結構

  • form.html — 上傳表單頁面
  • upload.php — 上傳處理邏輯
  • check.php — 核心工具函數(擴展名獲取、隨機命名)

1. 前端表單

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>圖片上傳</title>
</head>
<body>
    <form method="post" action="upload.php" enctype="multipart/form-data">
        <input type="hidden" name="MAX_FILE_SIZE" value="2000000">
        <input type="file" name="file" accept="image/*">
        <input type="submit" value="上傳" name="submit">
    </form>
</body>
</html>

關鍵點:enctype="multipart/form-data" 必須設置,MAX_FILE_SIZE 僅作客戶端提示,服務端必須再次校驗。

2. 核心工具函數

<?php
// check.php
header("Content-Type: text/html; charset=utf-8");

/**
 * 獲取文件擴展名(推薦 pathinfo)
 */
function getExtension(string $filename): string {
    return strtolower(pathinfo($filename, PATHINFO_EXTENSION));
}

/**
 * 生成隨機文件名
 * @param int $length 隨機部分長度
 * @param string $prefix 前綴
 */
function generateRandomName(int $length = 16, string $prefix = ''): string {
    $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $name = $prefix;
    for ($i = 0; $i < $length; $i++) {
        $name .= $chars[random_int(0, strlen($chars) - 1)];
    }
    return $name;
}

/**
 * 完整上傳處理流程
 * @return array ['success'=>bool, 'message'=>string, 'path'=>string|null]
 */
function handleUpload(array $file, array $allowedExts, string $uploadDir): array {
    // 1. 基礎檢查
    if ($file['error'] !== UPLOAD_ERR_OK) {
        return ['success' => false, 'message' => '上傳錯誤碼: ' . $file['error']];
    }

    // 2. 文件大小限制(2MB)
    if ($file['size'] > 2 * 1024 * 1024) {
        return ['success' => false, 'message' => '文件過大,限制 2MB'];
    }

    // 3. 擴展名驗證
    $ext = getExtension($file['name']);
    if (!in_array($ext, $allowedExts, true)) {
        return ['success' => false, 'message' => '不允許的文件類型'];
    }

    // 4. MIME 類型驗證(防偽裝)
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $mime = finfo_file($finfo, $file['tmp_name']);
    $allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
    if (!in_array($mime, $allowedMimes, true)) {
        return ['success' => false, 'message' => '文件內容類型不合法'];
    }

    // 5. 目錄檢查
    if (!is_dir($uploadDir)) {
        mkdir($uploadDir, 0755, true);
    }

    // 6. 生成唯一文件名
    $newName = generateRandomName(20, 'img_') . '.' . $ext;
    $destPath = $uploadDir . '/' . $newName;

    // 7. 移動文件
    if (!move_uploaded_file($file['tmp_name'], $destPath)) {
        return ['success' => false, 'message' => '文件移動失敗'];
    }

    // 8. 設置權限(僅讀)
    chmod($destPath, 0644);

    return ['success' => true, 'message' => '上傳成功', 'path' => $destPath];
}
?>

3. 上傳處理邏輯

<?php
// upload.php
require_once 'check.php';

$allowedExts = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
$uploadDir = __DIR__ . '/uploads';

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
    $result = handleUpload($_FILES['file'], $allowedExts, $uploadDir);
    
    if ($result['success']) {
        echo "<p style='color:green'>{$result['message']}</p>";
        echo "<p>文件路徑: {$result['path']}</p>";
        echo "<img src='{$result['path']}' style='max-width:300px'>";
    } else {
        echo "<p style='color:red'>錯誤: {$result['message']}</p>";
    }
    echo '<p><a href="form.html">返回繼續上傳</a></p>';
    exit;
}

header('Location: form.html');

4. 單文件合併版(快速部署)

<?php
// upload_all_in_one.php
header("Content-Type: text/html; charset=utf-8");

function getExtension(string $filename): string {
    return strtolower(pathinfo($filename, PATHINFO_EXTENSION));
}

function generateRandomName(int $length = 16): string {
    $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $name = '';
    for ($i = 0; $i < $length; $i++) {
        $name .= $chars[random_int(0, strlen($chars) - 1)];
    }
    return $name;
}

$allowedExts = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
$uploadDir = __DIR__ . '/uploads';

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['file'])) {
    $file = $_FILES['file'];
    
    if ($file['error'] !== UPLOAD_ERR_OK) die('上傳失敗: ' . $file['error']);
    if ($file['size'] > 2 * 1024 * 1024) die('文件過大');
    
    $ext = getExtension($file['name']);
    if (!in_array($ext, $allowedExts)) die('類型不允許');
    
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $mime = finfo_file($finfo, $file['tmp_name']);
    if (!in_array($mime, ['image/jpeg','image/png','image/gif','image/webp'])) die('內容類型非法');
    
    if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true);
    
    $newName = generateRandomName(20) . '.' . $ext;
    $dest = $uploadDir . '/' . $newName;
    
    if (move_uploaded_file($file['tmp_name'], $dest)) {
        chmod($dest, 0644);
        echo "上傳成功!<br><img src='$dest' style='max-width:300px'>";
    } else {
        echo '移動失敗';
    }
    exit;
}
?>
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>上傳</title></head><body>
<form method="post" enctype="multipart/form-data">
<input type="file" name="file" accept="image/*">
<input type="submit" value="上傳">
</form></body></html>

安全加固清單

  1. 禁用腳本執行權限:上傳目錄配置 Nginx location ~ \.php$ { deny all; } 或 Apache SetHandler default-handler
  2. 文件類型三重驗證:擴展名 + MIME + 文件頭魔數
  3. 隨機文件名:防覆蓋、防路徑遍歷、防猜測
  4. 大小限制雙端控制:PHP upload_max_filesize + 代碼邏輯判斷
  5. 錯誤信息不外洩:生產環境關閉 display_errors,記錄日志
  6. 病毒掃描:集成 ClamAV 定時掃描上傳目錄
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved