Upload de fichiers (par HTTP)

10 messages   •   Page 1 sur 1
ViPHP
ViPHP | 60 Messages

27 févr. 2005, 17:59

Objectifs du tutorial:
- comprendre le fonctionnement de l'upload par HTTP,
- mettre en oeuvre une méthode de traitement du fichier uploadé.

Références:
Avant toute chose, ce tutorial n'est peut-être pas exhaustif, et vous devez IMPERATIVEMENT vous réferer à la documentation officielle de PHP sur l'upload de fichiers, disponible à http://www.php.net/manual/fr/features.file-upload.php

Environnement minimal:
PHP 4.1.0 (pour pouvoir utiliser la variable superglobale $_FILES)



Bases de l'upload par HTTP:
Avant toute chose, il faut un formulaire html classique doté d'au moins une balise <input type="file">.
A la soumission de ce formulaire (le mieux par la méthode POST), les différents champs sont envoyés par HTTP au script récepteur et les fichiers sélectionnés à partir de balises <input type="file"> sont uploadés dans un répertoire temporaire du serveur web (voir plus bas).
Il faut alors faire quelques manipulations pour tester que le fichier uploadé répond à certaines conditions ET pour le déplacer du répertoire temporaire vers un répertoire plus adéquat.



Notes préliminaires:
  • - Le fichier qui a été uploadé dans le répertoire temporaire sera AUTOMATIQUEMENT détruit à la fin du script de réception du formulaire, s'il n'a pas été renommé ou déplacé!

    - Pour effectuer le déplacement du fichier uploadé du répertoire temporaire vers le vrai répertoire destination, il faut s'assurer que le répertoire destination en question EXISTE et EST ACCESSIBLE EN ECRITURE!
Configuration du php.ini :
Certaines variables du fichier de configuration de PHP (php.ini) permettent une gestion avancée des fichiers uploadés:
  • - file_uploads. Autorise (On) ou non (Off) l'upload de fichiers.
    - upload_tmp_dir Définit un répertoire temporaire par défaut spécifique à PHP pour l'upload de fichiers. Si cette variable n'est pas définie, le répertoire par défaut du serveur web est utilisé.
    - upload_max_filesize Définit la taille maximale autorisée pour fichier à uploader. Au delà de cette limite, il y a génération d'une erreur (voir plus bas). La valeur par défaut est souvent 2M (2 Méga-octets)
    - post_max_size Définit la taille maximale autorisée des données envoyées par un formulaire. Influe indirectement sur upload_max_filesize; donc on a toujours post_max_size > upload_max_filesize
Cependant, à moins d'être votre propre hébergeur, vous ne pouvez *a priori* pas modifier le php.ini.

Pour vérifier la configuration proposée par votre hébergeur, écrivez un script contenant uniquement:
<?php
  phpinfo();
?>
exécutez-le, et recherchez les valeurs des paramètres indiqués plus haut.
Note de pjl: A noter qu'il semble y avoir des problèmes avec l'upload chez Free... Sans doute car file_uploads est à Off... Le vérifier par vous-même...

Si vous n'avez pas accès au php.ini, il faut trouver d'autres solutions de supervision de l'upload. C'est ce que nous allons voir dans les prochains sections.



Ecriture des scripts

Définition d'un formulaire destiné à l'upload d'un fichier :

Code : Tout sélectionner

<form name="form" method="post" action="page_receptrice.php" enctype="multipart/form-data"> <!-- Taille maximale en octets. Non sécurisé car facilement contournable !! --> <input type="hidden" name="MAX_FILE_SIZE" value="100000" /> Veuillez s&eacute;lectionner un fichier &agrave; uploader: <input type="file" name="aFile" /> <input type="submit" name="submitFile" value="Envoyer le fichier" /> </form>
  • - L'attribut enctype définit simplement le type MIME du formulaire; en gros le type de données envoyées par le formulaire. Il est indispensable dans le cas de l'upload de fichiers.
    - La balise <input type="hidden" name="MAX_FILE_SIZE"> est facultative; elle définit la taille maximum acceptée d'un fichier à uploader. Mais elle n'assure de rien; elle est facilement countournable et ne constitue donc qu'une maigre mesure de sécurité en soi.
    - La balise <input type="file" name="aFile" /> correspond à un champ "Sélectionner le fichier à uploader"


