ScotlandPHP 2016

Chargements de fichiers par méthode POST

Cette fonctionnalité permet aux personnes de télécharger à la fois du texte et des fichiers binaires. Avec les fonctions d'identification et de manipulation de fichiers de PHP, vous avez le contrôle total pour définir qui a le droit de télécharger mais aussi ce qui sera fait du fichier une fois qu'il sera téléchargé.

PHP est capable de recevoir des fichiers émis par un navigateur conforme à la norme RFC-1867.

Note: Notes de configuration

Voir aussi les directives file_uploads, upload_max_filesize, upload_tmp_dir, post_max_size et max_input_time dans php.ini

PHP supporte aussi le chargement par la méthode PUT comme dans le navigateur Netscape Composer et Amaya du W3C. Reportez-vous au chapitre sur le support de la méthode PUT.

Exemple #1 Formulaire de chargement de fichier

Un formulaire de téléchargement de fichiers peut être construit en créant un formulaire spécifique comme ceci :

<!-- Le type d'encodage des données, enctype, DOIT être spécifié comme ce qui suit -->
<form enctype="multipart/form-data" action="_URL_" method="post">
  <!-- MAX_FILE_SIZE doit précéder le champ input de type file -->
  <input type="hidden" name="MAX_FILE_SIZE" value="30000" />
  <!-- Le nom de l'élément input détermine le nom dans le tableau $_FILES -->
  Envoyez ce fichier : <input name="userfile" type="file" />
  <input type="submit" value="Envoyer le fichier" />
</form>

_URL_ dans l'exemple précédent doit être remplacé et pointé vers un fichier PHP.

Le champ caché MAX_FILE_SIZE (mesuré en octets) doit précéder le champ input de type file et sa valeur représente la taille maximale acceptée du fichier par PHP. Cet élément de formulaire doit toujours être utilisé car il permet d'informer l'utilisateur que le transfert désiré est trop lourd avant d'atteindre la fin du téléchargement. Gardez à l'esprit que ce paramètre peut être "trompé" du côté du navigateur facilement, aussi ne faîtes pas confiance à ce dernier, ne s'agissant finalement que d'une fonctionnalité de convenance côté client. Le paramètre PHP (côté serveur) à propos de la taille maximale d'un fichier téléchargé, ne peut, lui, être trompé.

Note:

Assurez-vous que votre formulaire de téléchargement de fichier contienne enctype="multipart/form-data", sinon, le fichier se sera pas téléchargé.

La variable globale $_FILES va contenir toutes les informations sur le fichier téléchargé. Son contenu est détaillé dans notre exemple ci-dessous. Notez que l'on suppose que le nom de la variable du fichier téléchargé est userfile, tel que défini dans le formulaire ci-dessus, mais peut être n'importe quel nom.

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

Le nom original du fichier, tel que sur la machine du client web.

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

Le type MIME du fichier, si le navigateur a fourni cette information. Par exemple, cela pourra être "image/gif". Ce type mime n'est cependant pas vérifié du côté de PHP et, donc, ne prend pas sa valeur pour se synchroniser.

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

La taille, en octets, du fichier téléchargé.

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

Le nom temporaire du fichier qui sera chargé sur la machine serveur.

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

Le code d'erreur associé au téléchargement de fichier.

Le fichier téléchargé sera stocké temporairement dans le dossier temporaire du système, à moins qu'un autre dossier soit fourni avec la directive upload_tmp_dir du php.ini. Le dossier par défaut du serveur peut être changé dans l'environnement via la variable TMPDIR. Modifier la valeur de cette variable avec la fonction putenv() dans un script PHP sera sans effet. Cette variable d'environnement peut aussi être utilisée pour s'assurer que d'autres opérations fonctionnent aussi sur les fichiers téléchargés.

Exemple #2 Validation de téléchargement de fichiers

Voyez aussi les fonctions is_uploaded_file() et move_uploaded_file() pour plus d'informations. L'exemple suivant va télécharger un fichier venant d'un formulaire.

<?php
// Dans les versions de PHP antiéreures à 4.1.0, la variable $HTTP_POST_FILES
// doit être utilisée à la place de la variable $_FILES.

$uploaddir '/var/www/uploads/';
$uploadfile $uploaddir basename($_FILES['userfile']['name']);

echo 
'<pre>';
if (
move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile)) {
    echo 
"Le fichier est valide, et a été téléchargé
           avec succès. Voici plus d'informations :\n"
;
} else {
    echo 
"Attaque potentielle par téléchargement de fichiers.
          Voici plus d'informations :\n"
;
}

