Uploader des images sur un serveur web

Rechercher

Uploader des images sur un serveur web

  • Par Emacs
  • 31 commentaires
  • 44002 lectures
  • RSS -  Atom

Beaucoup d'applications web ont recours à des modules d'upload d'images. Parmi elles nous pouvons citer les galleries d'images ou bien encore les systèmes d'avatars de forums type PHPBB, VBulletin ou IPB (pour ne citer que les plus connus). Les programmes permettant ce genre de fonctionnalités peuvent-être parfois très évolués car ils se chargent de contrôler l'intégrité du fichier ou bien même de le redimensionner à la volée.

Le script qui suit montre le fonctionnement d'un système d'upload d'images sur le serveur Web. Ses principales caractéristiques sont les suivantes :

  • Il est entièrement configurable grâce aux constantes situées en tête du script.
  • Il dispose d'un tableau d'extensions d'image autorisées. Les extensions possibles sont celles acceptées par la fonction getimagesize() de PHP.
  • Il crée le répertoire cible s'il n'existe pas.
  • Il contrôle l'extension du fichier envoyé.
  • Il contrôle le type de l'image.
  • Il contrôle les dimensions de l'image (largeur et hauteur).
  • Il contrôle le poids de l'image.
  • Il upload l'image sur le serveur avec un nouveau nom.
  • Il retourne un message de réussite ou d'erreur à l'utilisateur.
  • Il est sécurisé.

Le script complet

Script PHP de l'upload d'image
<?php
/************************************************************
* Script realise par Emacs
* Crée le 19/12/2004
* Maj : 23/06/2008
* Licence GNU / GPL
* webmaster@apprendre-php.com
* http://www.apprendre-php.com
* http://www.hugohamon.com
*
* Changelog:
*
* 2008-06-24 : suppression d'une boucle foreach() inutile
* qui posait problème. Merci à Clément Robert pour ce bug.
*
*************************************************************/
/************************************************************
* Definition des constantes / tableaux et variables
*************************************************************/
// Constantes
define('TARGET', '/files/'); // Repertoire cible
define('MAX_SIZE', 100000); // Taille max en octets du fichier
define('WIDTH_MAX', 800); // Largeur max de l'image en pixels
define('HEIGHT_MAX', 800); // Hauteur max de l'image en pixels
// Tableaux de donnees
$tabExt = array('jpg','gif','png','jpeg'); // Extensions autorisees
$infosImg = array();
// Variables
$extension = '';
$message = '';
$nomImage = '';
/************************************************************
* Creation du repertoire cible si inexistant
*************************************************************/
if( !is_dir(TARGET) ) {
if( !mkdir(TARGET, 0755) ) {
exit('Erreur : le répertoire cible ne peut-être créé ! Vérifiez que vous diposiez des droits suffisants pour le faire ou créez le manuellement !');
}
}
/************************************************************
* Script d'upload
*************************************************************/
if(!empty($_POST))
{
// On verifie si le champ est rempli
if( !empty($_FILES['fichier']['name']) )
{
// Recuperation de l'extension du fichier
$extension = pathinfo($_FILES['fichier']['name'], PATHINFO_EXTENSION);
// On verifie l'extension du fichier
if(in_array(strtolower($extension),$tabExt))
{
// On recupere les dimensions du fichier
$infosImg = getimagesize($_FILES['fichier']['tmp_name']);
// On verifie le type de l'image
if($infosImg[2] >= 1 && $infosImg[2] <= 14)
{
// On verifie les dimensions et taille de l'image
if(($infosImg[0] <= WIDTH_MAX) && ($infosImg[1] <= HEIGHT_MAX) && (filesize($_FILES['fichier']['tmp_name']) <= MAX_SIZE))
{
// Parcours du tableau d'erreurs
if(isset($_FILES['fichier']['error'])
&& UPLOAD_ERR_OK === $_FILES['fichier']['error'])
{
// On renomme le fichier
$nomImage = md5(uniqid()) .'.'. $extension;
// Si c'est OK, on teste l'upload
if(move_uploaded_file($_FILES['fichier']['tmp_name'], TARGET.$nomImage))
{
$message = 'Upload réussi !';
}
else
{
// Sinon on affiche une erreur systeme
$message = 'Problème lors de l\'upload !';
}
}
else
{
$message = 'Une erreur interne a empêché l\'uplaod de l\'image';
}
}
else
{
// Sinon erreur sur les dimensions et taille de l'image
$message = 'Erreur dans les dimensions de l\'image !';
}
}
else
{
// Sinon erreur sur le type de l'image
$message = 'Le fichier à uploader n\'est pas une image !';
}
}
else
{
// Sinon on affiche une erreur pour l'extension
$message = 'L\'extension du fichier est incorrecte !';
}
}
else
{
// Sinon on affiche une erreur pour le champ vide
$message = 'Veuillez remplir le formulaire svp !';
}
}
?>
Le formulaire HTML
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr">
<head>
<title>Upload d'une image sur le serveur !</title>
</head>
<body>
<?php
if( !empty($message) )
{
echo '<p>',"\n";
echo "\t\t<strong>", htmlspecialchars($message) ,"</strong>\n";
echo "\t</p>\n\n";
}
?>
<!-- Debut du formulaire -->
<form enctype="multipart/form-data" action="<?php echo htmlspecialchars($_SERVER['PHP_SELF']); ?>" method="post">
<fieldset>
<legend>Formulaire</legend>
<p>
<label for="fichier_a_uploader" title="Recherchez le fichier à uploader !">Envoyer le fichier :</label>
<input type="hidden" name="MAX_FILE_SIZE" value="<?php echo MAX_SIZE; ?>" />
<input name="fichier" type="file" id="fichier_a_uploader" />
<input type="submit" name="submit" value="Uploader" />
</p>
</fieldset>
</form>
<!-- Fin du formulaire -->
</body>
</html>


