• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • 文件上传处理

    You'd better check $_FILES structure and values throughly.
    The following code cannot cause any errors absolutely.
    Example:
    <?php
    header('Content-Type: text/plain; charset=utf-8');
    try {
      
      // Undefined | Multiple Files | $_FILES Corruption Attack
      // If this request falls under any of them, treat it invalid.
      if (
        !isset($_FILES['upfile']['error']) ||
        is_array($_FILES['upfile']['error'])
      ) {
        throw new RuntimeException('Invalid parameters.');
      }
      // Check $_FILES['upfile']['error'] value.
      switch ($_FILES['upfile']['error']) {
        case UPLOAD_ERR_OK:
          break;
        case UPLOAD_ERR_NO_FILE:
          throw new RuntimeException('No file sent.');
        case UPLOAD_ERR_INI_SIZE:
        case UPLOAD_ERR_FORM_SIZE:
          throw new RuntimeException('Exceeded filesize limit.');
        default:
          throw new RuntimeException('Unknown errors.');
      }
      // You should also check filesize here. 
      if ($_FILES['upfile']['size'] > 1000000) {
        throw new RuntimeException('Exceeded filesize limit.');
      }
      // DO NOT TRUST $_FILES['upfile']['mime'] VALUE !!
      // Check MIME Type by yourself.
      $finfo = new finfo(FILEINFO_MIME_TYPE);
      if (false === $ext = array_search(
        $finfo->file($_FILES['upfile']['tmp_name']),
        array(
          'jpg' => 'image/jpeg',
          'png' => 'image/png',
          'gif' => 'image/gif',
        ),
        true
      )) {
        throw new RuntimeException('Invalid file format.');
      }
      // You should name it uniquely.
      // DO NOT USE $_FILES['upfile']['name'] WITHOUT ANY VALIDATION !!
      // On this example, obtain safe unique name from its binary data.
      if (!move_uploaded_file(
        $_FILES['upfile']['tmp_name'],
        sprintf('./uploads/%s.%s',
          sha1_file($_FILES['upfile']['tmp_name']),
          $ext
        )
      )) {
        throw new RuntimeException('Failed to move uploaded file.');
      }
      echo 'File is uploaded successfully.';
    } catch (RuntimeException $e) {
      echo $e->getMessage();
    }
    ?>
    
    If "large files" (ie: 50 or 100 MB) fail, check this:
    It may happen that your outgoing connection to the server is slow, and it may timeout not the "execution time" but the "input time", which for example in our system defaulted to 60s. In our case a large upload could take 1 or 2 hours.
    Additionally we had "session settings" that should be preserved after upload.
    1) You might want review those ini entries:
    * session.gc_maxlifetime
    * max_input_time
    * max_execution_time
    * upload_max_filesize
    * post_max_size
    2) Still fails? Caution, not all are changeable from the script itself. ini_set() might fail to override.
    More info here:
    http://www.php.net/manual/es/ini.list.php
    You can see that the "upload_max_filesize", among others, is PHP_INI_PERDIR and not PHP_INI_ALL. This invalidates to use ini_set():
    http://www.php.net/manual/en/configuration.changes.modes.php
    Use .htaccess instead.
    3) Still fails?. Just make sure you enabled ".htaccess" to overwrite your php settings. This is made in the apache file. You need at least AllowOverride Options.
    See this here:
    http://www.php.net/manual/en/configuration.changes.php
    You will necessarily allow this manually in the case your master files come with AllowOverride None.
    Conclussion:
    Depending on the system, to allow "large file uploads" you must go up and up and up and touch your config necessarily up to the apache config.
    Sample files:
    These work for me, for 100MB uploads, lasting 2 hours:
    In apache-virtual-host:
    -----------------------------------------------------------
    <Directory /var/www/MyProgram>
      AllowOverride Options
    </Directory>
    -----------------------------------------------------------
    In .htaccess:
    -----------------------------------------------------------
    php_value session.gc_maxlifetime 10800
    php_value max_input_time     10800
    php_value max_execution_time   10800
    php_value upload_max_filesize  110M
    php_value post_max_size     120M
    -----------------------------------------------------------
    In the example,
    - As I last 1 to 2 hours, I allow 3 hours (3600x3)
    - As I need 100MB, I allow air above for the file (110M) and a bit more for the whole post (120M).
    Also stumbled on the max_file_size problem, in particular getting no response, no error whatsoever when uploading a file bigger than the set upload_max_filesize.
    I found that it's not the upload_max_filesize setting, but instead the post_max_size setting causing this no response issue. So if you set post_max_size way larger than upload_max_filesize, at least you are likely to get an error response when filesize exceeds upload_max_filesize but is still within the limits of post_max_size.
    Hope this helps anyone.
    A little codesnippet which returns a filesize in a more legible format.
    <?php
    function display_filesize($filesize){
      
      if(is_numeric($filesize)){
      $decr = 1024; $step = 0;
      $prefix = array('Byte','KB','MB','GB','TB','PB');
        
      while(($filesize / $decr) > 0.9){
        $filesize = $filesize / $decr;
        $step++;
      } 
      return round($filesize,2).' '.$prefix[$step];
      } else {
      return 'NaN';
      }
      
    }
    ?>
    
    Your binary files may be uploaded incorrectly if you use modules what recode characters. For example, for Russian Apache, you should use 
    <Files ScriptThatReceivesUploads.php>
    CharsetDisable On
    </Files>
    This is simpler method of checking for too much POST data (alternative to that by v3 from sonic-world.ru).
    <?php
      if ($_SERVER['REQUEST_METHOD'] == 'POST' && empty($_POST) && $_SERVER['CONTENT_LENGTH'] > 0) {
        throw new Exception(sprintf('The server was unable to handle that much POST data (%s bytes) due to its current configuration', $_SERVER['CONTENT_LENGTH']));
      }
    ?>
    
    IE on the Mac is a bit troublesome. If you are uploading a file with an unknown file suffix, IE uploads the file with a mime type of "application/x-macbinary". The resulting file includes the resource fork wrapped around the file. Not terribly useful.
    The following code assumes that the mime type is in $type, and that you have loaded the file's contents into $content. If the file is in MacBinary format, it delves into the resource fork header, gets the length of the data fork (bytes 83-86) and uses that to get rid of the resource fork.
    (There is probably a better way to do it, but this solved my problem):
    <?php
    if ($type == 'application/x-macbinary') {
      if (strlen($content) < 128) die('File too small');
      $length = 0;
      for ($i=83; $i<=86; $i++) {
        $length = ($length * 256) + ord(substr($content,$i,1));
         }
      $content = substr($content,128,$length);
    }
    ?>
    
    Just wanted to point out a detail that might be of interest to some:
    when using base64_encode to store binary data in a database, you are increasing the size of the data by 1.33 times. There is a nicer way of storing the data directly. Try the following: 
    <?php $data = mysql_real_escape_string($data); ?>
    This will leave the data untouched and formatted in the correct way and ready to be inserted right into a MySQL statement without wasting space.
    By the way, I'd like to thank therebechips for his excellent advice on data chunks.
    It's important to note that when using the move_uploaded_file() command, that some configurations (Especially IIS) will fail if you prefix the destination path with a leading "/". Try the following:
    <?php move_uploaded_file($tmpFileName,'uploads/'.$fileName); ?>
    Setting up permissions is also a must. Make sure all accounts have write access to your upload directory, and read access if you wish to view these files later. You might have to chmod() the directory or file afterwards as well if you're still getting access errors.
    If $_FILES is always empty, check the method of your form.
    It should be POST. Default method of a form is GET.
    <form action="myaction.php">
     <input type="file" name"userfile">
    </form>
    File will not be uploaded as default method of the form is GET.
    <form action="myaction.php" method="POST">
     <input type="file" name"userfile">
    </form>
    Files will be uploaded and $_FILES will be populated.
    If you are experiencing problems posting files from Internet Explorer to a PHP script over an SSL connection, for instance "Page can not be displayed" or empty $_FILES and $_POST arrays (described by jason 10-Jan-2006 02:08), then check out this microsoft knowledgebase article:
    http://support.microsoft.com/?kbid=889334
    This knowledgebase article explains how since service pack 2 there may be problems posting from IE over SSL. It is worth checking whether your problem is IE specific since this is definitely not a PHP problem!
    When file names do contain single quote parts of the filename are being lost.
    eg.: uploading a filename 
       startName 'middlepart' endName.txt
    will be uploaded (and hence stored in the _Files ['userfile'] variable as 
       endName.txt 
    skipping everything before the second single quote.
    When uploading large images, I got a "Document contains no data" error when using Netscape and an error page when using Explorer. My server setup is RH Linux 9, Apache 2 and PHP 4.3.
    I found out that the following entry in the httpd.conf file was missing:
    <Files *.php>
     SetOutputFilter PHP
     SetInputFilter PHP
     LimitRequestBody 524288 (max size in bytes)
    </Files>
    When this had been added, everything worked smoothly.
    - Oli Jon, Iceland
    when you upload the file, $_FILES['file']['name'] contains its original name converted into server's default charset.
    if a name contain characters that aren't present in default charset, the conversion fails and the $_FILES['file']['name'] remains in original charset.
    i've got this behavior when uploading from a windows-1251 environment into koi8-r. if a filename has the number sign "" (0xb9), it DOES NOT GET CONVERTED as soon as there is no such character in koi8-r.
    Workaround i use:
    <?php
    if (strstr ($_FILES['file']['name'], chr(0xb9)) != "")
    {
      $_FILES['file']['name'] = iconv (
        "windows-1251",
        "koi8-r",
        str_replace (chr(0xb9), "N.", $_FILES['file']['name']));
    };
    ?>
    
    If your upload script is meant only for uploading images, you can use the image function getimagesize() (does not require the GD image library) to make sure you're really getting an image and also filter image types.
    <?php getimagesize($file); ?>
    ...will return false if the file is not an image or is not accessable, otherwise it will return an array...
    <?php
    $file = 'somefile.jpg';
    # assuming you've already taken some other
    # preventive measures such as checking file
    # extensions...
    $result_array = getimagesize($file);
    if ($result_array !== false) {
      $mime_type = $result_array['mime'];
      switch($mime_type) {
        case "image/jpeg":
          echo "file is jpeg type";
          break;
        case "image/gif":
          echo "file is gif type";
          break;
        default:
          echo "file is an image, but not of gif or jpeg type";
      }
    } else {
      echo "file is not a valid image file";
    }
    ?>
    using this function along with others mentioned on this page, image ploading can be made pretty much fool-proof.
    See http://php.net/manual/en/function.getimagesize.php for supported image types and more info.
    -
    Be carefull with setting max_file_size via
    <?php ini_get('upload_max_filesize'); ?>
    ini_get might return values like "2M" which will result in non working uploads.
    This was the "no no" in my case:
    <?php
    $form = '<input type="hidden" name="MAX_FILE_SIZE" value=".ini_get('upload_max_filesize')." />';
    ?>
    Files were uploaded to the server, but than there was not any upload information, not even an error message. $_FILES was completly empty.
    djot
    -
    // code for handling simple http uploads
    <?php
    //properties of the uploaded file
    $name= $_FILES["myfile"]["name"];
    $type= $_FILES["myfile"]["type"];
    $size= $_FILES["myfile"]["size"];
    $temp= $_FILES["myfile"]["temp_name"];
    $error= $_FILES["myfile"]["error"];
    if ($error > 0)
        die("Error uploading file! code $error.");
    else
       {
        if($type=="image/png" || $size > 2000000)//condition for the file
        {
        die("Format  not allowed or file size too big!");
        }
        else
        {
         move_uploaded_file($temp, "uploaded/" .$name);
         echo "Upload complete!"; 
         }
    }
    ?>
    
    As it has been mentioned, Windows-based servers have trouble with the path to move the uploaded file to when using move_uploaded_file()... this may also be the reason copy() works and not move_uploaded_file(), but of course move_uploaded_file() is a much better method to use. The solution in the aforementioned note said you must use "\\" in the path, but I found "/" works as well. So to get a working path, I used something to the effect of:
    "g:/rootdir/default/www/".$_FILES['userfile']['name']
    ...which worked like a charm.
    I am using PHP 4.3.0 on a win2k server.
    Hope this helps!

    POST 方法上传

    本特性可以使用户上传文本和二进制文件。用 PHP 的认证和文件操作函数,可以完全控制允许哪些人上传以及文件上传后怎样处理。

    PHP 能够接受任何来自符合 RFC-1867 标准的浏览器(包括 Netscape Navigator 3 及更高版本,打了补丁的 Microsoft Internet Explorer 3 或者更高版本)上传的文件。

    Note:相关的设置

    请参阅php.ini的 file_uploads,upload_max_filesize,upload_tmp_dirpost_max_size 以及 max_input_time 设置选项。


    请注意 PHP 也支持 PUT 方法的文件上传,Netscape Composer 和 W3C 的 Amaya 客户端使用这种方法。请参阅对 PUT 方法的支持以获取更多信息。

    Example #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 文件。

    MAX_FILE_SIZE隐藏字段(单位为字节)必须放在文件输入字段之前,其值为接收文件的最大尺寸。这是对浏览器的一个建议,PHP 也会检查此项。在浏览器端可以简单绕过此设置,因此不要指望用此特性来阻挡大文件。实际上,PHP 设置中的上传文件最大值是不会失效的。但是最好还是在表单中加上此项目,因为它可以避免用户在花时间等待上传大文件之后才发现文件过大上传失败的麻烦。

    Note:

    要确保文件上传表单的属性是enctype="multipart/form-data",否则文件上传不了。


    全局变量$_FILES 自 PHP 4.1.0 起存在(在更早的版本中用$HTTP_POST_FILES替代)。此数组包含有所有上传的文件信息。

    以上范例中$_FILES 数组的内容如下所示。我们假设文件上传字段的名称如上例所示,为userfile。名称可随意命名。

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

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

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

    文件的 MIME 类型,如果浏览器提供此信息的话。一个例子是“image/gif”。不过此 MIME 类型在 PHP 端并不检查,因此不要想当然认为有这个值。

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

    已上传文件的大小,单位为字节。

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

    文件被上传后在服务端储存的临时文件名。

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

    和该文件上传相关的错误代码。此项目是在 PHP 4.2.0 版本中增加的。


    文件被上传后,默认地会被储存到服务端的默认临时目录中,除非php.ini中的 upload_tmp_dir 设置为其它的路径。服务端的默认临时目录可以通过更改 PHP 运行环境的环境变量TMPDIR来重新设置,但是在 PHP 脚本内部通过运行 putenv()函数来设置是不起作用的。该环境变量也可以用来确认其它的操作也是在上传的文件上进行的。

    Example #2 使文件上传生效

    请查阅函数 is_uploaded_file()和 move_uploaded_file()以获取进一步的信息。以下范例处理由表单提供的文件上传。

    <?php
    // In PHP versions earlier than 4.1.0, $HTTP_POST_FILES should be used instead
    // of $_FILES.
    $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 端并不检查。自 PHP 4.2.0 起,还可以通过$_FILES['userfile']['error']变量来根据不同的错误代码来计划下一步如何处理。不管怎样,要么将该文件从临时目录中删除,要么将其移动到其它的地方。

    如果表单中没有选择上传的文件,则 PHP 变量$_FILES['userfile']['size']的值将为 0,$_FILES['userfile']['tmp_name']将为空。

    如果该文件没有被移动到其它地方也没有被改名,则该文件将在表单请求结束时被删除。

    Example #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];
            $name = $_FILES["pictures"]["name"][$key];
            move_uploaded_file($tmp_name, "data/$name");
        }
    }
    ?>
    
    Do not use Coreywelch or Daevid's way, because their methods can handle only within two-dimensional structure. $_FILES can consist of any hierarchy, such as 3d or 4d structure.
    The following example form breaks their codes:
    <form action="" method="post" enctype="multipart/form-data">
      <input type="file" name="files[x][y][z]">
      <input type="submit">
    </form>
    As the solution, you should use PSR-7 based zendframework/zend-diactoros.
    GitHub:
    https://github.com/zendframework/zend-diactoros
    Example:
    <?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');
    ?>
    
    I think the way an array of attachments works is kind of cumbersome. Usually the PHP guys are right on the money, but this is just counter-intuitive. It should have been more like:
    Array
    (
      [0] => Array
        (
          [name] => facepalm.jpg
          [type] => image/jpeg
          [tmp_name] => /tmp/phpn3FmFr
          [error] => 0
          [size] => 15476
        )
      [1] => Array
        (
          [name] => 
          [type] => 
          [tmp_name] => 
          [error] => 4
          [size] => 
        )
    )
    and not this
    Array
    (
      [name] => Array
        (
          [0] => facepalm.jpg
          [1] => 
        )
      [type] => Array
        (
          [0] => image/jpeg
          [1] => 
        )
      [tmp_name] => Array
        (
          [0] => /tmp/phpn3FmFr
          [1] => 
        )
      [error] => Array
        (
          [0] => 0
          [1] => 4
        )
      [size] => Array
        (
          [0] => 15476
          [1] => 0
        )
    )
    Anyways, here is a fuller example than the sparce one in the documentation above:
    <?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";
    }
    ?>
    
    The documentation doesn't have any details about how the HTML array feature formats the $_FILES array. 
    Example $_FILES array:
    For single file -
    Array
    (
      [document] => Array
        (
          [name] => sample-file.doc
          [type] => application/msword
          [tmp_name] => /tmp/path/phpVGCDAJ
          [error] => 0
          [size] => 0
        )
    )
    Multi-files with HTML array feature -
    Array
    (
      [documents] => Array
        (
          [name] => Array
            (
              [0] => sample-file.doc
              [1] => sample-file.doc
            )
          [type] => Array
            (
              [0] => application/msword
              [1] => application/msword
            )
          [tmp_name] => Array
            (
              [0] => /tmp/path/phpVGCDAJ
              [1] => /tmp/path/phpVGCDAJ
            )
          [error] => Array
            (
              [0] => 0
              [1] => 0
            )
          [size] => Array
            (
              [0] => 0
              [1] => 0
            )
        )
    )
    The problem occurs when you have a form that uses both single file and HTML array feature. The array isn't normalized and tends to make coding for it really sloppy. I have included a nice method to normalize the $_FILES array.
    <?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;
      }
    ?>
    The following is the output from the above method.
    Array
    (
      [document] => Array
        (
          [0] => Array
            (
            [name] => sample-file.doc
              [type] => application/msword
              [tmp_name] => /tmp/path/phpVGCDAJ
              [error] => 0
              [size] => 0
            )
        )
      [documents] => Array
        (
          [0] => Array
            (
              [name] => sample-file.doc
              [type] => application/msword
              [tmp_name] => /tmp/path/phpVGCDAJ
              [error] => 0
              [size] => 0
            )
          [1] => Array
            (
              [name] => sample-file.doc
              [type] => application/msword
              [tmp_name] => /tmp/path/phpVGCDAJ
              [error] => 0
              [size] => 0
            )
        )
    )
    For clarity; the reason you would NOT want to replace the example script with
    $uploaddir = './';
    is because if you have no coded file constraints a nerd could upload a php script with the same name of one of your scripts in the scripts directory.
    Given the right settings and permissions php-cgi is capable of replacing even php files.
    Imagine if it replaced the upload post processor file itself. The next "upload" could lead to some easy exploits.
    Even when replacements are not possible; uploading an .htaccess file could cause some problems, especially if it is sent after the nerd throws in a devious script to use htaccess to redirect to his upload.
    There are probably more ways of exploiting it. Don't let the nerds get you.
    More sensible to use a fresh directory for uploads with some form of unique naming algorithm; maybe even a cron job for sanitizing the directory so older files do not linger for too long.
    Also note that since MAX_FILE_SIZE hidden field is supplied by the browser doing the submitting, it is easily overridden from the clients' side. You should always perform your own examination and error checking of the file after it reaches you, instead of relying on information submitted by the client. This includes checks for file size (always check the length of the actual data versus the reported file size) as well as file type (the MIME type submitted by the browser can be inaccurate at best, and intentionally set to an incorrect value at worst).
    I have found it useful to re-order the multidimensional $_FILES array into a more intuitive format, as proposed by many other developers already. 
    Unfortunately, most of the proposed functions are not able to re-order the $_FILES array when it has more than 1 additional dimension.
    Therefore, I would like to contribute the function below, which is capable of meeting the aforementioned requirement:
    <?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);
      }
    ?>
    Side note: the unnamed function within the function is used to avoid confusion regarding the arguments necessary for the recursion within the function, for example when viewing the function in an IDE.
    $_FILES will be empty if a user attempts to upload a file greater than post_max_size in your php.ini
    post_max_size should be >= upload_max_filesize in your php.ini.
    Note that the MAX_FILE_SIZE hidden field is only used by the PHP script which receives the request, as an instruction to reject files larger than the given bound. This field has no significance for the browser, it does not provide a client-side check of the file-size, and it has nothing to do with web standards or browser features.
    "If no file is selected for upload in your form, PHP will return $_FILES['userfile']['size'] as 0, and $_FILES['userfile']['tmp_name'] as none."
    Note that the situation above is the same when a file exceeding the MAX_FILE_SIZE hidden field is being uploaded. In this case $_FILES['userfile']['size'] is also set to 0, and $_FILES['userfile']['tmp_name'] is also empty. The difference would only be the error code.
    Simply checking for these two conditions and assuming no file upload has been attempted is incorrect.
    Instead, check if $_FILES['userfile']['name'] is set or not. If it is, a file upload has at least been attempted (a failed attempt or not). If it is not set, no attempt has been made.
    Some suggestions here:
    1. It is always better to check for your error status. If MAX_FILE_SIZE is active and the uploaded file crossed the limit, it will set the error. So, only when error is zero (0), move the file.
    2. If possible, never allow your script to upload in the path where file can be downloaded. Point your upload path to outside of public_html area or prevent direct browsing (using .htaccess restrictions). Think, if someone uploads malicious code, specially php codes, they will be executed on the server.
    3. Do not use the file name sent by the client. Regenerate a new name for newly uploaded file. This prevents overwriting your old files.
    4. Regularly track the disk space consumed, if you are running out of storage.
    Here's a complete example of the $_FILES array with nested and non-nested names. Let's say we have this html form:
    <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>
    In the test.php file:
    <?php
      print_r($_FILES);
      exit;
    ?>
    If we upload a text file with the same name as the input id for each input and click submit, test.php will output this:
    <?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 will have the same structure.
        )
    )
    ?>
    

    错误信息说明

    从 PHP 4.2.0 开始,PHP 将随文件信息数组一起返回一个对应的错误代码。该代码可以在文件上传时生成的文件数组中的error字段中被找到,也就是$_FILES['userfile']['error']

    UPLOAD_ERR_OK

    其值为 0,没有错误发生,文件上传成功。

    UPLOAD_ERR_INI_SIZE

    其值为 1,上传的文件超过了php.ini中 upload_max_filesize 选项限制的值。

    UPLOAD_ERR_FORM_SIZE

    其值为 2,上传文件的大小超过了 HTML 表单中MAX_FILE_SIZE选项指定的值。

    UPLOAD_ERR_PARTIAL

    其值为 3,文件只有部分被上传。

    UPLOAD_ERR_NO_FILE

    其值为 4,没有文件被上传。

    UPLOAD_ERR_NO_TMP_DIR

    其值为 6,找不到临时文件夹。PHP 4.3.10 和 PHP 5.0.3 引进。

    UPLOAD_ERR_CANT_WRITE

    其值为 7,文件写入失败。PHP 5.1.0 引进。

    Note:

    以上值在 PHP 4.3.0 之后变成了 PHP 常量。


    [EDIT BY danbrown AT php DOT net: This code is a fixed version of a note originally submitted by (Thalent, Michiel Thalen) on 04-Mar-2009.]
    This is a handy exception to use when handling upload errors:
    <?php
    class UploadException extends Exception
    {
      public function __construct($code) {
        $message = $this->codeToMessage($code);
        parent::__construct($message, $code);
      }
      private function codeToMessage($code)
      {
        switch ($code) {
          case UPLOAD_ERR_INI_SIZE:
            $message = "The uploaded file exceeds the upload_max_filesize directive in php.ini";
            break;
          case UPLOAD_ERR_FORM_SIZE:
            $message = "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form";
            break;
          case UPLOAD_ERR_PARTIAL:
            $message = "The uploaded file was only partially uploaded";
            break;
          case UPLOAD_ERR_NO_FILE:
            $message = "No file was uploaded";
            break;
          case UPLOAD_ERR_NO_TMP_DIR:
            $message = "Missing a temporary folder";
            break;
          case UPLOAD_ERR_CANT_WRITE:
            $message = "Failed to write file to disk";
            break;
          case UPLOAD_ERR_EXTENSION:
            $message = "File upload stopped by extension";
            break;
          default:
            $message = "Unknown upload error";
            break;
        }
        return $message;
      }
    }
    // Use
     if ($_FILES['file']['error'] === UPLOAD_ERR_OK) {
    //uploading successfully done
    } else {
    throw new UploadException($_FILES['file']['error']);
    }
    ?>
    
    Update to Adams old comment.
    This is probably useful to someone.
    <?php
    $phpFileUploadErrors = array(
      0 => 'There is no error, the file uploaded with success',
      1 => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
      2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
      3 => 'The uploaded file was only partially uploaded',
      4 => 'No file was uploaded',
      6 => 'Missing a temporary folder',
      7 => 'Failed to write file to disk.',
      8 => 'A PHP extension stopped the file upload.',
    );
    This is probably useful to someone.
    <?php
    array(
        0=>"There is no error, the file uploaded with success", 
        1=>"The uploaded file exceeds the upload_max_filesize directive in php.ini", 
        2=>"The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form"
        3=>"The uploaded file was only partially uploaded",
        4=>"No file was uploaded",
        6=>"Missing a temporary folder" 
    );
    ?>
    
    if post is greater than post_max_size set in php.ini
    $_FILES and $_POST will return empty
    One thing that is annoying is that the way these constant values are handled requires processing no error with the equality, which wastes a little bit of space. Even though "no error" is 0, which typically evaluates to "false" in an if statement, it will always evaluate to true in this context.
    So, instead of this:
    -----
    <?php
    if($_FILES['userfile']['error']) {
     // handle the error
    } else {
     // process
    }
    ?>
    -----
    You have to do this:
    -----
    <?php
    if($_FILES['userfile']['error']==0) {
     // process
    } else {
     // handle the error
    }
    ?>
    -----
    Also, ctype_digit fails, but is_int works. If you're wondering... no, it doesn't make any sense.
    To Schoschie:
    You ask the question: Why make stuff complicated when you can make it easy? I ask the same question since the version of the code you / Anonymous / Thalent (per danbrown) have posted is unnecessary overhead and would result in a function call, as well as a potentially lengthy switch statement. In a loop, that would be deadly... try this instead:
    -----
    <?php
    $error_types = array(
    1=>'The uploaded file exceeds the upload_max_filesize directive in php.ini.',
    'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.',
    'The uploaded file was only partially uploaded.',
    'No file was uploaded.',
    6=>'Missing a temporary folder.',
    'Failed to write file to disk.',
    'A PHP extension stopped the file upload.'
    );
    // Outside a loop...
    if($_FILES['userfile']['error']==0) {
     // process
    } else {
     $error_message = $error_types[$_FILES['userfile']['error']];
     // do whatever with the error message
    }
    // In a loop...
    for($x=0,$y=count($_FILES['userfile']['error']);$x<$y;++$x) {
     if($_FILES['userfile']['error'][$x]==0) {
      // process
     } else {
      $error_message = $error_types[$_FILES['userfile']['error'][$x]];
      // Do whatever with the error message
     }
    }
    // When you're done... if you aren't doing all of this in a function that's about to end / complete all the processing and want to reclaim the memory
    unset($error_types);
    ?>
    
    In regards to the dud filename being sent, a very simple way to check for this is to check the file size as well as the file name. For example, to check the file size simple use the size attribute in your file info array:
    <?php
    if($_FILES["file_id"]["size"] == 0)
    {
         // ...PROCESS ERROR
    }
    ?>
    
    We use this function to handle file uploads.
    Since $_FILES allows for more than a single file, this loops through each file and if there's an error, it is displayed in human readable language to the error log and then returned / exited. You can adjust that to echo's if preferred:
    function file_upload_test($messageBefore="CM FILE UPLOAD MESSAGE"){
      global $_FILES;
      # a single file limit
      $upload_max_size = ini_get('upload_max_filesize');
      # the combination of a batch o multiple files
      $post_max_size = ini_get('post_max_size');
      # array of possible fails which are retuned below in if $key=error section
      $phpFileUploadErrors = array(
        0 => 'There is no error, the file uploaded with success',
        1 => 'Exceeds php.ini upload_max_filesize of '.$upload_max_size.'MB',
        2 => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
        3 => 'The uploaded file was only partially uploaded',
        4 => 'No file was uploaded',
        6 => 'Missing a temporary folder',
        7 => 'Failed to write file to disk.',
        8 => 'A PHP extension stopped the file upload.',
      );
      error_log("========================================");
      error_log("$messageBefore");
      error_log("========================================");
      foreach ($_FILES['cmmediabrowsedfor'] as $key => $value) {
        ${$key}=$value;
        error_log('$_FILES ['.$key.'] = ['.$value.']');
        if(is_array($value)){
          foreach ($value as $key2 => $value2) {
            error_log('  >  $_FILES ['.$key.']['.$key2.'] = '.$value2);
            if($key=='error'){
               error_log('  ******* CM Failed Upload: '.$phpFileUploadErrors[$value2]);
               error_log('      Exit / Return in plugins/cm-alpha/php/images/img-create-tmp.php');
               error_log(' ');
               exit;
            }
          }
        }
      }
      if(!file_exists($_FILES["cmmediabrowsedfor"]["tmp_name"][0]) || !is_uploaded_file($_FILES["cmmediabrowsedfor"]["tmp_name"][0])) {
        error_log("NO FILE GOT UPLOADED ");
      } else {
        error_log("SUCCESS FILE GOT UPLOADED ");
      }
    }
    Just found out that it is very important to define the
    input type="hidden" name="MAX_FILE_SIZE" value=...
    AFTER defining the input type="FILE" name=...
    in your html/php.
    If you swap them around, you will keep getting the filesize exceeded (error 2)!
    Hope this helps.
    Roger
    Note: something that might surprise you, PHP also provides a value in the $_FILES array, if the input element has no value at all, stating an error UPLOAD_ERR_NO_FILE.
    So UPLOAD_ERR_NO_FILE is not an error, but a note that the input element just had no value. Thus you can't rely on the $_FILES array to see if a file was provided. Instead you have to walk the array and check every single damn entry - which can be quite difficult since the values may be nested if you use input elements named like "foo[bar][bla]".
    Seems like PHP just introduced you to yet another common pitfall.
    I've been playing around with the file size limits and with respect to the post_max_size setting, there appears to be a hard limit of 2047M. Any number that you specify above that results in a failed upload without any informative error describing what went wrong. This happens regardless of how small the file you're uploading may be. On error, my page attempts to output the name of the original file. But what I discovered is that this original file name, which I maintained in a local variable, actually gets corrupted. Even my attempt to output the error code in $_FILES['uploadedfiles']['error'] returns an empty string/value.
    Hopefully, this tidbit will save someone else some grief.
    UPLOAD_ERR_PARTIAL is given when the mime boundary is not found after the file data. A possibly cause for this is that the upload was cancelled by the user (pressed ESC, etc).
    This updates "adam at gotlinux dot us" above and makes it version aware, and also adds newer constants to the array.
    The reason we want to check the version is that the constants are not defined in earlier versions, and they appear later in the array. They would effectively overwrite the "0" index (no error) with an error message when the file actually uploaded fine.
    It also drops the constant's value (0,1,2, etc) for the errors, in the likely event that they are changed later (the code should still work fine).
    <?php
    $upload_errors = array(
      0            => "There is no error, the file uploaded with success"
      ,UPLOAD_ERR_INI_SIZE  => "The uploaded file exceeds the upload_max_filesize directive in php.ini"
      ,UPLOAD_ERR_FORM_SIZE  => "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form"
      ,UPLOAD_ERR_PARTIAL    => "The uploaded file was only partially uploaded"
      ,UPLOAD_ERR_NO_FILE    => "No file was uploaded"
    );
    if (version_compare(PHP_VERSION, '5.0.3') >= 0)
      $upload_errors[UPLOAD_ERR_NO_TMP_DIR] = "Missing a temporary folder";
    if (version_compare(PHP_VERSION, '5.1.0') >= 0)
      $upload_errors[UPLOAD_ERR_CANT_WRITE] = "Failed to write to disk";
    if (version_compare(PHP_VERSION, '5.2.0') >= 0)
      $upload_errors[UPLOAD_ERR_EXTENSION] = "File upload stopped by extension";
    ?>
    
    I have expanded @adam at gotlinux dot us's example a bit with proper UPLOAD_FOO constants and gettext support. Also UPLOAD_ERR_EXTENSION is added (was missing in his version). Hope this helps someone.
    <?php
    class Some {
      /**
       * Upload error codes
       * @var array
       */
      private static $upload_errors = [];
      public function __construct() {
        // Init upload errors
        self::$upload_errors = [
          UPLOAD_ERR_OK => _('There is no error, the file uploaded with success.'),
          UPLOAD_ERR_INI_SIZE => _('The uploaded file exceeds the upload_max_filesize directive in php.ini.'),
          UPLOAD_ERR_FORM_SIZE => _('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.'),
          UPLOAD_ERR_PARTIAL => _('The uploaded file was only partially uploaded.'),
          UPLOAD_ERR_NO_FILE => _('No file was uploaded.'),
          UPLOAD_ERR_NO_TMP_DIR => _('Missing a temporary folder.'),
          UPLOAD_ERR_CANT_WRITE => _('Cannot write to target directory. Please fix CHMOD.'),
          UPLOAD_ERR_EXTENSION => _('A PHP extension stopped the file upload.'),
        ];
      }
    }
    ?>
    
    When $_FILES etc is empty like Dub spencer says in the note at the top and the error is not set, that might be because the form enctype isnt sat correctly. then nothing more than maybe a http server error happens.
    enctype="multipart/form-data" works fine
    Upload doesnt work, and no error?
    actually, both $_FILES and $_REQUEST in the posted to script are empty?
    just see, if "post_max_size" is lower than the data you want to load.
    in the apache error log, there will be an entry like "Invalid method in request". and in the access log, there will be two requests: one for the POST, and another that starts with all "----" and produces a 501.
    1. And what about multiple file upload ? - If there is an UPLOAD_ERR_INI_SIZE error with multiple files - we can`t detect it normaly ? ...because that we have an array, but this error returns null and can`t use foreach. So, by having a multiple upload, we can`t normaly inform user about that.. we can just detect, that sizeof($_FILES["file"]["error"]) == 0 , but we can`t actualy return an error code. The max_file_size also is not an exit, becouse it refers on each file seperatly, but upload_max_filesize directive in php.ini refers to all files together. So, for example, if upload_max_filesize=8Mb , max_file_size = 7Mb and one of my files is 6.5Mb and other is 5Mb, it exeeds the upload_max_filesize - cant return an error, becouse we don`t know where to get that error.
    Unfortunately we cannot get the file sizes on client side, even AJAX normaly can`t do that.
    2. If in file field we paste something, like, D:\whatever , then there also isn`t an error to return in spite of that no such file at all.
    For those reading this manual in german (and/or probably some other languages) and you miss error numbers listed here, have a look to the english version of this page ;)
    As it is common to use move_uploaded_file with file uploads, how to get it's error messages should be noted here (especially since it's not noted anywhere else in the manual).
    Common code is to do something like:
     if (move_uploaded_file($_FILES["file1"]["tmp_name"], $target_file)) {
       echo "<P>FILE UPLOADED TO: $target_file</P>";
      } else {
       echo "<P>MOVE UPLOADED FILE FAILED!!</P>";
       print_r(error_get_last());
      }

    上传多个文件

    可以对input域使用不同的name来上传多个文件。

    PHP 支持同时上传多个文件并将它们的信息自动以数组的形式组织。要完成这项功能,需要在 HTML 表单中对文件上传域使用和多选框与复选框相同的数组式提交语法。

    Note:

    对多文件上传的支持是在 PHP 3.0.10 版本添加的。


    Example #1 上传多个文件

    <form action="file-upload.php" method="post" enctype="multipart/form-data">
      Send these files:<br />
      <input name="userfile[]" type="file" /><br />
      <input name="userfile[]" type="file" /><br />
      <input type="submit" value="Send files" />
    </form>

    当以上表单被提交后,数组$_FILES['userfile']$_FILES['userfile']['name']$_FILES['userfile']['size']将被初始化(在 PHP 4.1.0 以前版本是$HTTP_POST_FILES)。如果 register_globals 的设置为 on,则和文件上传相关的全局变量也将被初始化。所有这些提交的信息都将被储存到以数字为索引的数组中。

    例如,假设名为/home/test/review.html/home/test/xwp.out的文件被提交,则$_FILES['userfile']['name'][0]的值将是review.html,而$_FILES['userfile']['name'][1]的值将是xwp.out。类似的,$_FILES['userfile']['size'][0]将包含文件review.html的大小,依此类推。

    此外也同时设置了$_FILES['userfile']['name'][0]$_FILES['userfile']['tmp_name'][0]$_FILES['userfile']['size'][0]以及$_FILES['userfile']['type'][0]

    When uploading multiple files, the $_FILES variable is created in the form:
    Array
    (
      [name] => Array
        (
          [0] => foo.txt
          [1] => bar.txt
        )
      [type] => Array
        (
          [0] => text/plain
          [1] => text/plain
        )
      [tmp_name] => Array
        (
          [0] => /tmp/phpYzdqkD
          [1] => /tmp/phpeEwEWG
        )
      [error] => Array
        (
          [0] => 0
          [1] => 0
        )
      [size] => Array
        (
          [0] => 123
          [1] => 456
        )
    )
    I found it made for a little cleaner code if I had the uploaded files array in the form
    Array
    (
      [0] => Array
        (
          [name] => foo.txt
          [type] => text/plain
          [tmp_name] => /tmp/phpYzdqkD
          [error] => 0
          [size] => 123
        )
      [1] => Array
        (
          [name] => bar.txt
          [type] => text/plain
          [tmp_name] => /tmp/phpeEwEWG
          [error] => 0
          [size] => 456
        )
    )
    I wrote a quick function that would convert the $_FILES array to the cleaner (IMHO) array.
    <?php
    function reArrayFiles(&$file_post) {
      $file_ary = array();
      $file_count = count($file_post['name']);
      $file_keys = array_keys($file_post);
      for ($i=0; $i<$file_count; $i++) {
        foreach ($file_keys as $key) {
          $file_ary[$i][$key] = $file_post[$key][$i];
        }
      }
      return $file_ary;
    }
    ?>
    Now I can do the following:
    <?php
    if ($_FILES['upload']) {
      $file_ary = reArrayFiles($_FILES['ufile']);
      foreach ($file_ary as $file) {
        print 'File Name: ' . $file['name'];
        print 'File Type: ' . $file['type'];
        print 'File Size: ' . $file['size'];
      }
    }
    ?>
    
    This is also needed for <input type=file multiple> elements.
    So, if you have an input element like this:
    <input type="file" multiple="multiple" name="foobar" />
    This should be written as
    <input type="file" multiple="multiple" name="foobar[]" />
    else you'll only be able to get one of the files.
    The cleanest way to rearrange the $_FILES
    <?php
    function rearrange( $arr ){
      foreach( $arr as $key => $all ){
        foreach( $all as $i => $val ){
          $new[$i][$key] = $val;  
        }  
      }
      return $new;
    }
    ?>
    
    This is a very simple example:
    Part I : HTML
    <!doctype html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Document</title>
    </head>
    <body>
      <form action="upload.php" method="post" multipart="" enctype="multipart/form-data">
        <input type="file" name="img[]" multiple>
        <input type="submit">
      </form>
    </body>
    </html>
    Part II : PHP
    <?php
    echo '<pre>';
    $img = $_FILES['img'];
    if(!empty($img))
    {
      $img_desc = reArrayFiles($img);
      print_r($img_desc);
      
      foreach($img_desc as $val)
      {
        $newname = date('YmdHis',time()).mt_rand().'.jpg';
        move_uploaded_file($val['tmp_name'],'./uploads/'.$newname);
      }
    }
    function reArrayFiles($file)
    {
      $file_ary = array();
      $file_count = count($file['name']);
      $file_key = array_keys($file);
      
      for($i=0;$i<$file_count;$i++)
      {
        foreach($file_key as $val)
        {
          $file_ary[$i][$val] = $file[$val][$i];
        }
      }
      return $file_ary;
    }
    Here is a function to fix the indices of a multi-dimensional for easier parsing when dealing with file uploads. It takes a single $_FILES field array as a parameter and separates each individual uploaded file by numeric key. This allows for iterating like:
    <?php
    fixFilesArray($_FILES['array_of_files']);
    foreach ($_FILES['array_of_files'] as $position => $file) {
      // should output array with indices name, type, tmp_name, error, size
      var_dump($file);
    }
    ?>
    Here's the code:
    <?php
    /**
     * Fixes the odd indexing of multiple file uploads from the format:
     *
     * $_FILES['field']['key']['index']
     *
     * To the more standard and appropriate:
     *
     * $_FILES['field']['index']['key']
     *
     * @param array $files
     * @author Corey Ballou
     * @link http://www.jqueryin.com
     */
    function fixFilesArray(&$files)
    {
      $names = array( 'name' => 1, 'type' => 1, 'tmp_name' => 1, 'error' => 1, 'size' => 1);
      foreach ($files as $key => $part) {
        // only deal with valid keys and multiple files
        $key = (string) $key;
        if (isset($names[$key]) && is_array($part)) {
          foreach ($part as $position => $value) {
            $files[$position][$key] = $value;
          }
          // remove old key reference
          unset($files[$key]);
        }
      }
    }
    ?>
    
    Once I had to do a maintenance in a huge ERP that had several multiple upload inputs inside an array. Just like this:
    <form method="post" enctype="multipart/form-data">
    <input type="file" multiple name="upload[avatar]" />
    <input type="file" multiple name="upload[attachment]" />
    <input type="file" multiple name="upload2[avatar]" />
    <input type="file" multiple name="upload2[attachment]" />
    <input type="submit" />
    </form>
    The $_FILES array is created like this:
    Array
    (
      [upload] => Array
        (
          [name] => Array
            (
              [avatar] => teste.c
              [attachment] => teste
            )
          [type] => Array
            (
              [avatar] => text/x-csrc
              [attachment] => application/octet-stream
            )
          [tmp_name] => Array
            (
              [avatar] => /opt/lampp/temp/phpuf3KNj
              [attachment] => /opt/lampp/temp/php0yPZap
            )
          [error] => Array
            (
              [avatar] => 0
              [attachment] => 0
            )
          [size] => Array
            (
              [avatar] => 1960
              [attachment] => 8661
            )
        )
      [upload2] => Array
        (
          [name] => Array
            (
              [avatar] => jefrey.html
              [attachment] => notas.txt
            )
          [type] => Array
            (
              [avatar] => text/html
              [attachment] => text/plain
            )
          [tmp_name] => Array
            (
              [avatar] => /opt/lampp/temp/php87nfyu
              [attachment] => /opt/lampp/temp/phpUBlvVz
            )
          [error] => Array
            (
              [avatar] => 0
              [attachment] => 0
            )
          [size] => Array
            (
              [avatar] => 583
              [attachment] => 191
            )
        )
    )
    I've managed to re-arrange this array like this:
    Array
    (
      [upload] => Array
        (
          [avatar] => Array
            (
              [name] => teste.c
              [type] => text/x-csrc
              [tmp_name] => /opt/lampp/temp/phpuf3KNj
              [error] => 0
              [size] => 1960
            )
          [attachment] => Array
            (
              [name] => teste
              [type] => application/octet-stream
              [tmp_name] => /opt/lampp/temp/php0yPZap
              [error] => 0
              [size] => 8661
            )
        )
      [upload2] => Array
        (
          [avatar] => Array
            (
              [name] => jefrey.html
              [type] => text/html
              [tmp_name] => /opt/lampp/temp/php87nfyu
              [error] => 0
              [size] => 583
            )
          [attachment] => Array
            (
              [name] => notas.txt
              [type] => text/plain
              [tmp_name] => /opt/lampp/temp/phpUBlvVz
              [error] => 0
              [size] => 191
            )
        )
    )
    Here's my snippet:
    <?php
    function reArrayFilesMultiple(&$files) {
      $uploads = array();
      foreach($_FILES as $key0=>$FILES) {
        foreach($FILES as $key=>$value) {
          foreach($value as $key2=>$value2) {
            $uploads[$key0][$key2][$key] = $value2;
          }
        }
      }
      $files = $uploads;
      return $uploads; // prevent misuse issue
    }
    ?>
    
    This is just a modification of the code which is the top note by "phpuser" here. His/her version requires that the $file_post array passed in to the function was created by a form submitted with the multiple attribute set. With multiple set in the html input tag, $_FILES["fileInputName"]["name"] is an array no matter if only one file is sent or multiple. But when <input type="file"> is used without the multiple attribute then $_FILES["fileInputName"]["name"] is not an array, it contains the the string with the filename. To use this neat function with or without multiple set and to get back an array which you can "foreach" over in either case, use this modification:
    function reArrayFiles(&$file_post)
    {
      $file_ary = array();
      $multiple = is_array($file_post['name']);
      $file_count = $multiple ? count($file_post['name']) : 1;
      $file_keys = array_keys($file_post);
      for ($i=0; $i<$file_count; $i++)
      {
        foreach ($file_keys as $key)
        {
          $file_ary[$i][$key] = $multiple ? $file_post[$key][$i] : $file_post[$key];
        }
      }
      return $file_ary;
    }
    Recursive solution for complex situations (supports any nested arrays including indexed arrays)
    function getFixedFilesArray() {
      $walker = function ($arr, $fileInfokey, callable $walker) {
        $ret = array();
        foreach ($arr as $k => $v) {
          if (is_array($v)) {
            $ret[$k] = $walker($v, $fileInfokey, $walker);
          } else {
            $ret[$k][$fileInfokey] = $v;
          }
        }
        return $ret;
      };
      $files = array();
      foreach ($_FILES as $name => $values) {
        // init for array_merge
        if (!isset($files[$name])) {
          $files[$name] = array();
        }
        if (!is_array($values['error'])) {
          // normal syntax
          $files[$name] = $values;
        } else {
          // html array feature
          foreach ($values as $fileInfoKey => $subArray) {
            $files[$name] = array_replace_recursive($files[$name], $walker($subArray, $fileInfoKey, $walker));
          }
        }
      }
      return $files;
    }
    If you try and upload files with multi-dimensional names like this:
    <input type="file" name="submission[screenshot]" />
    <input type="file" name="other[dem][][img][]" />
    You will get an unexpected format like this:
    <?php
    array(
      'submission' => array
        (
          'name' => array( 'screenshot' => 'monster_wallpaper.jpg' ),
          'type' => array( 'screenshot' => 'image/jpeg' ),
          'tmp_name' => array( 'screenshot' => '/tmp/php48lX2Y' ),
          'error' => array( 'screenshot' => 0 ),
          'size' => array( 'screenshot' => 223262 ),
        ),
    ....
    ?>
    You can use the following function to re-format the array recursively in the usual format:
    <?php
    function format_files_array( $files, $name = null, &$new = false, $path = false ){
      $names = array( 'name' => 'name', 'type' => 'type', 'tmp_name' => 'tmp_name', 'error' => 'error', 'size' => 'size' );
      
      foreach( $files as $key => &$part )
      {
        $key = ( string ) $key;
        if( in_array( $key, $names ) )
          $name = $key;
        if( !in_array( $key, $names ) )
          $path[] = $key;
        if( is_array( $part ) )
          $part = format_files_array( $part, $name, $new, $path );
        elseif( !is_array( $part ) )
        {
          $current =& $new;
          foreach( $path as $p )
            $current =& $current[$p];
          $current[$name] = $part;
          unset( $path );
          $name = null;
        }
      }
      
      return $new;
    }
    ?>
    
    With multiple file uploads
    post_max_size: the total amount of data posted by the client (all files, and all other form field)
    upload_max_filesize: the maximum size of 1 single file. (just like <input type="hidden" name="MAX_FILE_SIZE" value="..."/>)
    so, with the directives:
     post_max_size 25M
     upload_max_filesize 2M
    you can send 12 files of up to 2 MB and use up to 1 MB for your additional form-values.
    As long as you read only a single copy of 1 file into memory, the memory_limit directive can be held reasonable small as well.
    I prefer something like this!
    <?php
    public function arrayImages ( &$file_post )
    {
      if( empty( $file_post ) ) {
        return $file_post;
      }
      if( 'array'!==gettype($file_post['name']) ) {
        return $file_post;
      }
      $keys = array_keys($file_post['name']);
      $file_array = array();
      foreach ($keys as $key) {
        foreach ($file_post as $res=>$item) {
          $file_array[$key][$res] = $item[$key];
        }
      }
      return $file_array;
    }
    ?>
    
    $countarray = count($_FILES['uploadfile']['name']);
            $newarray = array();
            for($i=0;$i<$countarray;$i++){
              $newarray[$i]['name']=$_FILES['uploadfile']['name'][$i];
              $newarray[$i]['type']=$_FILES['uploadfile']['type'][$i];
              $newarray[$i]['tmp_name']=$_FILES['uploadfile']['tmp_name'][$i];
              $newarray[$i]['error']=$_FILES['uploadfile']['error'][$i];
              $newarray[$i]['size']=$_FILES['uploadfile']['size'][$i];
            }
    by simply naming differently each file input you'll get easily accesible arrays from $_FILES, in the form $_FILES['input_name']['file_attribute']. For example:
    $_FILES['input_name1']['name']...['input_name1']['size']
    $_FILES['input_name2']['name']...['input_name2']['size']
    $_FILES['input_nameX']['name']...['input_nameX']['size']
    If you want to upload multiple file at once, remember "multiple" attribute:
    <input type="file" multiple="multiple" name="file[]" enctype="multipart/form-data"/>
    function reorganize($files) {
      foreach ($files as $var => $params) {
        foreach ($params as $name => $i) {
          foreach ($i as $num => $val) {
            $images[$var][$name] = $val;
            $arr[$num] = $images;
          }
        }
      }
      return $arr;
    }
    Array (
       [0] => Array ( 
         [image] => Array (
           [name] => white-rabbit-med-crop.jpg
           [type] => image/jpeg 
           [tmp_name] => E:\xampp\tmp\phpC008.tmp 
           [error] => 0 
           [size] => 343326 ) 
        ) 
      [1] => Array ( 
         [image] => Array ( 
           [name] => white-rabbit-med-crop.jpg 
           [type] => image/jpeg 
           [tmp_name] => E:\xampp\tmp\phpC008.tmp 
           [error] => 0 
           [size] => 1429802 )
        )
    )

    对 PUT 方法的支持

    PHP 3 和 PHP 4 对 PUT 方法的支持有所不同。在 PHP 4 中,必须使用标准的输入流来读取一个 HTTP PUT 的内容。

    Example #1 用 PHP 4 来保存 HTTP PUT 文件

    <?php
    /* PUT data comes in on the stdin stream */
    $putdata = fopen("php://stdin", "r");
    /* Open a file for writing */
    $fp = fopen("myputfile.ext", "w");
    /* Read the data 1 KB at a time
       and write to the file */
    while ($data = fread($putdata, 1024))
      fwrite($fp, $data);
    /* Close the streams */
    fclose($fp);
    fclose($putdata);
    ?>
    

    Note:

    以下文档的内容仅对 PHP 3 适用。


    PHP 提供对诸如 Netscape Composer 和 W3C Amaya 等客户端使用的 HTTP PUT 方法的支持。PUT 请求比文件上传要简单的多,它们一般的形式为:

    PUT /path/filename.html HTTP/1.1

    这通常意味着远程客户端会将其中的/path/filename.html存储到 web 目录树。让 Apache 或者 PHP 自动允许所有人覆盖 web 目录树下的任何文件显然是很不明智的。因此,要处理类似的请求,必须先告诉 web 服务器需要用特定的 PHP 脚本来处理该请求。在 Apache 下,可以用Script选项来设置。它可以被放置到 Apache 配置文件中几乎所有的位置。通常我们把它放置在<Directory>区域或者<Virtualhost>区域。可以用如下一行来完成该设置:

    Script PUT /put.php
    


    这将告诉 Apache 将所有对 URI 的 PUT 请求全部发送到 put.php 脚本,这些 URI 必须和 PUT 命令中的内容相匹配。当然,这是建立在 PHP 支持.php 扩展名,并且 PHP 已经在运行的假设之上。

    在 put.php 文件中,可以作如下操作:

    <?php copy($PHP_UPLOADED_FILE_NAME, $DOCUMENT_ROOT . $REQUEST_URI); ?>
    

    这将会把文件拷贝到远程客户端请求的位置。可能希望在文件拷贝之前进行一些检查或者对用户认证之类的操作。这里唯一的问题是,当 PHP 接受到 PUT 方法的请求时,它将会把上传的文件储存到和其它用 POST 方法处理过的文件相同的临时目录。在请求结束时,临时文件将被删除。因此,用来处理 PUT 的 PHP 脚本必须将该文件拷贝到其它的地方。该临时文件的文件名被储存在变量$PHP_PUT_FILENAME中,也可以通过$REQUEST_URI变量获得建议的目标文件名(在非 Apache web 服务器上可能会有较大的变化)。该目标文件名是由远程客户端指定的。也可以不听从改客户端的信息,而把所有上传的文件存储到一个特殊的上传目录下。

    Hello PHP World After many Hours of worryness :=)
    I have found the Solution for Resume or Pause Uploads
    In this Code Snippet it is the Server Side not Client on any Desktop Programm you must use byte ranges to calculate the uploaded bytes and missing of total bytes.
    Here the PHP Code
    <?php
    $CHUNK = 8192;
        try {
          if (!($putData = fopen("php://input", "r")))
            throw new Exception("Can't get PUT data.");
          // now the params can be used like any other variable
          // see below after input has finished
          $tot_write = 0;
          $tmpFileName = "/var/dev/tmp/PUT_FILE";
          // Create a temp file
          if (!is_file($tmpFileName)) {
            fclose(fopen($tmpFileName, "x")); //create the file and close it
            // Open the file for writing
            if (!($fp = fopen($tmpFileName, "w")))
              throw new Exception("Can't write to tmp file");
            // Read the data a chunk at a time and write to the file
            while ($data = fread($putData, $CHUNK)) {
              $chunk_read = strlen($data);
              if (($block_write = fwrite($fp, $data)) != $chunk_read)
                throw new Exception("Can't write more to tmp file");
              $tot_write += $block_write;
            }
            if (!fclose($fp))
              throw new Exception("Can't close tmp file");
            unset($putData);
          } else {
            // Open the file for writing
            if (!($fp = fopen($tmpFileName, "a")))
              throw new Exception("Can't write to tmp file");
            // Read the data a chunk at a time and write to the file
            while ($data = fread($putData, $CHUNK)) {
              $chunk_read = strlen($data);
              if (($block_write = fwrite($fp, $data)) != $chunk_read)
                throw new Exception("Can't write more to tmp file");
              $tot_write += $block_write;
            }
            if (!fclose($fp))
              throw new Exception("Can't close tmp file");
            unset($putData);
          }
          // Check file length and MD5
          if ($tot_write != $file_size)
            throw new Exception("Wrong file size");
          $md5_arr = explode(' ', exec("md5sum $tmpFileName"));
          $md5 = $md5sum_arr[0];
          if ($md5 != $md5sum)
            throw new Exception("Wrong md5");
        } catch (Exception $e) {
          echo '', $e->getMessage(), "\n";
        }
    ?>
    
    Instead of using fread fwrite to save uploaded content to a file.
    stream_copy_to_stream is much cleaner.
    A Case Study: To set up publishing with Netscape 7.2 Composer to Apache/PHP, no need to use CGI (which I tried unsuccessfully for too long) or to alter Apache's httpd.conf. I needed only to click Publish As, fill in put2disk.php as the filename (where its contents are the below), and fill in that file's dir as the "Publishing address".
    XAMPP 1.4.14: Apache/2.0.54 (Win32) mod_ssl/2.0.54 OpenSSL/0.9.7g PHP/5.0.4.
    <? // filename: put2disk.php.
    //file_put_contents ("get_def.out", print_r (get_defined_vars(), TRUE)); // debugging
    // Two slurp methods: (a) didn't work, (b) did.
    //$stdin_rsc = fopen("php://input", "r");
    //$putdata='';
    //while ($putdata .= fread($stdin_rsc, 1024)); // a. Hangs the "Publishing..." dialog.
    //while (!feof($stdin_rsc)) $putdata.=fread($stdin_rsc, 8192); // b. Worked, but file_get_contents is faster.
    //fclose($stdin_rsc);
    // All that's nec:
    $putdata=file_get_contents('php://input'); // Not php://stdin! (When the ability to see error messages isn't available, the doc (this manual page) needs to be more accurate.)
    file_put_contents("stdin.out",$putdata);
    ?>
    
    NOTE: The <Script>-Directive can not be placed in .htaccess files.
    So if you're having shared webspace and no access to the apache-configuration file you will have little chance to make something like this work.
    But you can solve the problem, using mod_rewrite (for Apache) - for further information see the documentation at http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html
    PUT raw data comes in php://input, and you have to use fopen() and fread() to get the content. file_get_contents() is useless. 
    The HTTP PUT request MUST contain a Content-Length header to specify the length (in bytes) of the body, or the server will not be able to know when the input stream is over. This is the common problem for many to find the php://input empty if no such header available. 
    This should make PUT work properly on win32 using PHP5.1.1 and apache2.
    to create variable such as $_GET, $_POST use
    that solution
    <?php
      $_SERVER['REQUEST_METHOD']==="PUT" ? parse_str(file_get_contents('php://input', false , null, -1 , $_SERVER['CONTENT_LENGTH'] ), $_PUT): $_PUT=array();
    ?>
    

    常见缺陷

    MAX_FILE_SIZE设置的值,不能大于 ini 设置中 upload_max_filesize 选项设置的值。其默认值为 2M 字节。

    如果内存限制设置被激活,可能需要将 memory_limit 设置的更大些,请确认 memory_limit 的设置足够的大。

    如果 max_execution_time 设置的值太小,脚本运行的时间可能会超过该设置。因此,也请保证max_execution_time足够的大。

    Note: max_execution_time 仅仅只影响脚本本身运行的时间。任何其它花费在脚本运行之外的时间,诸如用函数 system()对系统的调用、sleep()函数的使用、数据库查询、文件上传等,在计算脚本运行的最大时间时都不包括在内。

    Warning

    max_input_time 以秒为单位设定了脚本接收输入的最大时间,包括文件上传。对于较大或多个文件,或者用户的网速较慢时,可能会超过默认的60 秒

    如果 post_max_size 设置的值太小,则较大的文件会无法被上传。因此,请保证post_max_size的值足够的大。

    不对正在操作的文件进行验证可能意味着用户能够访问其它目录下的敏感信息。

    请注意 CERN httpd 似乎会丢弃它从客户端获得的 content-type mime 头信息中第一个空格后所有的内容,基于这一点,CERN httpd 不支持文件上传特性。

    鉴于文件路径的表示方法有很多种,我们无法确保用使用各种外语的文件名(尤其是包含空格的)能够被正确的处理。

    开发人员不应将普通的输入字段和文件上传的字段混用同一个表单变量(例如都用foo[])。

    Note that, when you want to upload VERY large files (or you want to set the limiters VERY high for test purposes), all of the upload file size limiters are stored in signed 32-bit ints. This means that setting a limit higher than about 2.1 GB will result in PHP seeing a large negative number. I have not found any way around this.
    If using IIS 7.0 or above, the Request Filtering is enabled by default and the max allowed content length is set to 30 MB.
    One must change this value if they want to allow file uploads of more than 30 MB.
    Sample web.config entry:
    <configuration>
      </system.webServer>
        <security>
          <requestFiltering>
            <requestLimits maxAllowedContentLength="314572800"/>
          </requestFiltering>
        </security>
      </system.webServer>
    </configuration>
    The above setting will allow 300 MB of data to be sent as a request. Hope this helps someone.
    [Editor's note: to be more precise, MAX_FILE_SIZE can't exceed PHP_INT_MAX before PHP 7.1.]
    Please note that the field MAX_FILE_SIZE cannot exceed 2147483647. Any greater value will lead to an upload error that will be displayed at the end of the upload
    This is explained by the related C code :
    if (!strcasecmp(param, "MAX_FILE_SIZE")) {
      max_file_size = atol(value);
    }
    The string is converted into a long int, which max value is... 2147483647
    Seems to be corrected since php-7.1.0beta3 (https://github.com/php/php-src/commit/cb4c195f0b85ca5d91fee1ebe90105b8bb68356c)
    Took me a while to figure this one out...
    I think this is actually a header problem, but it only
    happens when doing a file upload.
    If you attept a header("location:http://...) redirect after
    processing a $_POST[''] from a form doing a file upload
    (i.e. having enctype="multipart/form-data"), the redirect
    doesn't work in IE if you don't have a space between
    location: & http, i.e.
    header("location:http://...) vs
    header("location: http://...)
    ===================================
    <?php
    if ($_POST['submit']=='Upload') {
      // Process File and the redirect...
      header("location: http://"..."/somewhere.php");
      exit;
    }
    ?>
    <html><head></head><body>
    <form enctype="multipart/form-data" action="upload.php" method="POST">
      <input type="hidden" name="MAX_FILE_SIZE" value="20000">
      Your file: <input name="filename" type="file">
      <input name="submit" type="submit" value="Upload">
    </form>
    </body></html>
    ===================================
    This only happens if all of the following are true:
    header("location:http://...) with no space
    Form being processed has enctype="multipart/form-data"
    Browser=IE
    To fix the problem, simply add the space.
    Hope this helps someone else.

    上篇:处理 XForms

    下篇:使用远程文件