Définition du script en réception (page_receptrice.php) :

Erreurs possibles
Plusieurs types d'erreurs peuvent arriver à la réception ou au traitement du fichier uploadé.
- Première chose, il peut y avoir eu une erreur lors de l'upload.
- Ensuite, le fichier uploadé n'a peut-être pas la bonne taille, ni le bon type, ni la bonne extension.
- Le déplacement du fichier du répertoire temporaire vers le répertoire réel peut engendrer une erreur (répertoire destination non existant ou non accessible en écriture par exemple).
- Le nom réel du fichier original peut contenir des caractères que le système de fichiers du serveur web ne reconnait pas (par exemple, espaces, caractères accentués, etc.)
- Un fichier portant le même nom que le fichier qui va être déplacé vers le bon répertoire existe déjà dans ce même répertoire. Si c'est le cas, la fonction de déplacement utilisée (move_uploaded_files) va écrire par dessus le fichier existant. Afin de conserver des fichiers existants, il vaut donc mieux renommer de manière unique le fichier lors du déplacement.

Il va nous falloir traiter tous ces cas d'erreurs possibles.


La variable superglobale $_FILES
Les champs classiques d'un formulaire sont accessibles à la réception du formulaire en manipulant la variable superglobale $_POST. Les champs de type file sont quand à eux accessibles à partir de la variable superglobale $_FILES.
En d'autres termes, si dans un formulaire, il y a <input type="text" name="unChamp" />, alors $_POST['unChamp'] contiendra la valeur qui aura été saisie.
Si dans ce formulaire, il y a aussi une balise <input type="file" name="unFichier" />, alors c'est $_FILES['unFichier'] qui contiendra des informations sur le fichier uploadé.

Un petit script tout simple comme
<?php
  print '<pre>';
  print_r($_FILES);
  print '</pre>';
?>
vous donnera le contenu d'une telle variable.
A l'exécution, vous obtiendrez quelque chose ressemblant à:

Code : Tout sélectionner

Array ( [aFile] => Array ( [name] => chiffrir.exe [type] => application/octet-stream [tmp_name] => C:\WINDOWS\TEMP\php362.tmp [error] => 0 [size] => 6304 ) )
Pour chaque champ "file" reçu (ci-dessus, un seul: aFile), vous avez donc 5 propriétés accessibles par $_FILES['aFile']['nom_de_la_propriété']
  • - name - Nom du fichier source
    - type - Type MIME du fichier source (par exemple: image/png). N'est pas toujours disponible (selon le navigateur)
    - tmp_name - Chemin et nom du fichier qui a été uploadé dans un répertoire temporaire du serveur web
    - error - Code d'erreur possible lors de l'upload du fichier (voir plus bas)
    - size - Taille en octets du fichier uploadé


Code
Une fois que toutes ces informations ont été comprises, il suffit maintenant d'écrire un petit script de gestion d'upload de fichiers par HTTP.

La fonction principale permettant de déplacer le fichier uploadé du répertoire temporaire vers le répertoire final s'appelle move_uploaded_file.
Elle recoit le chemin et le nom du fichier temporaire comme 1er argument, et le répertoire et nom du fichier final après déplacement.

Le transfert s'effectue donc en appelant simplement:
$nouveau_chemin = '/var/www/html/upload/';
move_uploaded_file($_FILES['aFile']['tmp_name'], $nouveau_chemin.$_FILES['aFile']['name']);
A noter que si vous souhaitez renommer le fichier uploadé en "monFichier.png", le code correspondant est
$nouveau_chemin = '/var/www/html/upload/';
move_uploaded_file($_FILES['aFile']['tmp_name'], $nouveau_chemin.'monFichier.png');

Ce script suffit en soi...

Mais comme précisé plus haut, move_uploaded_file peut retourner un code d'erreur (false) et il vaut donc mieux la traiter (ne serait-ce que pour que l'utilisateur ait un message lui disant que l'opération s'est bien passée ou non).

Par ailleurs, si un fichier portant le nom spécifié comme 2ème paramètre existe déjà, php va écrire par dessus... Donc à éviter à tout prix !



