2.4 Transfert et gestion de fichiers 93 <head> <title >Transfert du fichier </title > < link rel=’stylesheet ’ href="films . css" type="text/css" /> </head> <body> <h1>Réception du fichier </h1> <?php require_once ("Connect.php"); require_once ("Connexion.php"); require_once ("ExecRequete.php") ; require_once ("Normalisation .php"); // Normalisation des données HTTP Normalisation() ; // Récupération du code indicateur du transfert $codeErreur = $_FILES [ ’maPhoto ’ ][ ’ error ’ ]; if ( $ c o d e E r r e u r == UPLOAD_ERR_OK) { // Le fichier a bien été transmis $fichier = $_FILES [ ’maPhoto ’ ] ; echo "<b>Nom du fichier client :</b> " . $fichier [ ’name’] . "<br/>"; echo "<b>Nom du fichier serveur :</b> " . $fichier [ ’tmp_name’] . "<br/>"; echo "<b>Taille du fichier :</b> " . $fichier [ ’size ’] . "<br/>"; echo "<b>Type du f i chier : </b>" . $fichier [ ’ type ’ ] . "<br/>" ; // On insère la description dans la table Album $connexion = Connexion (NOM, PASSE, BASE, SERVEUR) ; // Protection des données à insérer $description = htmlSpecialChars(mysql_real_escape_string($_POST [’description’])); $requete = "INSERT INTO Album (description ) VALUES (’$description ’)"; $resultat = ExecRequete ($requete , $connexion); // On récupère l ’ identifiant attribué par MySQL $id = mysql_insert_id ($connexion); / / C o p i e d u f i c h i e r d a n s l e r é p e r t o i r e PHOTOS copy( $ f i c h i e r [ ’ tmp_name ’ ] , " . / PHOTOS/ $ i d . j p g " ) ; } else { // Une erreur quelque part switch ($codeErreur) { 94 Chapitre 2. Techniques de base case UPLOAD_ERR_NO_FILE : echo "Vous avez oublié de transmettre le fichier !?\n"; break ; case UPLOAD_ERR_INI_SIZE : echo "Le fichier dépasse la taille max. autorisée par PHP"; break ; case UPLOAD_ERR_FORM_SIZE : echo "Le fichier dépasse la taille max. autorisée par le formulaire"; break ; case UPLOAD_ERR_PARTIAL: echo "Le fichier a été transféré partiellement"; break ; default : echo "Ne doit pas arriver !!!"; } } ?> </body> </html> Le script teste soigneusement ces erreurs et affiche un message approprié au cas de figure. Si un fichier est transféré correctement sur le serveur, ce dernier le copie dans un répertoire temporaire (sous Unix, /tmp, paramétrable dans le fichier de configuration php.ini). Le nom de ce fichier temporaire est donné (dans notre cas) par $_FILES[’maPhoto’][’tmp_name’]. Notre script affiche alors les quatre informations connues sur ce fichier. On insère ensuite la description du fichier dans la table Album. Remarquez que l’attribut id n’est pas donné dans la commande INSERT : MySQL se charge d’at- tribuer automatiquement un identifiant aux champs AUTO_INCREMENT.Lafonction mysql_insert_id() permet de récupérer l’identifiant attribué par le dernier ordre INSERT effectué. Finalement, on copie (fonction copy()) le fichier temporaire dans le sous- répertoire PHOTOS, en lui donnant comme nom l’identifiant de la description dans MySQL, et comme extension « .jpg ». On a supposé ici pour simplifier que tous les fichiers transmis sont au format JPEG, mais il serait bon bien sûr de choisir l’extension en fonction du type MIME du fichier transmis. Attention : le processus qui effectue cette copie est le programme serveur. Ce programme doit impérativement avoir les droits d’accès et d’écriture sur les réper- toires dans lesquels on copie les fichiers (ici c’est le répertoire PHOTOS situé sous le répértoire contenant le script TransfertFichier.php). 2.4 Transfert et gestion de fichiers 95 Quelques précautions Le transfert de fichiers extérieurs sur une machine serveur nécessite quelques précau- tions. Il est évidemment très dangereux d’exécuter un script PHP ou un programme reçu via le Web. Faites attention également à ne pas permettre à celui qui transfère le fichier d’indiquer un chemin d’accès absolu sur la machine (risque d’accès à des ressources sensibles). Enfin il est recommandé de contrôler la taille du fichier transmis, soit au niveau du navigateur, soit au niveau du serveur. Du côté navigateur, un champ caché de nom max_file_size peut précéder le champ de type file (voir l’exemple de FormTransfert.html ci-dessus). Le navigateur doit alors en principe interdire le transfert de fichier plus gros que la taille maximale indiquée. Comme tous les contrôles côté client, il ne s’agit pas d’une garantie absolue, et il est préférable de la doubler côté serveur. Le paramètre upload_max_filesize dans le fichier php.ini indique à PHP la taille maximale des fichiers recevables. Le transfert de fichiers sur le serveur peut être dangereux, et mérite que vous consacriez du temps et de la réflexion à vérifier que la sécurité n’est pas compromise. Tenez-vous également au courant des faiblesses détectées dans les différentes versions de PHP en lisant régulièrement les informations sur le sujet publiées dans les forums spécialisés ou sur http://www.php.net. 2.4.2 Transfert du serveur au client Voyons maintenant comment transférer des fichiers du serveur au client. À titre d’illustration, nous allons afficher la liste des photos disponibles et proposer leur téléchargement. En principe il existe autant de lignes dans la table Album que de fichiers dans le répertoire PHOTOS. On peut donc, au choix, parcourir la table Album et accé- der, pour chaque ligne, au fichier correspondant, ou l’inverse. Pour les besoins de l’exemple, nous allons adopter ici la seconde solution. PHP propose de nombreuses fonctions pour lire, créer, supprimer ou modifier des fichiers et des répertoires. Nous utilisons ici les fonctions opendir() , qui renvoie un identifiant permettant d’accéder à un répertoire, readir() qui permet de parcourir les fichiers d’un répertoire, et closedir() qui ferme l’accès à un répertoire. Voici ces fonctions à l’œuvre dans le script ListePhotos.php. Exemple 2.23 exemples/ListePhotos.php : Affichage de la liste des photos <?xml version=" 1.0" encoding="iso−8959−1"?> <!DOCTYPE html PUBLIC " −/ /W3C / / DTD XHTML 1 . 0 S t r i c t / / EN " "http ://www.w3.org/TR/xhtml1/DTD/xhtml1− strict .dtd"> <html xmlns="http ://www.w3. org/1999/xhtml" xml:lang=" fr "> <head> <title >Liste et téléchargement des photos </ title > < link rel=’stylesheet ’ href="films . css" type="text/css" /> </head> 96 Chapitre 2. Techniques de base <body> <h1>Liste et téléchargement des photos </h1> <?php require_once ("UtilBD.php") ; $connexion = Connexion (NOM, PASSE, BASE, SERVEUR) ; // On affiche la liste des photos echo "<table border=’4’ cellspacing =’2’ cellpadding=’2’>" . "<caption ALIGN=’bottom’>Les photos disponibles </caption> " . "<tr><th>Vignette </th><th>Description </th><th>Taille </th>" . "<th>Agrandir </th><th>Compteur </th><th>Action </th></tr >\n" ; $dir = opendir ( "PHOTOS" ) ; while ($fichier = readdir ($dir)) { if ( ereg ("\.jpg\$" , $fichier)) { $id = substr ($fichier , 0, strpos ($fichier , ".")); $ r e q u e t e = "SELECT ∗ FROM Album WHERE id =’ $id ’ " ; $resultat = ExecRequete ($requete , $connexion); $photo = ObjetSuivant ( $resultat ) ; echo " < t r >< t d ><img s r c = ’PHOTOS / $ f i c h i e r ’ h e i g h t = ’ 7 0 ’ w i d t h =’70’/></td>" . "<td>$photo−>description </td>" ."<td>".filesize( "PHOTOS/ $ f i c h i e r " ) . " </ t d > " . " < t d >< a h r e f = ’PHOTOS/ $ f i c h i e r ’ > $ f i c h i e r < / a > </ t d > " . "<td>$photo−>compteur </td>" . "<td><a href=’ChargerPhoto .php?id=$id ’> " . "Télécharger cette photo</a></td>\n" ; } } echo "</table>\n"; closedir ($dir); ?> <a href=’FormTransfert .html ’>Ajouter une photo</a> </body> </html> L’accès aux fichiers du répertoire se fait avec la boucle suivante : $dir = opendir ( "PHOTOS" ) ; while ($fichier = readdir ($dir)) { // On ne prend que les fichiers JPEG if ( ereg ("\.jpg\$" , $fichier)) { } } closedir ($dir); 2.4 Transfert et gestion de fichiers 97 À l’intérieur de la boucle on veille à ne prendre que les fichiers JPEG en testant avec une expression régulière (voir section précédente) que l’extension est bien « jpg ». Notez que le caractère réservé PHP « $ » est précédé de \ pour s’assurer qu’il est bien passé littéralement à la fonction ereg(), où il indique que la chaîne doit se terminer par « jpeg ». Le point, « . » est lui un caractère réservé dans les expressions régulières (il représente n’importe quel caractère), et on « l’échappe » donc également pour qu’il soit interprété littéralement. On récupère ensuite l’identifiant de la photographie en prenant, dans le nom du fichier, la sous-chaîne précédant le « . », dont on se sert pour chercher la description et le compteur de la photographie dans la base. Il ne reste plus qu’à afficher une ligne du tableau HTML. • Pour afficher une vignette (format réduit) de la photo, on utilise la balise <img> en indiquant une hauteur et une largeur limitée à 70 pixels. • On peut accéder à l’image complète avec l’ancre qui fait référence au fichier JPEG. Si l’utilisateur choisit cette ancre, le serveur envoie automatiquement un fichier avec un en-tête image/jpeg qui indique au navigateur qu’il s’agit d’une image et pas d’un fichier HTML. • Enfin, la fonction filesize() renvoie la taille du fichier passée en argument. Le téléchargement du fichier image nous montre pour conclure comment compter le nombre d’accès à un fichier. Il y a deux problèmes à résoudre. Le premier est « d’in- tercepter » la demande de téléchargement pour pouvoir exécuter l’ordre SQL qui va incrémenter le compteur. Le second est d’éviter que le fichier s’affiche purement et simplement dans la fenêtre du navigateur, ce qui n’est pas le but recherché. Il faut au contraire que, quel que soit le type du fichier transmis, le navigateur, au lieu de l’afficher, propose une petite fenêtre demandant dans quel répertoire de la machine client on doit le stocker, et sous quel nom. La solution consiste à utiliser un script intermédiaire, ChargerPhoto.php qui, contrai- rement à tout ceux que nous avons vus jusqu’à présent, ne produit aucune ligne HTML. Ce script nous permet d’intercaler l’exécution d’instructions PHP entre la demande de l’utilisateur et la transmission du fichier. On peut donc résoudre facilement les deux problèmes précédents : 1. l’identifiant du fichier à récupérer est passé au script en mode get (voir le script ListePhotos.php ci-dessus) : on peut donc incrémenter le compteur dans la table Album ; 2. on donne explicitement l’en-tête du fichier transmis grâce à la fonction Header(). Exemple 2.24 exemples/ChargerPhoto.php : Script de téléchargement d’une photo <?php require_once ("Connect.php"); require_once ("Connexion.php"); require_once ("ExecRequete .php"); . = htmlSpecialChars (mysql_ real_escape_string($_POST [’description’])); $requete = "INSERT INTO Album (description ) VALUES (’$description ’)"; $resultat = ExecRequete ($requete , $connexion); // On récupère l ’ identifiant attribué par MySQL $id = mysql_ insert_id. permet d’intercaler l’exécution d’instructions PHP entre la demande de l’utilisateur et la transmission du fichier. On peut donc résoudre facilement les deux problèmes précédents : 1. l’identifiant. champ caché de nom max_file_size peut précéder le champ de type file (voir l’exemple de FormTransfert.html ci-dessus). Le navigateur doit alors en principe interdire le transfert de fichier plus