SELFHTML

Christian Kruse:
Transmission de fichier avec Perl/CGI

Page d'information: vue d'ensemble

vers le bas Christian Kruse
vers le bas Transmission de fichiers avec Perl/CGI
vers le bas Conditions préalables
vers le bas Source d'un script d'exemple
vers le bas Source du module Fileupload.pm
vers le bas Installation du Script et appel en HTML
vers le bas Remarques pour la sécurité

vers le bas 

Christian Kruse

Adresse électronique: Adresse électronique CK1@wwwtech.de
Présence Internet: Page en langue allemande http://wwwtech.de/

Il s'agit ici d'un article traduit de l'allemand par la rédaction de SELFHTML actuel. Veuillez poser vos questions relatives à cet article uniquement à son auteur, prenant compte que celui-ci ne maîtrise peut-être pas la langue française!

vers le hautvers le bas 

Transmission de fichiers avec Perl/CGI

Qui ne connaît pas le problème: un fichier doit être transmis au serveur par un formulaire via HTTP, alors qu'il n'y a pas d'accès FTP ou que ce dernier ne peut pas être utilisé.
Cet article traite de cette question et des lacunes en matière de sécurité qui lui sont propres.

vers le hautvers le bas 

Conditions préalables

Pour la mise en service de ce script, il vous faut un serveur WWW installé prêt à fonctionner (voir: Page d'information: connexion exigée serveur WWW) et qui supporte CGI (voir: Page d'information: connexion exigée Perl/CGI) et Page en langue anglaise Perl 5).

vers le hautvers le bas 

Source d'un script d'exemple (script CGI en Perl)

Le code source Perl qui suit contient un exemple pour un script de transmission de fichier.

Exemple:

#!/usr/bin/perl

# incorporer le module pour la transmission de fichier
use Fileupload;

# Type de contenu pour la sortie
print "Content-type: text/html\n\n";

# créer l'objet fileupload et évaluer les données du formulaire
my $upload = new Fileupload;
$upload->parse;

# prendre les données du fichier
my %file = $upload->param("myfile");

# sauvegarder le fichier sur le serveur
$upload->writefile("myfile","../".$file{"filename"});

print <<HTML;
<html>
 <head>
  <title>Transmission de fichier</title>
 </head>
 <body bgcolor="#FFFFFF">
  <h1>Le fichier a été transmis avec succès.</h1>

  <p>
   Le fichier  $file{"filename"} a été sauvegardé avec succès sur le serveur.
  </p>
 </body>
</html>
HTML

Explication:

Dan un premier temps, le script incorpore le module Fileupload.pm qui assure l'évaluation des données transmises.

Avec la commande my $fileupload = new Fileupload;, un nouvel objet $fileupload est créé et les données transmises sont lues.

La commande $fileupload->parse; évalue maintenant les données du formulaire et les tient à disposition par la méthode my %file = $fileupload->param("parametername"); .
Dans le hash, les champs suivants sont alors disponibles:
filename (seulement si le champ du formulaire est du type file ),
content (ici se trouve le contenu d'un fichier ou d'un champ de formulaire) et
content-type (ici se trouve le type de contenu d'un fichier, dans la mesure où il est connu).

Dans l'étape suivante, le fichier est alors sauvegardé sur le serveur à l'appel de $fileupload->writefile("parametername","dateiname");.

Enfin, une confirmation du succès de la sauvegarde du fichier est transmise à l'utilisateur.

Attention:

Ce script n' est pas conçu pour être utilisé dans la pratique mais n'est donné qu'à titre d'exemple. Pour un script applicable dans la pratique, les mesures de sécurité doivent être bien plus strictes.

vers le hautvers le bas 

Source du module Fileupload.pm (module CGI en Perl)

Le module Fileupload lit les données du formulaire et les évalue. De plus il vérifie si la taille du fichier n'excède pas la taille permise.

Exemple:

#
# Auteur: CK1
# Date: 23/2/2001
#
# évalue les données du formulaire dans le format "multipart/mixed"
#

package Fileupload;

# incorporer le module pour la déclaration de variables
use strict;

# déclaration de variables
my $formdata = {}; # les donnés du formulaire évaluées
my $data;          # les données du formulaire
my $__parsed = 0;  # Le formulaire a-t-il déjà été évalué?
my $maxfilesize = 2097152; # taille maximale standard pour les fichiers - 2mo


# le constructeur de cet objet
sub new
 {
 my $self = {};
 bless $self;

 $self->_init;

 return $self;
 }

# lecture des données du formulaire
sub _init
 {
 binmode STDIN;
 read STDIN, $data, $ENV{'CONTENT_LENGTH'};
 }

# évaluation des données du formulaire
sub parse
 {
 my $self = shift;

 # trouver la clé qui sépare les données
 $data =~ /(\-+?[a-z0-9]+?)\r\n/i;
 my $key = $1;

 # séparer les données
 my @pieces = split(/$key/,$data);
 shift @pieces;
 pop @pieces;

 foreach my $p (@pieces)
  {
  # champ de formulaire, pas de fichier
  if($p =~ /^[\r\n]{1,2}Content-Disposition: form-data; name="([^"]*?)"[\r\n]{1,2}[\r\n]{1,2}(.*)[\r\n]{1,2}/is)
   {
   my $name = $1;
   my $content = $2;

   $formdata->{$name}->{"content"} = $content;

   # fichier au contenu de type connu
   } elsif($p =~ /^[\r\n]{1,2}Content-Disposition: form-data; name="(.*?)"; filename="([^"]+?)"[\r\n]{1,2}Content-type: (.*?)[\r\n]{1,2}[\r\n]{1,2}(.*)[\r\n]{1,2}/is) {
   my $name = $1;
   my $filename = $2;
   my $ctype = $3;
   my $content = $4;

   $filename =~ s/.*\\//;
   $filename =~ s/.*\///;

   $formdata->{$name}->{"filename"} = $filename;
   $formdata->{$name}->{"content-type"} = $ctype;
   $formdata->{$name}->{"content"} = $content;

   # fichier au contenu de type inconnu
   } elsif($p =~ /^[\r\n]{1,2}Content-Disposition: form-data; name="(.*?)"; filename="([^"]+?)"[\r\n]{1,2}[\r\n]{1,2}(.*)[\r\n]{1,2}/is) {
   my $name = $1;
   my $filename = $2;
   my $content = $3;

   $filename =~ s/.*\\//;
   $filename =~ s/.*\///;

   $formdata->{$name}->{"filename"} = $filename;
   $formdata->{$name}->{"content-type"} = "unknown";
   $formdata->{$name}->{"content"} = $content;
   }

  }

 # fixer la taille maximale standard du fichier; le standard est de 2mo
 if(exists $formdata->{"MAX_FILE_SIZE"})
  { $maxfilesize = $formdata->{"MAX_FILE_SIZE"}->{"content"};  }

 # Si l'un des champs est plus grand, efface le
 foreach my $k (keys %{$formdata})
  { delete $formdata->{$k} if length($formdata->{$k}->{"content"}) > $maxfilesize; }

 # les données du formulaire sont maintenant évaluées
 $__parsed = 1;
 }

