HomeDevUpload de fichiers en PHP

Upload de fichiers en PHP

L’upload de fichier via le formulaire adapté se fait non pas par le protocole FTP, mais HTTP.

Pour simplifier, les données dont le fichier uploadé est constitué sont envoyées de la même manière que les données classiques d’un formulaire, mais en utilisant une « frontière » (boundary en anglais) pour séparer les données du fichier des données classiques du formulaire. On parle alors d’un flot de données multiple [1].

Tout comme pour les emails donc, chaque partie du flot de données est accompagnée d’entêtes de description (type MIME, taille des données, etc…).

Pour en savoir plus, reportez vous à la RFC 1867 (cf lien en bas de page).

Formulaire d’upload PHP

Passons maintenant à la pratique 🙂

Pour uploader un fichier, il nous faut d’abord constituer le formulaire adéquat.

En dehors, d’éventuels champs texte, password, checkbox ou autre, il nous faut placer un champ de type file. Il nous faut également spécifier que le formulaire est constitué de plusieurs parties (les données classiques, et les données du fichier). Pour cela, nous ajouterons l’attribut enctype avec la valeur multipart/form-data à la balise form du code HTML.

Voici un formulaire de base, avec uniquement le champ de type file et le bouton de soumission :

<form method="post" enctype="multipart/form-data" action="upload.php">
<p>
<input type="file" name="fichier" size="30">
<input type="submit" name="upload" value="Uploader">
</p>
</form>

Nous avons donné au champ de type file le nom « fichier ». Cela nous servira pour traiter le fichier uploadé.

Vous pouvez également ajouter un champ de type hidden avec pour nom MAX_FILE_SIZE et valeur la taille maximale en octet, que le fichier peut faire.

Note : Prenez soin de ne pas oublier l’attribut enctype de la balise form, c’est un erreur fréquente !

Réception et traitement

Une fois le formulaire soumis, et si tout se passe correctement, le fichier est alors copié dans le dossier temporaire des fichiers uploadés [2] mais n’est pas encore présent sur l’espace du site, il nous faudra le copier sur notre espace web.

Quoi qu’il arrive, le fichier est supprimé du dossier temporaire à la fin de l’éxécution du script.

Le fichier uploadé est disponible via le tableau global $_FILES ($HTTP_POST_FILES si vous êtes avec PHP < 4.1.0) ou directement avec le nom que l’on a donné au formulaire si registar_globals est fixé à on dans la configuration de PHP.

Nous avons alors les variables suivantes (avec ici « fichier » pour le nom du champ de type file) :

$_FILES['fichier']['name']
Contient le nom d’origine du fichier
$_FILES['fichier']['tmp_name']
Nom temporaire du fichier dans le dossier temporaire du système
$_FILES['fichier']['type']
Contient le type MIME du fichier
$_FILES['fichier']['size']
Contient la taille du fichier en octets
$_FILES['fichier']['error']
Code de l’erreur (le cas échéant) (disponible à partir de php 4.2.0)

Première chose à faire, vérifier que l’opération s’est bien passée en vérifiant la présence du fichier dans le dossier temporaire; pour cela, nous avons à notre disposition la fonction is_uploaded_file().

Ensuite, vérifier ce qui nous a été envoyé; Si on s’attend à une image, on vérifie si l’extension est correcte par exemple.

Une fois que l’on est sùr que tout est ok, on copie le fichier sur notre espace web à l’aide de la fonction move_uploaded_file() qui est plus sùre que la fonction copy(), car elle vérifie que le fichier à copier vient bien du dossier temporaire (et donc, provient d’un formulaire d’upload).

A partir de PHP 4.2.0, vous avez également un index error dans le tableau $_FILES['fichier'], vous indiquant plus précisément les raisons d’un éventuel échec.

Voici les différents codes et l’erreur leur correspondant :

UPLOAD_ERR_OK
Valeur : 0; Aucune erreur, le fichier a bien été uploadé
UPLOAD_ERR_INI_SIZE
Valeur : 1; Le fichier excède le poids autorisé par la directive upload_max_filesize de php.ini
UPLOAD_ERR_FORM_SIZE
Valeur : 2; Le fichier excède le poids autorisé par le champ MAX_FILE_SIZE s’il a été donné
UPLOAD_ERR_PARTIAL
Valeur : 3; Le fichier n’a été uploadé que partiellement
UPLOAD_ERR_NO_FILE
Valeur : 4; Aucun fichier n’a été uploadé

