Le téléchargement de fichiers du client vers le serveur (upload en bon français) est une opération de plus en plus courante et extrêmement simple avec PHP.

### Le formulaire client

Pour que l'utilisateur puisse choisir le fichier à télécharger sur son ordinateur nous utilisons un formulaire et une zone de saisie <input> de type file, spécialement concue pour cet usage.

    <form method="post" action="page.php" enctype="multipart/form-data">
    Choisissez un fichier à télécharger :
    <input type="file" name="uplFichier">
    <input type="submit" name="btnUpload" value="Envoyer">
    </form>

Le tag <form> doit contenir l'attribut enctype avec la valeur multipart/form-data.
Par défaut, le type d'encodage (enctype) d'un formulaire est application/x-www-forme-urlencoded qui indique que les données saisies dans le formulaire seront transmises au serveur sous la forme de l'encodage des URLs (Zone1=Valeur1&Zone2=Valeur2&...). Ce type est inefficace pour envoyer de grandes quantités de données binaires ou des textes qui contiennent des caractères non-ASCII.
La RFC2388 (http://www.faqs.org/rfcs/rfc2388.html) définit donc la valeur multipart/form-data pour l'attribut enctype. Cette valeur prévient le serveur que le formulaire va envoyer éventuellement autre chose que des données contenues dans des commandes HTTP, et qu'il peut s'attendre à recevoir des données non-ASCII et des données binaires (des fichiers par exemple).

Ensuite, il faut prendre garde à la taille des fichiers téléchargés. L'option de configuration upload_max_filesize dans le fichier php.ini détermine la taille maximum d'un fichier uploadé (par défaut : 2 MB). Cette taille peut être réduite ponctuellement si nous ajoutons une zone cachée dans le formulaire :
<input type="hidden" name="MAX_FILE_SIZE" value="10240">

Si vous modifiez l'option de configuration upload_max_filesize il faut aussi modifier post_max_size en conséquence.

Cette zone cachée doit se trouver directement aprés le tag <form>, et doit s'appeler MAX_FILE_SIZE. Sa valeur détermine la taille maximum en KB du fichier qui pourra être téléchargé (dans la ligne d'exemple 10 KB). Si cette valeur est supérieure à la valeur de l'option de configuration upload_max_filesize, elle sera ignorée.

Le code de notre formulaire devient :

    <form enctype="multipart/form-data" method="post" action="page.php">
    <input type="hidden" name="MAX_FILE_SIZE" value="102400">
    Sélectionnez le	fichier à télécharger :
    <input type="file" name="uplFichier">
    <input type="submit" name="btnUpload" value="Envoyer">
    </form>

Le code de traitement pour le côté client est terminé. Nous pouvons maintenant examiner ce qu'il y a à faire du côté serveur pour correctement récupérer et traiter les informations passées par le formulaire.

### Le traitement serveur

Sur le serveur, les informations sur le fichier transmises par le formulaire seront accessible dans le tableau superglobal $_FILES. Chaque élément du tableau représentant un fichier télécharge est lui même un tableau associatif de 4 éléments :

name | le nom du fichier envoyé par le navigateur.
type | le type MIME du fichier uploadé, tel que déterminé par le navigateur (exemples : application/zip, image/png, video/mp4, application/pdf).
size | la taille en bytes du fichier uploadé. Si l'utilisateur essaye de télécharger un fichier dont la taille est supérieure à la limite autorisée, size sera égal à 0.
tmp_name | le nom du fichier temporaire créé sur le serveur. Si l'utilisateur essaye de télécharger un fichier dont la taille est supérieure à la limite autorisée, tmp_name est egal à "none".
error | code d'erreur 0 = pas d'erreur / 1 = la taille du fichier dépasse la taille de upload_max_filesize dans php.ini / 2 = la taille du fichier dépasse la taille spécifiée dans le formulaire HTML / 3 = fichier partiellement uploadé / 4 = fichier introuvable.

La récupération du fichier sur le serveur doit se faire avec 2 fonctions dédiées :