Maintenant, voilà un exemple plus complexe mais à peu près complet et sécurisé, prenant en compte toutes les erreurs possibles (listées plus haut) :
<?php
  /**
   * function formatFileName
   * @access public
   * @param string - nom de fichier à formater
   * @param int - longueur maximale autorisée pour le nom de fichier
   * @return string - nom de fichier formaté
   * @desc Tronque éventuellement le nom de fichier, le convertit en minuscules et
   *           y élimine les caractères potentiellement dangereux.
   */         
   function formatFileName($aFileName, $aMaxLength = 50) {
     $aFileName = strToLower(subStr($aFileName, 0, $aMaxLength));
     $aFileName = ereg_replace('[^a-zA-Z0-9,._\+\()\-]', '_', $aFileName);
      
     return $aFileName;
   } // end of function formatFileName() /2






   /* PARAMETRES DE CONFIGURATION DU SCRIPT
    */
   
   // chemin d'accès au répertoire d'upload (vers où le fichier uploadé temporaire sera transféré)
   // ce répertoire doit EXISTER et être ACCESSIBLE EN ECRITURE !!
   $destination_dir = '/var/www/html/upload';
   
   // taille maximale en octets du fichier à uploader
   $file_max_size = 100;
   
   // extensions de fichiers autorisées
   $authorized_extensions = array('jpg', 'gif');






   
   /* TRAITEMENT PRINCIPAL
    */   
   
   // vérifie l'existence du répertoire de destination
   if (!is_dir($destination_dir)) {
     echo 'Veuillez indiquer un r&eacute;pertoire destination correct !';
     die(); 
   }

   // vérifie que répertoire de destination a des droits en écriture
   if (!is_writeable($destination_dir)) {
     echo 'Veuillez spécifier des droits en écriture pour le r&eacute;pertoire destination !';
     die();      
   }   
   
   // réception du formulaire
   if (isSet($_POST['submitFile'])) {

     // vérifie qu'un fichier a bien été soumis
     if (isSet($_FILES) && is_array($_FILES)) {
   
       // pas d'erreur lors de l'upload
       if ($_FILES['aFile']['error'] == UPLOAD_ERR_OK) {
         
         // vérifie la taille en octets
         if ($_FILES['aFile']['size'] <= $file_max_size) {

           // vérifie l'extension du fichier recu
           // il est aussi possible (et sans doute mieux) de se baser sur $_FILES['aFile']['type']
           // qui retourne le type MIME correspondant (par exemple: image/pjpeg)         
           $lastPos = strRChr($_FILES['aFile']['name'], ".");
           if ($lastPos !== false && in_array(strToLower(subStr($lastPos, 1)), $authorized_extensions)) {
   
             // définit un nom de fichier destination unique à partir du nom du fichier original formaté
             $destination_file = time().formatFileName($_FILES['aFile']['name']);            

             // déplace le fichier uploadé du répertoire temporaire
             // vers les répertoire/fichier destination spécifiés
             if (move_uploaded_file($_FILES['aFile']['tmp_name'],
                                         $destination_dir.DIRECTORY_SEPARATOR.$destination_file)) {
               echo 'Fichier valide et upload&eacute; correctement.';   
             } else { // error sur move_uploaded_file
               echo 'Le fichier n\'a pas &eacute;t&eacute; upload&eacute; correctement !';
             }
           } else { // pas d'extension ou mauvaise extension
             echo 'Mauvaise extension !';
           }      
         } else { // Taille maximale dépassée
           echo 'Fichier trop volumineux !';
         }
       } else { // Erreur lors de l'upload
         switch ($_FILES['aFile']['error']){
           case UPLOAD_ERR_INI_SIZE:
              echo 'Le fichier upload&eacute; d&eacute;passe la valeur sp&eacute;cifi&eacute;e 
                       pour upload_max_filesize dans php.ini.';
              break;
           case UPLOAD_ERR_FORM_SIZE:
              echo 'Le fichier upload&eacute; d&eacute;passe la valeur sp&eacute;cifi&eacute;e
                       pour MAX_FILE_SIZE dans le formulaire d\'upload.';
              break;
           case UPLOAD_ERR_PARTIAL:
              echo 'Le fichier n\'a &eacute;t&eacute que partiellement upload&eacute;.';
              break;                            
           default:
              echo 'Aucun fichier n\'a &eacute;t&eacute upload&eacute;.';
         } // switch
       }   
     } else { // aucun fichier reçu
       echo 'Pas de fichier recu';
     }
   } // fin de réception de formulaire