Les commentaires

1. Par prout le 04/12/2007 22:55

Bonjour,

Votre script est il protégé contre les extensions fausses?

Merci pour votre site.

2. Par Emacs le 05/12/2007 13:42

Bonjour,

Qu'appellez-vous "extensions fausses" ? La fonction pathinfo() renvoie la véritable extension du fichier envoyé.

Cordialement.

3. Par Bruno le 16/01/2008 00:57

Bonjour .

Bravo pour votre site très soigné .

Bon tuto , mais il manque la protection contre les insertions de code dans la source mème de l'image , seul readfile peut contrer cette ultime attaque .

Bruno

4. Par Emacs le 16/01/2008 09:09

Effectivement Bruno ! Je corrige ça dans l'après midi

5. Par Emacs le 16/01/2008 20:23

Correction apportée. L'image est renommée à la volée avec une chaine MD5 aléatoire générée par un uniqid().

6. Par Bruno le 17/01/2008 08:42

Oui , en fait , mon post n'a pas un rapport direct avec ton tutoriel .

Il intervient seulement si l'on doit afficher des images du client et dont on ignore l'origine .

http://www.nexen.net/actualites/securite/17226-attaque_par_image_gif.php


Bruno

7. Par Emacs le 17/01/2008 09:07

Oui je suis d'accord avec toi. Sachant qu'en plus j'ai lu récemment le livre de Damien Séguy. Il parle de la sécurité des noms de fichiers. On peut effectivement avoir des attaques XSS avec un nom de fichier malsaint.

8. Par Emacs le 18/01/2008 00:03

Oui c'est exact avec IE 6. Il faut ouvrir le fichier gif et contrôler qu'il n'y ait pas de code PHP à l'intérieur.

9. Par Mazzu le 20/01/2008 02:37

J'ai fait très récemment un script d'upload qui ressemble beaucoup à ça. C'est marrant

