PHP 文件上传
PHP 文件上传
Section titled “PHP 文件上传”PHP 提供了内置功能来处理通过 HTML 表单上传到服务器的文件。
警告:允许文件上传会带来重大的安全风险。务必始终实施严格的检查和验证,以防止漏洞。
服务器配置 (php.ini)
Section titled “服务器配置 (php.ini)”在处理上传之前,请确保您的 PHP 配置允许文件上传。检查您的 php.ini 文件中的以下指令:
file_uploads = On:必须启用此选项以允许 HTTP 文件上传。upload_tmp_dir:指定上传过程中用于存储文件的临时目录(确保服务器在此目录有写入权限)。如果未设置,PHP 会使用系统默认值。upload_max_filesize:单个上传文件的最大大小(例如,8M表示 8 兆字节)。post_max_size:PHP 将接受的 POST 数据的最大大小。此值必须大于upload_max_filesize,并且足够大以容纳所有表单数据以及文件(例如,10M)。max_file_uploads:一次请求中可以同时上传的文件最大数量(默认值通常为 20)。
示例 php.ini 设置:
; 确保文件上传已启用file_uploads = On
; 设置单个文件的最大大小(例如,10 兆字节)upload_max_filesize = 10M
; 设置整个 POST 请求的最大大小(必须 >= upload_max_filesize)post_max_size = 12M注意:更改 php.ini 后,通常需要重启您的 Web 服务器(例如,Apache、Nginx)或 PHP-FPM 进程才能使更改生效。
创建 HTML 表单
Section titled “创建 HTML 表单”创建一个 HTML 表单,允许用户选择文件进行上传。此表单必须满足特定要求:
<!DOCTYPE html><html><head> <title>文件上传示例</title></head><body>
<form action="upload_handler.php" method="post" enctype="multipart/form-data"> <h2>选择要上传的图片:</h2> <input type="file" name="userFile" id="userFile"> <input type="submit" value="Upload Image" name="submit"></form>
</body></html>HTML 表单的关键要求:
- 方法必须是 POST (
method="post")。文件上传不能使用 GET。 - 编码类型必须是
multipart/form-data(enctype="multipart/form-data")。这允许发送二进制文件数据。 - 使用
<input type="file">元素提供文件选择界面。其name属性(示例中为userFile)对于在 PHP 中访问文件至关重要。
如果没有这些属性,文件上传将会失败。
安全提示:在实际应用中,考虑在表单中添加 CSRF(跨站请求伪造)保护 tokens。
此表单会将选定的文件及其他数据发送到 upload_handler.php,我们接下来将创建此文件。
创建 PHP 上传处理脚本
Section titled “创建 PHP 上传处理脚本”PHP 脚本(在我们的示例中为 upload_handler.php)处理上传的文件。关于上传文件的信息可在 $_FILES 超全局数组中获取。
$_FILES['userFile'] 数组(其中 userFile 与输入字段的 name 匹配)包含以下信息:
$_FILES['userFile']['name']:客户端机器上的原始文件名。$_FILES['userFile']['type']:文件的 mime 类型,如果浏览器提供了此信息(例如,‘image/jpeg’)。警告:此值由客户端控制,不可完全信任。$_FILES['userFile']['size']:上传文件的大小,以字节为单位。$_FILES['userFile']['tmp_name']:存储在服务器上的临时文件名。$_FILES['userFile']['error']:与此文件上传相关的错误代码。UPLOAD_ERR_OK(值为 0)表示成功。
基本上传逻辑:
<?php// 定义文件将要保存的目标目录$target_dir = "uploads/";
// 检查目标目录是否存在,如果不存在则创建if (!is_dir($target_dir)) { mkdir($target_dir, 0755, true); // 递归创建目录并设置权限}
// 检查表单是否已提交且文件输入字段是否存在if (isset($_POST["submit"]) && isset($_FILES["userFile"])) {
// --- 1. 检查上传错误 --- if ($_FILES["userFile"]["error"] !== UPLOAD_ERR_OK) { echo "Upload Error Code: " . $_FILES["userFile"]["error"]; // 处理特定错误(例如,UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE) // 提供用户友好的消息 exit; // 停止脚本执行 }
// --- 2. 生成安全的文件名 --- // 永远不要直接信任原始文件名! $original_filename = basename($_FILES["userFile"]["name"]); // 基本的清理 $file_extension = strtolower(pathinfo($original_filename, PATHINFO_EXTENSION));
// 创建唯一文件名以防止覆盖和命名冲突 $safe_filename = uniqid('file_', true) . '.' . $file_extension; $target_file = $target_dir . $safe_filename;
// --- 3. 验证文件大小 --- $max_filesize = 5 * 1024 * 1024; // 5 MB 限制(示例) if ($_FILES["userFile"]["size"] > $max_filesize) { echo "Error: File is too large (Max: " . ($max_filesize / 1024 / 1024) . " MB)."; exit; }
// --- 4. 验证文件类型(更安全的方式) --- // 不要仅仅依赖文件扩展名或客户端提供的 mime 类型。 $allowed_mime_types = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf']; $finfo = new finfo(FILEINFO_MIME_TYPE); $file_mime_type = $finfo->file($_FILES["userFile"]["tmp_name"]);
if (!in_array($file_mime_type, $allowed_mime_types)) { echo "Error: Invalid file type ('" . htmlspecialchars($file_mime_type) ."'). Allowed types: JPG, PNG, GIF, PDF."; exit; }
// 可选:使用 getimagesize 函数对图片文件进行更严格的检查 if (strpos($file_mime_type, 'image/') === 0) { if (getimagesize($_FILES["userFile"]["tmp_name"]) === false) { echo "Error: File is not a valid image."; exit; } }
// --- 5. 移动上传的文件 --- // 出于安全考虑,请使用 move_uploaded_file() if (move_uploaded_file($_FILES["userFile"]["tmp_name"], $target_file)) { echo "The file " . htmlspecialchars($original_filename) . " has been uploaded successfully as " . htmlspecialchars($safe_filename) . "."; } else { echo "Error: There was a problem moving the uploaded file. Check permissions and paths."; }
} else { // 处理表单未正确提交或文件未上传的情况 echo "Invalid request or no file uploaded.";}?>安全措施解释:
- 检查错误: 始终首先检查
$_FILES['userFile']['error']。 - 安全文件名: 切勿直接使用
$_FILES['userFile']['name']创建目标路径。它可能包含恶意字符(../),用于目录遍历攻击。使用basename()是一个基本步骤,但生成一个唯一、随机的文件名(例如使用uniqid()或散列)要安全得多。 - 验证大小: 根据脚本中定义的合理限制检查文件大小(并确保它在
php.ini设置的限制内)。 - 验证类型: 不要信任文件扩展名(
pathinfo)或客户端提供的 MIME 类型($_FILES['userFile']['type'])。使用服务器端方法,如finfo(Fileinfo 扩展)或mime_content_type(),从文件内容中确定实际的 MIME 类型。对于图片,getimagesize()可以提供额外的额外验证。 - 使用
move_uploaded_file(): 此函数专门用于处理上传文件。它会检查文件是否确实通过 HTTP POST 上传,防止攻击者诱骗脚本移动服务器上已有的任意文件。 - 安全目录: 如果可能,将上传的文件存储在 Web 根目录之外,或者如果存储在内部,请配置 Web 服务器以防止直接执行上传目录中的文件(例如,使用
.htaccess或 Nginx 配置)。
注意:确保目标目录(本例中的 uploads/)存在,并且 Web 服务器进程对此目录拥有写入权限。
处理多个文件上传
Section titled “处理多个文件上传”要处理使用相同输入名称上传的多个文件(例如,<input type="file" name="userFiles[]" multiple>),PHP 会以不同的方式组织 $_FILES 数组。您需要遍历结构化的 name、type、tmp_name、error 和 size 数组。
进一步阅读:
Section titled “进一步阅读:”查阅官方 PHP 文档以获取更多详细信息: