用PHP上传文件

What do pictures in an online photo album, email attachments in a web-based mail client, and data files submitted to an online application for batch processing all have in common? They all rely on the ability to upload files across the Internet from the user’s web browser. Indeed, uploading files is an important feature of many of the sites and web-based applications we use on a daily basis. In this post, I show you how to add support for file uploads to your site using PHP.

在线相册中的图片,基于Web的邮件客户端中的电子邮件附件以及提交给在线应用程序以进行批处理的数据文件有什么共同点? 它们都依赖于从用户的Web浏览器通过Internet上传文件的功能。 实际上,上传文件是我们每天使用的许多网站和基于Web的应用程序的重要功能。 在本文中,我将向您展示如何使用PHP添加对文件上传到站点的支持。

要求 (Requirements)

Handling file uploads isn’t difficult, but there are a handful of small details that must be correct or else the upload will fail. First, you need to ensure PHP is configured to allow uploads. Check your php.ini file and verify the file_uploads directive is set On.

处理文件上传并不困难,但是有一些小细节必须正确,否则上传将失败。 首先,您需要确保将PHP配置为允许上传。 检查您的php.ini文件,并验证file_uploads指令设置为On

file_uploads = On

Uploaded files are first stored in a temporary directory (don’t worry… your PHP script can move the files to a more permanent location afterward). By default, the initial location is the system’s default temporary directory. You can specify a different directory using the upload_tmp_dir directive in php.ini. Regardless, you should verify the PHP process has the proper privileges to write to whichever directory is used.

上传的文件首先存储在一个临时目录中(请放心,您PHP脚本随后可以将文件移至更永久的位置)。 默认情况下,初始位置是系统的默认临时目录。 您可以使用php.iniupload_tmp_dir指令指定其他目录。 无论如何,您都应验证PHP进程具有适当的特权以写入所使用的任何目录。

upload_tmp_dir = "/tmp"

tboronczyk@zarkov:~$ ls -l / | grep tmp
drwxrwxrwt  13 root root 40960 2011-08-31 00:50 tmp

After you’re certain the configuration allows the server to accept uploaded files, you can focus your attention on the HTML details. As with most other server-side interactions from HTML, uploading files make use of forms. It is imperative that your <form> element uses the POST method and has an enctype attribute set to multipart/form-data.

确定配置允许服务器接受上传的文件后,您可以将注意力集中在HTML详细信息上。 与大多数其他来自HTML的服务器端交互一样,上载文件也使用表格。 您的<form>元素必须使用POST方法,并且必须将enctype属性设置为multipart/form-data

<form action="upload.php" method="post" enctype="multipart/form-data">

编写上传过程脚本 (Scripting the Upload Process)

You can probably guess the workflow file uploads go through based on your own experiences and the requirement checks I’ve just mentioned.

您可能会根据自己的经验和我刚刚提到的需求检查来猜测工作流文件的上传过程。

  • A visitor views an HTML page with a form specifically written to support file uploads

    访客使用专门支持文件上传的表单查看HTML页面
  • The visitor provides the file he wants to upload and submits the form

    访客提供他要上传的文件并提交表格
  • The browser encodes the file and sends it as part of the POST request it makes to the server

    浏览器对文件进行编码,并将其作为POST请求的一部分发送给服务器
  • PHP receives the form submission, decodes the file and saves it in a temporary location on the server

    PHP接收表单提交,解码文件并将其保存在服务器上的临时位置
  • The PHP script responsible for handling the form post verifies the file and processes it in some manner, often moving it from its temporary location to a more permanent home

    负责处理表单发布PHP脚本验证文件并以某种方式处理它,通常将其从其临时位置移至更永久的位置

Adding support for file uploads requires you to create an HTML form to be presented to the user and a PHP script to take care of the uploaded file on the server.

要添加对文件上传的支持,您需要创建一个呈现给用户HTML表单和一个PHP脚本来处理服务器上的上传文件。

HTML (HTML)

HTML forms provide the interface through which a user initiates a file upload. Remember, the <form> element must have its method attribute set to post and its enctype attribute set to multipart/form-data. A file <input> element provides the a field used to specify the file that will be upload. Like any other form element, it is important you provide a name attribute so you can reference it in the PHP script that processes the form.