?>

Hope this helps.

/Flood

Eléphant du PHP | 67 Messages

25 mars 2005, 15:01

MAX_FILE_SIZE est facultative; elle définit la taille maximum acceptée d'un fichier à uploader. Mais elle n'assure de rien; elle est facilement countournable et ne constitue donc qu'une maigre mesure de sécurité
C'est exacte mais précisez la toujours tout de meme car si un internaute met un fichier de 20Mo alors que vous etes limité par votre serveur a 2Mo par exemple ca evitera de faire "tomber" votre visiteur sur une page 500.

Eléphant du PHP | 161 Messages

11 avr. 2005, 15:37

salut,
bien sympa ton tuto merci :)

par contre je suis en train de me pencher sur la vérification des types de fichiers et je pense que faut changer ton systeme. Par exemple si je renomme mon fichier avec une autre extension ou quoi que ce soit ta vérif passe a coté.

La fonction getimagesize() est interessante (si qq1 a d'autres idées je suis preneur a fond !)
Par exemple si on veut limiter au jpeg et au gif :
$infoImg = getimagesize($tempName);
if($infoImg[2] == '1' || $infoImg[2] == '2') {
    //...upload
}
else {
echo 'erreur mauvais fichier uploadé!!'
}
des suggestions ?

Administrateur PHPfrance
Administrateur PHPfrance | 250 Messages

12 avr. 2005, 03:27

salut

la fonction mime_content_typerepondra a tes besoins

Eléphant du PHP | 161 Messages

14 avr. 2005, 13:26

oki jvais essayer merci :)

Mammouth du PHP | 859 Messages

29 avr. 2005, 10:36

Je suis complètement perdu au niveau de cette ligne là :
$destination_dir = '/var/www/html/upload';
J'ai Wamp5, le dossier de stockage de tous les sites est www, le répertoire d'upload est upload et je n'arrive pas à trouver le bon chemin pour ajouter mes fichiers :shock:

I need some help guys ;)

Administrateur PHPfrance
Administrateur PHPfrance | 430 Messages

29 avr. 2005, 11:01

où est le probleme : le $destination_dir c'est toi qui le parametre !
tu peux le faire pointer n'importe où sur l'ordi à condition que apache ait les droits d'écriture.
tu peux mettre $destination_dir = 'C:\\Program Files\\wamp\\upload';
ca marchera nikel

Mammouth du PHP | 859 Messages

29 avr. 2005, 11:14

J'ai bêtement appliqué le tutorial du manuel PHP mais rien n'y fait :
<!-- Le type d'encodage des données, enctype, DOIT être spécifié comme ce qui suit -->
<form enctype="multipart/form-data" action="<?php echo $PHP_SELF; ?>" method="post">
  <!-- MAX_FILE_SIZE doit précéder le champs 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>

<?php
// En PHP < 4.1.0, $HTTP_POST_FILES doit être utilisé
//    à la place de $_FILES.

$uploaddir = "D:\\Logiciels\\Wamp\\www\\upload";
$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éboguage :';
print_r($_FILES);

echo '</pre>';

?>
J'obtiens cette réponse :
Le fichier est valide, et a été téléchargé
avec succès. Voici plus d'informations :
Voici quelques informations de déboguage :Array
(
[userfile] => Array
(
[name] => README.txt
[type] => text/plain
[tmp_name] => D:/Logiciels/Wamp/tmp\php2AF.tmp
[error] => 0
[size] => 239
)

)

Administrateur PHPfrance
Administrateur PHPfrance | 1275 Messages

29 avr. 2005, 11:20

Utilises plutôt :
$uploaddir = "D:/Logiciels/Wamp/www/upload";

Mammouth du PHP | 859 Messages

29 avr. 2005, 11:25

Bon j'ai trouvé la solution ;)

Il manquait un slash à la fin de D:/Logiciels/Wamp/www/upload

Merci pour l'aide ;)

10 messages   •   Page 1 sur 1