POST 方法上传

此功能允许用户上传文本和二进制文件。通过 PHP 的身份验证和文件操作功能,您可以完全控制谁可以上传以及上传后如何处理文件。

PHP 能够接收来自任何符合 RFC-1867 的浏览器的文件上传。

注意: 相关配置说明

另请参阅 php.ini 中的 file_uploadsupload_max_filesizeupload_tmp_dirpost_max_sizemax_input_time 指令。

PHP 还支持 Netscape Composer 和 W3C 的 Amaya 客户端使用的 PUT 方法文件上传。有关更多详细信息,请参阅 PUT 方法支持

示例 #1 文件上传表单

可以通过创建类似于以下内容的特殊表单来构建文件上传屏幕

<!-- The data encoding type, enctype, MUST be specified as below -->
<form enctype="multipart/form-data" action="__URL__" method="POST">
    <!-- MAX_FILE_SIZE must precede the file input field -->
    <input type="hidden" name="MAX_FILE_SIZE" value="30000" />
    <!-- Name of input element determines name in $_FILES array -->
    Send this file: <input name="userfile" type="file" />
    <input type="submit" value="Send File" />
</form>

上述示例中的 __URL__ 应替换为指向 PHP 文件的 URL。

隐藏字段 MAX_FILE_SIZE(以字节为单位)必须位于文件输入字段之前,其值为 PHP 接受的最大文件大小。此表单元素应始终使用,因为它可以为用户节省等待大型文件传输的时间,然后才发现文件太大,传输失败。请记住:欺骗浏览器端的此设置非常容易,因此切勿依赖此功能来阻止更大的文件。它仅仅是客户端应用程序用户的便利功能。然而,PHP 设置(服务器端)的最大大小是无法欺骗的。

注意:

确保您的文件上传表单具有属性 enctype="multipart/form-data",否则文件上传将无法正常工作。

全局变量 $_FILES 将包含所有上传的文件信息。来自示例表单的内容如下所示。请注意,这假设使用文件上传名称 *userfile*,如上述示例脚本中所示。这可以是任何名称。

$_FILES['userfile']['name']

客户端机器上文件的原始名称。

$_FILES['userfile']['type']

文件的 MIME 类型,如果浏览器提供了此信息。例如 "image/gif"。但是,此 MIME 类型不会在 PHP 端进行检查,因此不要将其值视为可靠的。

$_FILES['userfile']['size']

上传文件的大小(以字节为单位)。

$_FILES['userfile']['tmp_name']

上传文件在服务器上存储的临时文件名。

$_FILES['userfile']['error']

与该文件上传相关的 错误代码

$_FILES['userfile']['full_path']

浏览器提交的完整路径。此值并不总是包含真实的目录结构,不可信赖。从 PHP 8.1.0 开始可用。

默认情况下,文件将存储在服务器的默认临时目录中,除非在 php.ini 中使用 upload_tmp_dir 指令指定了其他位置。可以通过在 PHP 运行的环境中设置环境变量 TMPDIR 来更改服务器的默认目录。在 PHP 脚本中使用 putenv() 设置此环境变量将不起作用。此环境变量也可以用于确保其他操作也能正常处理上传文件。

示例 #2 验证文件上传

另请参阅 is_uploaded_file()move_uploaded_file() 函数的条目以获取更多信息。以下示例将处理来自表单的文件上传。

<?php
$uploaddir
= '/var/www/uploads/';
$uploadfile = $uploaddir . basename($_FILES['userfile']['name']);

echo
'<pre>';
if (
move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
echo
"File is valid, and was successfully uploaded.\n";
} else {
echo
"Possible file upload attack!\n";
}

echo
'Here is some more debugging info:';
print_r($_FILES);

print
"</pre>";

?>

接收上传文件的 PHP 脚本应该实现确定如何处理上传文件的任何必要逻辑。例如,您可以使用 $_FILES['userfile']['size'] 变量丢弃任何太小或太大的文件。您可以使用 $_FILES['userfile']['type'] 变量丢弃任何不符合特定类型标准的文件,但仅将其用作一系列检查中的第一项,因为此值完全受客户端控制,不会在 PHP 端进行检查。此外,您可以使用 $_FILES['userfile']['error'] 并根据 错误代码 规划您的逻辑。无论逻辑如何,您都应该从临时目录中删除文件或将其移动到其他位置。

如果您的表单中没有选择要上传的文件,PHP 将返回 $_FILES['userfile']['size'] 为 0,以及 $_FILES['userfile']['tmp_name'] 为 none。

如果文件没有被移动或重命名,它将在请求结束时从临时目录中删除。

示例 #3 上传文件数组