Note : Attention, les constantes UPLOAD_ERR_* ne sont disponibles qu’à partir de PHP 4.3.0. Sur les versions précédentes, il vous faudra utiliser directement les numéros des codes d’erreur.

Voici notre fichier PHP qui traitera les données du formulaire.

Nous partons ici du principe que nous voulons un fichier image en réception.

<?php

if( isset($_POST['upload']) ) // si formulaire soumis
{
    $content_dir = 'upload/'; // dossier où sera déplacé le fichier

    $tmp_file = $_FILES['fichier']['tmp_name'];

    if( !is_uploaded_file($tmp_file) )
    {
        exit("Le fichier est introuvable");
    }

    // on vérifie maintenant l'extension
    $type_file = $_FILES['fichier']['type'];

    if( !strstr($type_file, 'jpg') && !strstr($type_file, 'jpeg') && !strstr($type_file, 'bmp') && !strstr($type_file, 'gif') )
    {
        exit("Le fichier n'est pas une image");
    }

    // on copie le fichier dans le dossier de destination
    $name_file = $_FILES['fichier']['name'];

    if( !move_uploaded_file($tmp_file, $content_dir . $name_file) )
    {
        exit("Impossible de copier le fichier dans $content_dir");
    }

    echo "Le fichier a bien été uploadé";
}

?>

Bien entendu, rien ne vous oblige à garder le nom d’origine (disponible dans $_FILES['fichier']['name']) du fichier lorsque vous le copiez sur votre espace web. Vous pouvez parfaitement lui attribuer un autre nom. [3]

Note : J’attire votre attention sur le fait que le code ci-dessus comporte une faille de sécurité. En effet, je vous invite à tester le code suivant :

<?php
$fw = fopen("hack.php\0.png", "w");
fwrite($fw, "<?php echo 'Je vous ai hacké'; ");
fclose($fw);

Et le fichier créé portera bien le nom « hack.php ». Cela vient de ce qu’en C, le caractère null (placé ici dans le nom de fichier avec \0) marque la fin de la chaîne. PHP ne filtre manifestement pas le nom de fichier passé en argument de fopen() (et ça vaut certainement pour les autres fonctions de création de fichier).

Maintenant, imaginez ce que peut faire un attaquant avec votre formulaire d’upload… Tout simplement placer du code PHP à exécuter sur votre serveur.

Il est donc indispensable de vérifier que le nom du fichier uploadé ne comporte pas le caractère null ni, tant qu’à faire, aucun autre caractère de contrôle ou slashe et backslashe. Voici un morceau de code basé sur celui donné plus haut pour réaliser cette opération :

if( preg_match('#[\x00-\x1F\x7F-\x9F/\\\\]#', $name_file) )
{
    exit("Nom de fichier non valide");
}
else if( !move_uploaded_file($tmp_file, $content_dir . $name_file) )
{
    exit("Impossible de copier le fichier dans $content_dir");
}

Informations complémentaires

Le protocole HTTP n’est pas, à l’origine, prévu pour l’upload de fichiers. Cette utilisation doit donc rester marginale, d’autant plus que la taille des fichiers uploadés est limitée par la directive upload_max_filesize dans le fichier de configuration de php. Par défaut, elle est fixée à 2 Mo, mais une limite peut également être fixée dans la configuration du serveur.

Enfin, la plupart des hébergeurs gratuit désactivent la possibilité d’uploader des fichiers par cette méthode [4], donc vérifiez d’abord si l’upload est autorisé sur votre serveur 😉

Liens connexes

  • [1] : En fait, on a ici le même principe que pour les emails avec pièces jointes, voir l’article sur l’envoi d’emails
  • [2] : Celui ci est défini dans le fichier php.ini par la directive upload_tmp_dir
  • [3] : Par exemple en générant un nom aléatoire pour éviter d’écraser un autre fichier portant le même nom dans le répertoire de stockage
  • [4] : Directive file_uploads à on/off dans le fichier php.ini

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.