echo 
'Voici quelques informations de débogage :';
print_r($_FILES);

echo 
'</pre>';

?>

Le script PHP qui reçoit le fichier chargé doit pouvoir gérer le fichier de manière appropriée. Vous pouvez utiliser la variable $_FILES['userfile']['size'] pour recaler tous les fichiers qui sont trop gros ou trop petits. Vous pouvez utiliser la variable $_FILES['userfile']['type'] pour écarter les fichiers qui n'ont pas le bon type, mais l'utiliser uniquement pour une série de vérifications, car cette valeur est complètement sous le contrôle du client et n'est pas vérifiée du côté de PHP. Vous pouvez utiliser l'information dans $_FILES['userfile']['error'] et adapter votre politique en fonction des codes d'erreur. Quelle que soit votre politique, vous devriez soit effacer le fichier du dossier temporaire, soit le déplacer.

Si aucun fichier n'est sélectionné dans le formulaire, PHP retournera 0 dans $_FILES['userfile']['size'] et rien du tout dans $_FILES['userfile']['tmp_name'].

Le fichier sera automatiquement effacé du dossier temporaire à la fin du script, s'il n'a pas été déplacé ou renommé.

Exemple #3 Envoi d'un tableau de fichiers

PHP supporte les tableaux en HTML ainsi qu'avec les fichiers.

<form action="" method="post" enctype="multipart/form-data">
<p>Images:
<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");
    }
}
?>

La barre de progression de téléchargement peut être implémentée en utilisant la progression du chargement via les sessions.

add a note add a note

User Contributed Notes 14 notes

up
239
Anonymous
1 year ago
For the love of god, don't do what michael suggests in http://php.net/manual/en/features.file-upload.post-method.php#94973 or you will be instantly pwned by someone uploading a php-shell to your script dir.

When the mods come to delete this note for violating the don't-refer-to-another-note rule, please please /please/ delete michael's note too.
up
8
mpyw
1 month ago
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');

?>
up
44
daevid at daevid dot com
7 years ago
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";
}
?>
up
9
coreywelch+phpnet at gmail dot com
6 months ago
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
                )

        )

)
up
15
eslindsey at gmail dot com
7 years ago
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).
up
4
anon
1 year ago
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.
up
1
russianhacker
6 days ago
function normalize_files_array($files = []) {

    $arr = [];

    foreach ($files['name'] as $index => $filename) {
        $arr[] = array(
            'name' => $filename,
            'tmp_name' => $files['tmp_name'][$index],
            'error' => $files['error'][$index],
            'size' => $files['size'][$index],
            'type' => $files['type'][$index]
        );
    }

    return $arr;
}
up
3
Mark
5 years ago
$_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.
up
3
claude dot pache at gmail dot com
7 years ago
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.
up
1
Age Bosma
5 years ago
"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.
up
-6
milosdj at gmail dot com
1 year ago
MAX_FILE_SIZE is completely useless from browser perspective. Up to this day there is no browser that supports it.

From PHP's point there is one mention to max_file_size in source code.
http://stackoverflow.com/a/6273418/352260

In essence, no need to use MAX_FILE_SIZE until browsers start to support it. Which will probably never happen.
But if html5 kicks in, input maxlength attribute might become applicable to file input type.
up
-18
Anonymous
3 years ago
Normalizing $_FILES structure:

<?php
    $files
= [];
   
$fix = function (&$files, $values, $prop) use (&$fix) {
        foreach (
$values as $key => $value) {
            if (
is_array($value)) {
               
$fix($files[$key], $value, $prop);
            } else {
               
$files[$key][$prop] = $value;
            }
        }
    };
    foreach (
$_FILES as $name => $props) {
        foreach (
$props as $prop => $value) {
            if (
is_array($value)) {
               
$fix($files[$name], $value, $prop);
            } else {
               
$files[$name][$prop] = $value;
            }
        }
    }
?>
up
-11
bimal at sanjaal dot com
1 year ago
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.
up
-19
mail at markuszeller dot com
5 years ago
If you want to increase the upload size, it could make sense to allow it only to a specified directory and not in the php.ini for the whole domain or server. In my case it worked very well placing that into the .htaccess file like this:

php_value    upload_max_filesize    100M
php_value    post_max_size    101M

Remember, post_max_size must be bigger than the upload_max_filesize.
To Top