# renvoie les données d'un champ de formulaire
# si aucun nom de champ a été transmis, renvoie tous les noms de champ
sub param
 {
 my $self = shift;
 my $name = shift;

 if(!$__parsed)
  { $self->parse; }

 return ($name ? %{$formdata->{$name}} : keys %{$formdata});
 }

# écrit le fichier sur le serveur
# La routine donne le nombre d'octets ayant été écrits.
sub writefile
 {
 local *DAT;
 my $self = shift;
 my $fieldname = shift;

 if($fieldname eq "")
  { return undef; }

 my $filename = shift || $formdata->{$fieldname}->{"filename"};

 open(DAT,">".$filename) or die("Fileupload.pm: Error: $!");
 binmode DAT;
 print DAT $formdata->{$fieldname}->{"content"};
 close(DAT);

 return length($formdata->{$fieldname}->{"content"});
 }

1;

Le module ci-dessus propose les méthodes suivantes:
new - C'est le constructeur.
parse - Ici les données du formulaire sont évaluées.
param - Ici les données d'un champ de formulaire sont renvoyées.
writefile - Ici les données d'un champ de formulaire sont écrites dans un fichier, de préférence le fichier transmis ;-)

vers le hautvers le bas 

Installation du Script et appel en HTML

Pour pouvoir utiliser le module, marquez le code source et copiez le dans un éditeur de texte et enfin sauvegardez le sous le nom "Fileupload.pm". Veillez à ce que le F du début soit en majuscule.
Pour le reste veuillez lire dans SELFHTML la rubrique Page d'information: connexion exigée Introduction en CGI et Perl et l'article sur Autre page d'information la mise en service d'un script CGI de Michael Schröpl .

Maintenant vous pouvez utiliser le module. L'incorporation dans votre fichier HTML pourrait ressembler à ça:

Exemple:

<form action="http://www.votreadresse.fr/cgi-bin/votrescript.pl" method="post" enctype="multipart/form-data">
 <input type="hidden" name="MAX_FILE_SIZE" value="2097152"> <!-- taille maximale du fichier, ici 2mo -->
 <input type="file" name="fichier" accept="text/*" maxlength="2097152">
 <!-- ici d'autres champs de votre choix -->
 <input type="submit"><input type="reset">
</form>

Explication:

Avec l'attribut action du repère <form>, vous donnez l'adresse URL de votre script de transmission de fichier. Indiquez dans l'attribut value du champ hidden, nommé MAX_FILE_SIZE, la taille maximale permise pour le fichier.
Par le champ de fichier appelé "fichier", le fichier est transmis au script.
Vous pouvez définir à la suite le nombre de champs de formulaire que vous voulez.

Attention:

Veuillez toujours indiquer dans le champ de fichier le paramètre accept ainsi que le paramètre maxlength!
Ceux-ci veillent à ce que seuls des fichiers déterminés d'une taille déterminée puissent être transmis.

vers le hautvers le bas 

Remarques pour la sécurité

Une transmission de fichier sur ler serveur implique toujours un certain risque. Un pirate informatique pourrait profiter de votre script pour transmettre ses propres scripts de façon à accéder au serveur et à vos fichiers.

C'est pourquoi quelques mesures de sécurité doivent être prises dès le début. Citons entre autres:

Ce qui pourrait revêtir cet aspect par exemple:

Exemple:

my @extensions = qw(txt dat gif jpg);

if($file{"filename"} !~ /^[a-z\.\-_]+?\.([a-z]{3})$/)
 { die("nom de fichier non valable"); }
else
 {
 my $extension = $1;
 if(!grep($extension,@extensions))
  { die("extension de fichier non valable"); }
 }

Explication:

Dans le petit morceau de code défini ci-dessus une liste des extensions @extensions de fichier valables a été définie.
Puis l'intégrité du nom de fichier est vérifiée. Si des signes particuliers, des filtres ou autres s'y trouvaient, le script serait interrompu avec un message d'erreur.

Veuillez vous reporter à l'article de Wolfgang Wiese pour d'autres remarques sur la sécurité dans les scripts CGI Page en langue allemande sécurité CGI.

vers le haut

© 2001-2005 Seite Informations