PHP 甚至支持 HTML 数组功能 和文件。

<form action="" method="post" enctype="multipart/form-data">
<p>Pictures:
<input type="file" name="pictures[]" />
<input type="file" name="pictures[]" />
<input type="file" name="pictures[]" />
<input type="submit" value="Send" />
</p>
</form>
<?php
foreach ($_FILES["pictures"]["error"] as $key => $error) {
if (
$error == UPLOAD_ERR_OK) {
$tmp_name = $_FILES["pictures"]["tmp_name"][$key];
// basename() may prevent filesystem traversal attacks;
// further validation/sanitation of the filename may be appropriate
$name = basename($_FILES["pictures"]["name"][$key]);
move_uploaded_file($tmp_name, "data/$name");
}
}
?>

可以使用 会话上传进度 实现文件上传进度条。

添加备注

用户贡献的备注 12 备注

79
daevid at daevid dot com
15 年前
我认为附件数组的工作方式有点繁琐。通常 PHP 团队的想法都非常棒,但这很不直观。它应该是这样的

数组
(
[0] => 数组
(
[name] => facepalm.jpg
[type] => image/jpeg
[tmp_name] => /tmp/phpn3FmFr
[error] => 0
[size] => 15476
)

[1] => 数组
(
[name] =>
[type] =>
[tmp_name] =>
[error] => 4
[size] =>
)
)

而不是这样
数组
(
[name] => 数组
(
[0] => facepalm.jpg
[1] =>
)

[type] => 数组
(
[0] => image/jpeg
[1] =>
)

[tmp_name] => 数组
(
[0] => /tmp/phpn3FmFr
[1] =>
)

[error] => 数组
(
[0] => 0
[1] => 4
)

[size] => 数组
(
[0] => 15476
[1] => 0
)
)

总之,这里有一个比上面文档中的稀疏示例更完整的示例

<?php
foreach ($_FILES["attachment"]["error"] as $key => $error)
{
$tmp_name = $_FILES["attachment"]["tmp_name"][$key];
if (!
$tmp_name) continue;

$name = basename($_FILES["attachment"]["name"][$key]);

if (
$error == UPLOAD_ERR_OK)
{
if (
move_uploaded_file($tmp_name, "/tmp/".$name) )
$uploaded_array[] .= "Uploaded file '".$name."'.<br/>\n";
else
$errormsg .= "Could not move uploaded file '".$tmp_name."' to '".$name."'<br/>\n";
}
else
$errormsg .= "Upload error. [".$error."] on file '".$name."'<br/>\n";
}
?>
46
mpyw
8 年前
不要使用 Coreywelch 或 Daevid 的方法,因为他们的方法只能处理二维结构。$_FILES 可以包含任何层次结构,例如 3D 或 4D 结构。

以下示例表单会破坏他们的代码

<form action="" method="post" enctype="multipart/form-data">
<input type="file" name="files[x][y][z]">
<input type="submit">
</form>

作为解决方案,您应该使用基于 PSR-7 的 zendframework/zend-diactoros。

GitHub

https://github.com/zendframework/zend-diactoros

示例

<?php

use Psr\Http\Message\UploadedFileInterface;
use
Zend\Diactoros\ServerRequestFactory;

$request = ServerRequestFactory::fromGlobals();

if (
$request->getMethod() !== 'POST') {
http_response_code(405);
exit(
'Use POST method.');
}

$uploaded_files = $request->getUploadedFiles();

if (
!isset(
$uploaded_files['files']['x']['y']['z']) ||
!
$uploaded_files['files']['x']['y']['z'] instanceof UploadedFileInterface
) {
http_response_code(400);
exit(
'Invalid request body.');
}

$file = $uploaded_files['files']['x']['y']['z'];

if (
$file->getError() !== UPLOAD_ERR_OK) {
http_response_code(400);
exit(
'File uploading failed.');
}

$file->moveTo('/path/to/new/file');

?>
24
coreywelch+phpnet at gmail dot com
8 年前
文档中没有关于 HTML 数组功能如何格式化 $_FILES 数组的任何细节。

$_FILES 数组示例

对于单个文件 -

数组
(
[document] => Array
(
[name] => sample-file.doc
[type] => application/msword
[tmp_name] => /tmp/path/phpVGCDAJ
[error] => 0
[size] => 0
)
)

使用 HTML 数组功能的多文件 -

数组
(
[documents] => Array
(
[name] => 数组
(
[0] => sample-file.doc
[1] => sample-file.doc
)

[type] => 数组
(
[0] => application/msword
[1] => application/msword
)

[tmp_name] => 数组
(
[0] => /tmp/path/phpVGCDAJ
[1] => /tmp/path/phpVGCDAJ
)

[error] => 数组
(
[0] => 0
[1] => 0
)

[size] => 数组
(
[0] => 0
[1] => 0
)

)

)