- is_uploaded_file()
- move_uploaded_file()

La fonction is_uploaded_file() prévient des erreurs de transfert et garantit que l'on travaille bien avec un fichier téléchargé, et non un fichier "natif" du serveur (prévention contre les risques d'attaque pirates).

La fonction move_uploaded_file() nous permet de déplacer le fichier téléchargé du répertoire d'upload vers son répertoire final sur le serveur. En effet, un fichier téléchargé sur le serveur est toujours stocké dans un répertoire temporaire, déterminé par l'option de configuration upload_tmp_dir dans le fichier php.ini. Je vous recommande pour des raisons de sécurité de ne jamais utiliser une fonction de copie de fichier comme copy(), mais de toujours utiliser move_uploaded_file() qui vérifie que le fichier copié est bien un fichier téléchargé.

Il faut bien entendu que le répertoire de fichiers temporaire et le répertoire dans lequel vous voulez déposer le fichier téléchargé permettent l'accès en lecture / écriture à l'utilisateur système associé au serveur Web (par exemple IUSR_NomMachine sous NT, l'utilisateur Apache ou nobody sous Linux).

Exemple : upload de fichier
<?php require('bib_fonctions.php'); function afficherForm($err) { if ($err != '') { echo '<p>', $err, '</p>'; } echo '<form method="post" ', ' enctype="multipart/form-data" ', 'action="', $_SERVER['PHP_SELF'], '">', '<input type="hidden" name="MAX_FILE_SIZE" ', 'value="102400">', 'Sélectionnez le fichier à télécharger : ', '<input type="file" name="uplFichier">', '<input type="submit" name="btnUpl" value="Go">', '</form>'; htmlFin(); } htmlDebut('Upload de fichier'); $err = ''; // Premier affichage if (!isset($_POST['btnUpl'])) { afficherForm($err); exit(); } // Phase traitement de la soumission du formulaire // Vérification si erreurs $f = $_FILES['uplFichier']; switch ($f['error']) { case 1: case 2: $err = $f['name'].' est trop gros.'; break; case 3: $err = 'Erreur de transfert de '.$f['name']; break; case 4: $err = $f['name'].' introuvable.'; } if ($err != '') { afficherForm($err); exit(); } // Pas d'erreur => placement du fichier if (! @is_uploaded_file($f['tmp_name'])) { afficherForm('Erreur interne de transfert'); exit(); } $place = realpath('.').'/'.$f['name']; if (@move_uploaded_file($f['tmp_name'], $place)) { $err = $f['name'].' OK'; } else { $err = 'Erreur interne de transfert'; } afficherForm($err); ?>

Vous pouvez remarquer l'utilisation du caractère @ devant certaines fonctions :
if (! @is_uploaded_file($f['tmp_name'])) {
et
if (@move_uploaded_file($f['tmp_name'], $place)) {
Le caractère @ placé devant une fonction empêche tout message d'erreur éventuel d'être affiché par le navigateur. Dans notre cas, si une erreur se produit, aucune information sur des répertoires ne sera affichée, rendant la page plus sécurisée.

### Problèmes typiques

Voici quelques problèmes typiques que l'on peut rencontrer lors de l'upload de fichier.

Pas de fichier uploadé, pas de message d'erreur.
Le plus souvent c'est dû à la taille du fichier qui dépasse le maximum autorisé par PHP. Testez si un fichier de petite taille (quelque ko) est correctement uploadé.

Pas de fichier copié, erreur de permission.
Le script doit avoir les droits d'écriture et de lecture sur le répertoire final ou le fichier est uploadé.

Pas de fichier uploadé ou copié, erreurs telles que "open_basedir restriction", "Safe Mode Restriction", "function has been disabled"
PHP fonctionne en mode sécurisé (Safe Mode), ce qui permet à l'administrateur de contrôler les fonctions qu'un utilisateur a le droit d'exécuter, et d'interdire l'exécution de certaines fonctions (comme la copie ou le transfert de fichiers)