HTML表单提供了一个界面,用户可以通过该界面启动文件上传。 请记住, <form>元素必须将其method属性设置为post并将其enctype属性设置为multipart/form-data 。 文件<input>元素提供了一个字段,用于指定将要上传的文件。 与其他任何表单元素一样,提供name属性非常重要,这样您就可以在处理表单PHP脚本中引用它。

Here’s what markup for a basic file upload form looks like:

基本文件上传表单的标记如下所示:

<form action="upload.php" method="post" enctype="multipart/form-data"> 
 <input type="file" name="myFile">
 <br>
 <input type="submit" value="Upload">
</form>

It’s worth noting that different browsers will render the file field differently. IE, Firefox, and Opera display it as a text field with a button next to it labeled “Browse” or “Choose.” Safari renders it just as button labeled “Choose File.” This isn’t a problem most of the time since users are accustomed to how the field renders in their browser of choice and know how to use it. Occasionally, however, you will be faced with a client or designer who is adamant on presenting it a certain way. The amount of CSS and JavaScript that can be applied to a file field is extremely limited because of security reasons imposed by the browsers. Styling the file field can be difficult. If appearance is important for you, I recommend you check out Peter-Paul Koch’s Styling an input type=”file”.

值得注意的是,不同的浏览器将以不同的方式呈现文件字段。 IE,Firefox和Opera将其显示为文本字段,并在其旁边的按钮上标有“浏览”或“选择”。 Safari就像标记为“选择文件”的按钮一样呈现它。 大多数情况下,这不是问题,因为用户已经习惯了该字段在所选浏览器中的呈现方式,并且知道如何使用它。 但是,有时候,您会遇到客户或设计师,他们坚持以某种方式展示它。 由于浏览器强加的安全性原因,可应用于文件字段CSS和JavaScript的数量非常有限。 设置文件字段的样式可能很困难。 如果外观对您很重要,我建议您检查一下Peter-Paul Koch的输入类型=“文件”样式

PHP (PHP)

Information about the file upload is made available with the multidimensional $_FILES array. This array is indexed by the names assigned to the file fields in the HTML form, just as how $_GET and $_POST are indexed. Each file’s array then contains the following indexes:

多维$_FILES数组可提供有关文件上传的信息。 该数组由分配给HTML表单中文件字段的名称索引,就像$_GET$_POST索引的方式一样。 每个文件的数组都包含以下索引:

  • $_FILES["myFile"]["name"] stores the original filename from the client

    $_FILES["myFile"]["name"]存储来自客户端的原始文件名

  • $_FILES["myFile"]["type"] stores the file’s mime-type

    $_FILES["myFile"]["type"]存储文件的mime类型

  • $_FILES["myFile"]["size"] stores the file’s size (in bytes)

    $_FILES["myFile"]["size"]存储文件的大小(以字节为单位)

  • $_FILES["myFile"]["tmp_name"] stores the name of the temporary file

    $_FILES["myFile"]["tmp_name"]存储临时文件的名称

  • $_FILES[“myFile”][“error”] stores any error code resulting from the transfer

    $ _FILES [“ myFile”] [“ error”]存储由传输导致的任何错误代码

The move_uploaded_file() function moves an uploaded file from its temporary to permanent location. You should always use move_uploaded_file() over functions like copy() and rename() for this purpose because it performs additional checks to ensure the file was indeed uploaded by the HTTP POST request.

move_uploaded_file()函数将上载的文件从其临时位置移动到永久位置。 你应该总是使用move_uploaded_file()在功能类似于copy()rename()用于此目的,因为它执行额外的检查,以确保通过HTTP POST请求确实是上传的文件。

If you plan on saving a file with the original filename provided by the user, it’s a good idea to make sure it’s safe to do so. The filename should not contain any characters that can affect the destination path, such as a slash. The name shouldn’t cause the file to overwrite an existing file with the same name, either (unless that’s what your application is designed to do). I ensure a safe filename by replacing any characters with an underscore that aren’t a letter, number, or a member of a very restricted set of punctuation, and then append an incrementing number when a file by that name already exists.

如果您打算使用用户提供的原始文件名保存文件,则最好确保这样做是安全的。 文件名不应包含任何可能影响目标路径的字符,例如斜杠。 该名称也不应导致文件覆盖具有相同名称的现有文件(除非这是您的应用程序设计的目的)。 我可以通过用下划线代替字母,数字或一组非常受限制的标点符号的字符来确保文件名的安全,然后在使用该名称的文件已经存在时追加一个递增的数字。