当您的表单同时使用单个文件和 HTML 数组功能时,问题就出现了。数组没有被规范化,这往往使编码变得很糟糕。我已经包含了一个很好的方法来规范化 $_FILES 数组。

<?php

function normalize_files_array($files = []) {

$normalized_array = [];

foreach(
$files as $index => $file) {

if (!
is_array($file['name'])) {
$normalized_array[$index][] = $file;
continue;
}

foreach(
$file['name'] as $idx => $name) {
$normalized_array[$index][$idx] = [
'name' => $name,
'type' => $file['type'][$idx],
'tmp_name' => $file['tmp_name'][$idx],
'error' => $file['error'][$idx],
'size' => $file['size'][$idx]
];
}

}

return
$normalized_array;

}

?>

以下是上面方法的输出结果。

数组
(
[document] => Array
(
[0] => 数组
(
[name] => sample-file.doc
[type] => application/msword
[tmp_name] => /tmp/path/phpVGCDAJ
[error] => 0
[size] => 0
)

)

[documents] => Array
(
[0] => 数组
(
[name] => sample-file.doc
[type] => application/msword
[tmp_name] => /tmp/path/phpVGCDAJ
[error] => 0
[size] => 0
)

[1] => 数组
(
[name] => sample-file.doc
[type] => application/msword
[tmp_name] => /tmp/path/phpVGCDAJ
[error] => 0
[size] => 0
)

)

)
15
anon
9 年前
为了清楚起见,您不希望将示例脚本替换为
$uploaddir = './';
是因为如果您没有对文件进行编码限制,那么一个技术宅就可以上传一个 php 脚本,其名称与您脚本目录中的某个脚本相同。

在正确的设置和权限下,php-cgi 能够替换甚至 php 文件。

想象一下,如果它替换了上传后处理器文件本身。下一个“上传”可能会导致一些简单的漏洞利用。

即使替换不可能;上传一个 .htaccess 文件也可能导致一些问题,尤其是在技术宅在里面放了一个狡猾的脚本来使用 htaccess 重定向到他的上传之后。

可能还有更多方法可以利用它。不要让技术宅们抓住你的把柄。

更明智的做法是使用一个新的目录来进行上传,并使用某种唯一命名算法;甚至可以使用 cron 作业来清理目录,这样旧文件就不会停留太长时间。
6
fravadona at gmail dot com
4 年前
mpyw 是对的,PSR-7 很棒,但对于简单的项目来说有点过分(在我看来)。

以下是一个函数示例,它以(类似于 PSR-7 的)规范化树形式返回文件上传元数据。此函数处理任何维度的上传元数据。

我保持代码极其简单,它不会验证 $_FILES 中的任何内容,等等... 而且最重要的是,它以“未定义的行为”的方式调用 array_walk_recursive!!!