Je vais me permettre quelques petites remarques.
- En rapport avec la remarque de Bruno, il faut ou bien faire comme phpclasses.org l'explique ou bien s'assurer que le fichier n'a pas une extension de fichier qui soit interprété par PHP (comme c'est fait ici) ou bien placer les images dans un répertoire à part qui contient un .htaccess avec la ligne : "php_flag engine Off" et s'assurer de ne pas écraser le .htaccess
- Ensuite, juste pour avoir quelque chose de plus propre, j'aurais changé les lignes suivantes,
ligne 49 : if(!empty($_FILES['fichier']['name']) && $_FILES['fichier']['error'] === UPLOAD_ERR_OK)
ligne 58 : $infosImg = @getimagesize($_FILES['fichier']['tmp_name']);
ligne 61 : if($infosImg !== FALSE && $infosImg[2] >= 1 && $infosImg[2] <= 14)

10. Par Emacs le 20/01/2008 10:31

Bonjour Mazzu,

Ta solution du .htaccess est pas mal mais ce n'est pas la meilleure. Pour sécuriser encore un peu mieux, il faudrait placer les fichiers uploadés dans un répertoire "hors web". Néanmoins la création de répertoires au dessus de la racine web est très rarement autorisée sur des serveurs mutualisés.

Quant à la constante UPLOAD_ERR_OK, tu viens de me l'apprendre Je vais l'intégrer au script je pense.

Pour la ligne 58, le @ ne sert strictement rien si le serveur est bien configuré pour masquer les erreurs en production.

A la ligne 61, effectivement je n'ai pas vérifié la valeur de retour de la variable $infosImg.

Je te remercie pour ton commentaire constructif Mazzu

Hugo.

11. Par Mazzu le 20/01/2008 18:27

You're Welcome !

Pour la constante UPLOAD_ERR_OK, je n'ai rien inventé, tout est là
http://fr3.php.net/manual/fr/features.file-upload.php

12. Par bruno le 22/01/2008 15:50

Bonjour a vous .
Finalement , la meilleure solution serait d'utiliser le format png a l'instar du gif . Avec le patch png pour IE .
Ce n'est qu'une idée .

Bruno

13. Par rocawear le 29/01/2008 03:13

Bonjour a vous tous,
j'ai deja subit une attaque dans le passé car les fichier de type .php.ext fonctionais et php les executais.

pour resoudre ce probleme j'ai du faire une cimple condition toute bête !

Voila:

if(preg_match('#php#isU',$fichier)){ // Si le fichier comporte .php.jext, ont renome le php en 484 ( a vous de choisir )
$fichier = str_replace('php','484',$fichier);
}

14. Par Raherivo le 14/03/2008 13:43

Bonjour à tous,
Je demanderai à vous, donnez moi le code du modification d'upload image s'il vous plaît! je n'en trouvais plus.
Merci beaucoup à bientôt!

15. Par Jeux le 02/07/2008 09:29

Merci ce script me sera bien utile, surtout avec toutes les modifications apportées petit à petit

16. Par pasilage le 20/10/2008 14:02

bonjour excuser moi je decouvre le php, doit on mettre le formulaire et php dans le même fichier ou deux fichier distin? Si il sont distin comment les lier le action du formulaire ou habituellement on mais le nom du fichier et bizare je ne comprend pas

17. Par Emacs le 20/10/2008 22:38

Dans le même c'est mieux pour pouvoir gérer les erreurs.

18. Par pasilage le 21/10/2008 01:34

ou doije mettre le nom du fichier de destination je l'ai mis a la place de files mais ca ne marche pas il charge une page quand j'execute le script mais la page reste blanche et pas d'image dans mon dossier

19. Par pasilage le 21/10/2008 01:37