Here’s what receiving and processing a file upload with PHP looks like:

这是使用PHP接收和处理文件上传的样子:

<?php
define("UPLOAD_DIR", "/srv/www/uploads/");

if (!empty($_FILES["myFile"])) {
    $myFile = $_FILES["myFile"];

    if ($myFile["error"] !== UPLOAD_ERR_OK) {
        echo "<p>An error occurred.</p>";
        exit;
    }

    // ensure a safe filename
    $name = preg_replace("/[^A-Z0-9._-]/i", "_", $myFile["name"]);

    // don't overwrite an existing file
    $i = 0;
    $parts = pathinfo($name);
    while (file_exists(UPLOAD_DIR . $name)) {
        $i++;
        $name = $parts["filename"] . "-" . $i . "." . $parts["extension"];
    }

    // preserve file from temporary directory
    $success = move_uploaded_file($myFile["tmp_name"],
        UPLOAD_DIR . $name);
    if (!$success) { 
        echo "<p>Unable to save file.</p>";
        exit;
    }

    // set proper permissions on the new file
    chmod(UPLOAD_DIR . $name, 0644);
}

The code first makes sure the file uploaded without any errors. It then determines a safe filename as I just described, and then moves the file to its final directory using move_uploaded_file(). Finally, there is a call to chmod() to make sure sane access permissions are set on the new file.

该代码首先确保文件上传没有任何错误。 然后,它确定一个安全的文件名(如我刚才所述),然后使用move_uploaded_file()将文件移至其最终目录。 最后,调用chmod()以确保在新文件上设置了合理的访问权限。

安全注意事项 (Security Considerations)

Most of us wouldn’t let complete strangers store random files on our personal computers, and yet that is exactly what you are doing when you allow file uploads in our application. You may intend for a user to upload a picture of himself for a profile page, but what if he tries to upload a specially-crafted, virus-laden executable instead? I’d like to share a few steps that you can take to minimize the security risks inherent in allowing file uploads.

我们大多数人不会让完全陌生的人在我们的个人计算机上存储随机文件,但是,当您允许在我们的应用程序中上传文件时,这正是您要做的事情。 您可能希望用户将自己的图片上传到个人资料页面,但是如果他尝试上传特制的,载有病毒的可执行文件怎么办? 我想分享一些步骤,以最大程度地减少允许上传文件时固有的安全风险。

One is to verify the type of the uploaded file is what it should be. Relying on either the value of $_FILES["myFile"]["type"] or on the filename’s extension isn’t secure because both can easily be spoofed. Rather, use a function like exif_imagetype() to examine the contents of the file and determine if it is indeed a GIF, JPEG, or one of several other supported image formats. If exif_imagetype() isn’t available (the function requires the Exif extension to be enabled), then you can use getimagesize(). The array returned by getimagesize() will contain the image type if it is recognized.

一种是验证上传文件的类型是否正确。 不依靠$_FILES["myFile"]["type"]或文件名的扩展名是不安全的,因为两者很容易被欺骗。 而是使用exif_imagetype()类的函数来检查文件的内容,并确定它是否确实是GIF,JPEG或其他几种受支持的图像格式之一。 如果exif_imagetype()不可用(该函数需要启用Exif扩展名),则可以使用getimagesize() 。 如果可以识别,由getimagesize()返回的数组将包含图像类型。