您可以针对 PSR-7 规范中的示例(https://www.php-fig.org/psr/psr-7/#16-uploaded-files)进行测试,并尝试添加您自己的检查来检测最后一个示例中的错误 ^^

<?php
/**
* 此代码绝对不适合生产环境!!!希望它的见解能够帮助您!!!
*/
function getNormalizedFiles()
{
$normalized = array();

if ( isset(
$_FILES) ) {

foreach (
$_FILES as $field => $metadata ) {

$normalized[$field] = array(); // 需要初始化用于 array_replace_recursive

foreach ( $metadata as $meta => $data ) { // $meta 是 'tmp_name'、'error' 等等...

if ( is_array($data) ) {

// 在每个叶节点之前插入当前元数据!!!错误使用了 ARRAY_WALK_RECURSIVE!!!
array_walk_recursive($data, function (&$v,$k) use ($meta) { $v = array( $meta => $v ); });

// 将当前元数据与之前的元数据融合
$normalized[$field] = array_replace_recursive($normalized[$field], $data);

} else {
$normalized[$field][$meta] = $data;
}
}
}
}
return
$normalized;
}
?>
14
eslindsey at gmail dot com
15 年前
还要注意,由于 MAX_FILE_SIZE 隐藏字段是由执行提交的浏览器提供的,因此很容易从客户端侧覆盖。您应该始终对到达您的文件进行自己的检查和错误检查,而不是依赖客户端提交的信息。这包括对文件大小(始终检查实际数据的长度与报告的文件大小)以及文件类型(浏览器提交的 MIME 类型充其量可能不准确,最坏情况下可能被故意设置为不正确的值)进行检查。
5
Mark
13 年前
如果用户尝试上传的文件大小超过 php.ini 中的 post_max_size,$_FILES 将为空。

php.ini 中的 post_max_size 应大于等于 upload_max_filesize。
5
claude dot pache at gmail dot com
15 年前
请注意,MAX_FILE_SIZE 隐藏字段仅由接收请求的 PHP 脚本用作指示,用于拒绝大于给定边界的文件。此字段对浏览器没有意义,它不提供文件大小的客户端检查,并且与 Web 标准或浏览器功能无关。
-1
Anonymous
7 年前
我发现将多维 $_FILES 数组重新排序为更直观的格式非常有用,正如许多其他开发人员已经提出的那样。

不幸的是,大多数提议的函数在 $_FILES 数组具有多个附加维度时无法重新排序它。

因此,我想贡献下面的函数,它能够满足上述要求

<?php
function get_fixed_files() {
$function = function($files, $fixed_files = array(), $path = array()) use (&$function) {
foreach (
$files as $key => $value) {
$temp = $path;
$temp[] = $key;

if (
is_array($value)) {
$fixed_files = $function($value, $fixed_files, $temp);
} else {
$next = array_splice($temp, 1, 1);
$temp = array_merge($temp, $next);

$new = &$fixed_files;

foreach (
$temp as $key) {
$new = &$new[$key];
}

$new = $value;
}
}

return
$fixed_files;
};

return
$function($_FILES);
}
?>

旁注:函数内的匿名函数用于避免对函数递归所需的参数产生混淆,例如在 IDE 中查看函数时。
-7
Age Bosma
13 年前
“如果您的表单中没有选择要上传的文件,PHP 将返回 $_FILES['userfile']['size'] 为 0,$_FILES['userfile']['tmp_name'] 为 none。”

请注意,以上情况与上传超过 MAX_FILE_SIZE 隐藏字段的文件时的情况相同。在这种情况下,$_FILES['userfile']['size'] 也被设置为 0,$_FILES['userfile']['tmp_name'] 也为空。唯一的区别是错误代码。
仅检查这两个条件并假设没有尝试上传文件是不正确的。

相反,检查 $_FILES['userfile']['name'] 是否已设置。如果已设置,则至少已尝试上传文件(无论是失败还是成功)。如果未设置,则未进行任何尝试。
-11
bimal at sanjaal dot com
9 年前
这里有一些建议

1. 始终检查错误状态。如果 MAX_FILE_SIZE 生效并且上传的文件超过限制,它将设置错误。因此,仅当错误为零 (0) 时,才移动文件。

2. 如果可能,不要允许您的脚本在可以下载文件的路径中上传。将您的上传路径指向 public_html 区域之外或防止直接浏览(使用 .htaccess 限制)。请考虑,如果有人上传恶意代码,尤其是 php 代码,它们将在服务器上执行。

3. 不要使用客户端发送的文件名。为新上传的文件生成一个新名称。这可以防止覆盖您的旧文件。

4. 定期跟踪磁盘空间使用情况,如果您快用完存储空间。
-17
katrinaelaine6 at gmail dot com
6 年前
这是一个包含嵌套和非嵌套名称的 $_FILES 数组的完整示例。假设我们有以下 html 表单

<form action="test.php" method="post">

<input type="file" name="single" id="single">

<input type="file" name="nested[]" id="nested_one">
<input type="file" name="nested[root]" id="nested_root">
<input type="file" name="nested[][]" id="nested_two">
<input type="file" name="nested[][parent]" id="nested_parent">
<input type="file" name="nested[][][]" id="nested_three">
<input type="file" name="nested[][][child]" id="nested_child">

<input type="submit" value="Submit">

</form>

在 test.php 文件中

<?php

print_r
($_FILES);
exit;

?>

如果我们上传一个与每个输入的 id 相同的文件名的文本文件,然后单击提交,test.php 将输出以下内容

<?php

Array
(

[
single] => Array
(
[
name] => single.txt
[type] => text/plain
[tmp_name] => /tmp/phpApO28i
[error] => 0
[size] => 3441
)

[
nested] => Array
(
[
name] => Array
(
[
0] => nested_one.txt
[root] => nested_root.txt
[1] => Array
(
[
0] => nested_two.txt
)

[
2] => Array
(
[
parent] => nested_parent.txt
)

[
3] => Array
(
[
0] => Array
(
[
0] => nested_three.txt
)

)

[
4] => Array
(
[
0] => Array
(
[
child] => nested_child.txt
)

)

)

// type, tmp_name, size, and error 将具有相同的结构。
)

)

?>
To Top