Constantesdefine
('TARGET', 'C:/Program Files/EasyPHP 2.0b1/www/'; DOIT JE PARAMETRER AUTRE CHOSE MERCI DE M AIDER

20. Par pasilage le 21/10/2008 10:31

j'ai enfin reussit a faire fonctionner le script une erreur de frappe de ma part il marche bien c'est pile ce que je voulais merci

21. Par pasilage le 22/10/2008 11:40

bonjour j'ai ajoutés un script de redimentionnement bon la forme n'est peut être pas top car je débute mais ça marche dit moi ce que tu en pense

<?php
define('TARGET', 'C:\Program Files\EasyPHP 2.0b1\www\aaaa\image';
define('MAX_SIZE', 200000);
define('WIDTH_MAX', 800);
define('HEIGHT_MAX', 800);

$tabExt = array('jpg','gif','png','jpeg';
$infosImg = array();

$extension = '';
$message = '';
$nomImage = '';

if( !is_dir(TARGET) ) {
if( !mkdir(TARGET, 0755) ) {
exit('Erreur : le répertoire cible ne peut-être créé ! Vérifiez que vous diposiez des droits suffisants pour le faire ou créez le manuellement !';
}
}
if(!empty($_POST))
{
// On verifie si le champ est rempli
if( !empty($_FILES['fichier']['name']) )
{
// Recuperation de l'extension du fichier
$extension = pathinfo($_FILES['fichier']['name'], PATHINFO_EXTENSION);
// On verifie l'extension du fichier
if(in_array(strtolower($extension),$tabExt))
{
// On recupere les dimensions du fichier
$infosImg = getimagesize($_FILES['fichier']['tmp_name']);
// On verifie le type de l'image
if($infosImg[2] >= 1 && $infosImg[2] <= 14)
{


// On verifie les dimensions et taille de l'image
if(($infosImg[0] <= WIDTH_MAX) && ($infosImg[1] <= HEIGHT_MAX) && (filesize($_FILES['fichier']['tmp_name']) <= MAX_SIZE))

//on redimentionne limage
$x = 75;

$y = 75; # Taille en pixel de l'image redimensionnée

if ($infosImg)

echo 'Image redimensionnée ...
';

if ($infosImg['mime']=='image/jpeg' ) {
$img_big = imagecreatefromjpeg($_FILES['fichier']['tmp_name']); # On ouvre l'image d'origine
$img_new = imagecreate($x, $y);
# création de la miniature
$img_mini = imagecreatetruecolor($x, $y)
or $img_mini = imagecreate($x, $y);

// copie de l'image, avec le redimensionnement.
imagecopyresized($img_mini,$img_big,0,0,0,0,$x,$y,$infosImg[0],$infosImg[1]);

imagejpeg($img_mini,$_FILES['fichier']['tmp_name'] );
}
elseif ($infosImg['mime']=='image/png' ) {
$img_big = imagecreatefrompng($_FILES['fichier']['tmp_name']); # On ouvre l'image d'origine
$img_new = imagecreate($x, $y);
# création de la miniature
$img_mini = imagecreatetruecolor($x, $y)
or $img_mini = imagecreate($x, $y);

// copie de l'image, avec le redimensionnement.
imagecopyresized($img_mini,$img_big,0,0,0,0,$x,$y,$infosImg[0],$infosImg[1]);

imagepng($img_mini,$_FILES['fichier']['tmp_name'] );
}
elseif ($infosImg['mime']=='image/gif' ) {
$img_big = imagecreatefromgif($_FILES['fichier']['tmp_name']); # On ouvre l'image d'origine
$img_new = imagecreate($x, $y);
# création de la miniature
$img_mini = imagecreatetruecolor($x, $y)
or $img_mini = imagecreate($x, $y);

// copie de l'image, avec le redimensionnement.
imagecopyresized($img_mini,$img_big,0,0,0,0,$x,$y,$infosImg[0],$infosImg[1]);

imagegif($img_mini,$_FILES['fichier']['tmp_name'] );
}
{
// Parcours du tableau d'erreurs
if(isset($_FILES['fichier']['error'])
&& UPLOAD_ERR_OK === $_FILES['fichier']['error'])
{
// On renomme le fichier
$nomImage = md5(uniqid()) .'.'. $extension;
// Si c'est OK, on teste l'upload
if(move_uploaded_file($_FILES['fichier']['tmp_name'], TARGET.$nomImage))
{
$message = 'Upload réussi !';
}
else
{
// Sinon on affiche une erreur systeme
$message = 'Problème lors de l\'upload !';
}
}
else
{
$message = 'Une erreur interne a empêché l\'uplaod de l\'image';
}
}
}
else
{
// Sinon erreur sur les dimensions et taille de l'image
$message = 'Erreur dans les dimensions de l\'image !';
}
}
else
{
// Sinon erreur sur le type de l'image
$message = 'Le fichier à uploader n\'est pas une image !';
}
}
else
{
// Sinon on affiche une erreur pour l'extension
$message = 'L\'extension du fichier est incorrecte !';
}
}
else
{
// Sinon on affiche une erreur pour le champ vide
$message = 'Veuillez remplir le formulaire svp !';
}
?>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr"> <head> <title>Upload d'une image sur le serveur !</title> </head> <body> <?php if( !empty($message) ) { echo '<p>',"\n"; echo "\t\t<strong>", htmlspecialchars($message) ,"</strong>\n"; echo "\t</p>\n\n"; } ?> <!-- Debut du formulaire --> <form enctype="multipart/form-data" action="<?php echo htmlspecialchars($_SERVER['PHP_SELF']); ?>" method="post"> <fieldset> <legend>Formulaire</legend> <p> <label for="fichier_a_uploader" title="Recherchez le fichier à uploader !">Envoyer le fichier :</label> <input type="hidden" name="MAX_FILE_SIZE" value="<?php echo MAX_SIZE; ?>" /> <input name="fichier" type="file" id="fichier_a_uploader" /> <input type="submit" name="submit" value="Uploader" /> </p> </fieldset> </form> <!-- Fin du formulaire --> </body></html>

22. Par Emacs le 23/10/2008 00:29

Bonjour,

Peux-tu déplacer ce commentaire dans les forums stp. Je supprimerai ton message dans les heures qui viennent car il n'a pas sa place dans les commentaires.

++

Hugo.

23. Par Uzumaki le 24/10/2008 02:14

bonjour je voudrai savoir si le script du formulaire et celui du upload se mette sur la même page je voudrais savoir comment procéder merci ( au passage je suis un débutant)

24. Par phpmen le 20/12/2008 13:19

merci

25. Par didi le 17/04/2009 04:00

jai un petit probleme avec le script de pasilage getimagesize() [function.getimagesize]: Filename cannot be empty in

26. Par xsaiddx le 18/05/2009 16:33

bonjour j'ai pas compris cette ligne

$infosImg = getimagesize($_FILES['fichier']['tmp_name']);

// On verifie le type de l'image
if($infosImg[2] >= 1 && $infosImg[2] <= 14)

et sur la doc j'ai trouve ca ==>
L'index 2 est une constante parmi IMAGETYPE_XXX, indiquant le type de l'image.

alors je comprend encore pas qu'est ce qu'il ya dans l'index 2


merci

27. Par Emacs le 18/05/2009 19:46

La doc de la fonction getimagesize() est ton amie :

http://fr.php.net/getimagesize

28. Par xsaiddx le 18/05/2009 22:43

ouii j'ai deja vu la doc mais le blem que je pige rien
bon pour quoi tu compare $infoImg[2] avec 1 et qu'est ce qu'il ya dans $imgInfo[2]

merci

29. Par Emacs le 18/05/2009 23:46

L'index 2 contient le type de l'image numéroté de 1 à 14. On vérifie que l'image dont on récupère les infos est bien une image dont le type est prédéfini par le développeur. Nous aurions pu limiter le test aux types GIF, JPG, PNG et BMP par exemple.

30. Par jaymi le 20/06/2009 16:21

j'ai un problème avec certaines images d'extension .JPG qui ne sont pas acceptées. j'ai une réponse qui me dit que le fichier n'est pas une image pourtant ça l'est

31. Par alibaba le 03/07/2009 18:55

Salut Emacs!
Avez-vous testé ce script, car il ne crée pas le répertoire sible s'il n'existe pas.
Quand je supprime le '/' qui precède le nom du repertoire c-à-d, en faisant ceci : define('TARGET', 'files/'; ça fonctionne correctement(le script crée le répertoire et il envoie(upload) aussi l'image).
Merci pour ce tuo, il est top!!!