<?php
// verify the file is a GIF, JPEG, or PNG
$fileType = exif_imagetype($_FILES["myFile"]["tmp_name"]);
$allowed = array(IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG);
if (!in_array($fileType, $allowed)) {
    // file type is not permitted
    ...

For non-image files, you can use exec() to invoking the unix file utility. file determines a file’s type by looking for known binary signatures in expected locations.

对于非图像文件,可以使用exec()来调用unix file实用程序。 file通过在预期位置查找已知的二进制签名来确定文件的类型。

<?php
// verify the file is a PDF
$mime = "application/pdf; charset=binary";
exec("file -bi " . $_FILES["myFile"]["tmp_name"], $out);
if ($out[0] != $mime) {
    // file is not a PDF
    ...

Another step you can take is to impose hard limits on the total size of the POST request and the number of files that can be uploaded. To do so, specify an appropriate value for the upload_max_size, post_max_size, and max_file_uploads directives in php.ini. The upload_max_size directive specifies the maximum size a file upload can be. In addition to the size of the upload, you can limit the size of the entire POST request with the post_max_size directive. max_file_uploads is a newer directive (added in version 5.2.12) which limits the number of file uploads. These three directives help protect your site against attacks that try to disrupt its availability by causing heavy network traffic or system load.

您可以采取的另一步骤是对POST请求的总大小和可以上传的文件数施加硬性限制。 要做到这一点,指定一个合适的值upload_max_sizepost_max_sizemax_file_uploads在指令php.iniupload_max_size指令指定文件可以上传的最大大小。 除了上传的大小外,您还可以使用post_max_size指令限制整个POST请求的大小。 max_file_uploads是较新的指令(在5.2.12版中添加),它限制了文件上载的数量。 这三个指令有助于保护您的站点免受那些试图通过引起大量网络流量或系统负载来破坏其可用性的攻击。

post_max_size = 8M
upload_max_size = 2M
max_file_uploads = 20

A third step you can take to minimize your risk is to scan uploaded files with a virus scanner. This is vitally important in this day and age of widespread viruses and malware, especially if your site later makes uploaded files available for download by other individuals, such as with attachments in a web-based email client or a (legal) file-sharing site. There is a PHP extension that provides access to ClamAV, but of course you can invoke ClamAV’s command-line utility in much the same way I demonstrated for file.

您可以采取的降低风险的第三步是使用病毒扫描程序扫描上传的文件。 在当今广泛传播的病毒和恶意软件的时代,这一点至关重要,尤其是如果您的站点后来使上载的文件可供其他人下载,例如基于Web的电子邮件客户端或(合法)文件共享站点中的附件时,这一点尤为重要。 。 有一个PHP扩展提供对ClamAV的访问,但是您当然可以调用ClamAV的命令行实用程序,其方式与我为file演示的方式几乎相同。

<?php
exec("clamscan --stdout " . $_FILES["myFile"]["tmp_name"], $out, $return);
if ($return) {
    // file is infected
    ...

摘要 (Summary)

You’ve learned how easy it is to support file uploads with your site or web-based application. For the upload to succeed, the HTML form must be submitted via a multipart/form-data encoded POST request, and PHP must permit the transfer as specified using the file_uploads directive. After the file is transferred, the script responsible for handling the upload uses the information found in the $_FILES array to move the file from the temporary directory to the desired location. I also shared some extra precautions that you can take to protect yourself and your users from some of the risks associated with allowing file uploads. You saw how you can ensure the filename is safe, verify the file type, impose hard limits on upload traffic, and scan for viruses.

您已经了解了使用网站或基于Web的应用程序支持文件上传是多么容易。 为了使上传成功,HTML表单必须通过multipart/form-data编码的POST请求提交,并且PHP必须允许使用file_uploads指令指定的传输。 传输文件后,负责处理上载的脚本将使用$_FILES数组中的信息将文件从临时目录移动到所需位置。 我还分享了一些额外的预防措施,您可以采取这些预防措施来保护自己和用户免遭与允许文件上传相关的某些风险。 您了解了如何确保文件名安全,验证文件类型,对上传流量施加硬限制以及如何扫描病毒。

For those who may be interested, supplemental code for this article is available on GitHub. You can view, download, or clone the repository and play with the code to get a better understanding of how the process of uploading files works.

对于那些可能感兴趣的人,可以在GitHub上获得本文的补充代码 。 您可以查看,下载或克隆存储库,并使用代码来更好地了解文件上传过程的工作方式。

And if you enjoyed reading this post, you’ll love Learnable; the place to learn fresh skills and techniques from the masters. Members get instant access to all of SitePoint’s ebooks and interactive online courses, like Jump Start PHP.

并且,如果您喜欢阅读这篇文章,您会喜欢Learnable的 向大师学习新鲜技能的地方。 会员可以立即访问所有SitePoint的电子书和交互式在线课程,例如Jump Start PHP

Comments on this article are closed. Have a question about PHP? Why not ask it on our forums?

本文的评论已关闭。 对PHP有疑问吗? 为什么不在我们的论坛上提问呢?

Image via VolsKinvols / Shutterstock

图片来自VolsKinvols / Shutterstock

翻译自: https://www.sitepoint.com/file-uploads-with-php/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值