IntroCréer son site webHTMLJavaScript • [Perl]
Liens  
 Perl - Uploader un fichier 

Les premières pages de cette rubrique sur Perl n'ont d'autre but que de mettre le pied à l'étrier des curieux qui veulent tâter du Perl. Maintenant, on passe aux choses sérieuses : Vous avez ramé des heures pour enfin voir fonctionner votre premier script, vous avez perfectionné votre connaissance de Perl en pompant à droite et à gauche sur le web, parmi la multitude des exemples. Vous avez même découvert qu'il y a des "modules" qui permettent d'en faire encore plus...

Voici maintenant quelque chose de quasi introuvable et pourtant extrèmement utile : une version lisible d'un script d'upload. Les visiteurs de vos pages pourront désormais envoyer des fichiers sur votre site (avec contrôle de taille possible). Une page assez longue, pour un script très court.

Le formulaire utilisateur

C'est notre bon vieux HTML qui va s'occuper de fournir à l'utilisateur l'interface de sélection et d'envoi du fichier. Rien de bien nouveau là-dedans à part peut-être le ENCTYPE et le type de INPUT. Vous prenez donc une belle page web blanche, et vous inscrivez :

<FORM METHOD="POST" ACTION="/cgi-bin/upload.pl" ENCTYPE="multipart/form-data"> 1) Sélectionnez le fichier :<BR> <INPUT TYPE="FILE" SIZE="40"><BR> <INPUT TYPE="SUBMIT" VALUE="2) Télécharger"><BR> 3) Patientez... </FORM>

Ce qui produira un magnifique formulaire :

1) Sélectionnez le fichier :


3) Patientez...

Une fois sélectionné le fichier par l'intermédiaire du bouton "Parcourir...", l'utilisateur clique sur "Télécharger" et l'aventure commence...

Le navigateur invoque le script /cgi-bin/upload.pl du serveur et lui communique d'abord le type de données à tout faire "multipart/form-data", puis lui envoie le fichier sélectionné. Ça peut prendre un certain temps, dépendant de la connexion, de la rapidité et de la disponibilité du serveur et, bien sûr, de la taille du fichier. La réponse du script ne sera envoyée que lorsqu'il aura reçu tout le fichier.

Le script

Le script en question exploite pas mal de fonctionalités et on ne m'en voudra pas de ne pas détailler toutes les explications, n'est-ce pas ? Merci. La page est déjà assez longue, non ?

Le processus du script a accès à un certain nombre de données, rangées dans des variables qu'on appelle "variables d'environnement". Par exemple, quand on envoie au script les données d'un formulaire avec la méthode GET, celles-ci sont placées dans la variable QUERY_STRING. L'environnement a une taille limitée, trop petite pour notre fichier. On a donc inventé la méthode POST qui dit au script de boire directement au robinet, car il n'a pas de récipients assez grands. On lui précise quand même la quantité à ingurgiter : la variable CONTENT_LENGTH contient le nombre d'octets envoyés par le navigateur (fichier + infos complémentaires).

Si vous voulez installer un contrôle de la taille du fichier envoyé, il faut tester la valeur cette variable dès le début de script et n'autoriser ce qui va suivre que si la taille est acceptable. En Perl, on accède aux variables d'environnement par l'intermédiaire d'un tableau associatif nommé %ENV. Pour lire la valeur de la variable d'environnement CONTENT_LENGTH, on utilise la syntaxe Perl $ENV{'CONTENT_LENGTH'}.

Bref, on va faire les sauvages pour cette première démo : Pas de contrôle de taille d'une part, et réception de l'intégralité du fichier dans une variable, d'un seul coup ! C'est très dangereux car il devient très facile de saturer le serveur avec un seul upload. Normalement, il faudrait lire le fichier à petites gorgées (on verra ça plus tard).

Récupération des données

On commence par dire au script qu'on va lui envoyer des octets qu'il ne faudra pas abîmer. En effet, le serveur web est habitué à recevoir de l'ascii (7 bits sur 8 sont considérés) :

binmode STDIN;

On indique ensuite au script de placer la dose d'octets reçus (on connait leur nombre) dans une variable qu'on décide d'appeler $donnees :

read STDIN, $donnees, $ENV{'CONTENT_LENGTH'};

STDIN est "l'entrée standard" du processus dans lequel tourne notre script. C'est par là qu'un programme CGI peut recevoir des données volumineuses (grâce à la méthode POST, choisie pour l'émission). STDIN est considéré comme un fichier que le script peut lire, d'où l'utilisation de read.

Séparation des données

Nous allons maintenant séparer les données reçues en plusieurs morceaux. Le navigateur a envoyé, en plus du fichier, un certain nombre d'informations : les "en-têtes HTTP", le nom du fichier, etc...). Comme on est malins, on place ces morceaux dans un tableau qu'on appelle @morceaux :

@morceaux = split /-----------------------------.{9}/, $donnees;

Joli, non ? Un déchiffrage complet de cette ligne nous emmènerait au pays joyeux de MIME et de HTTP, mais aujourd'hui on a décidé d'aller ailleurs. Sachez simplement que les infos qui nous intéressent se trouvent dans la deuxième case du tableau @morceaux, c'est à dire dans $morceaux[1].

On va considérer que ce bloc est un ensemble de lignes. Le nom du fichier (donné par l'utilisateur) est quelque part sur la 2ème ligne, les données étant, elles, sur la 5ème. On reprend donc notre hachoir (split) pour récupérer les 5 premières "lignes" :

@fichier = split /\n/, $morceaux[1], 5;

J'abandonne ici le nom d'origine du fichier mais rien ne vous empêche d'aller jeter un coup d'oeil sur $fichier[1], facile à découper et à comprendre. Le contenu du fichier est, comme on l'a dit, dans $fichier[4]. On va illico copier tout ça sur notre serveur, dans le répertoire qui va bien, sous un nom qui nous plaît. Mais avant, on doit se débarrasser de quelques caractères de fin de ligne qui sont encore collés à la fin des données :

chop $fichier[4];
chop $fichier[4];

Ecriture du fichier sur le serveur

On y est presque ! Le plus dur est passé. On décide de placer le fichier dans un répertoire (existant) nommé images, et d'appeler le fichier toto.jpg . On commence par ouvrir ce fichier en écriture :

open FICHIER, ">images/toto.jpg";

Si un fichier du même nom existe déjà à cet endroit, il sera écrasé. On indique ensuite au script qu'on lui envoie des octets dont il faut conserver l'intégrité (on a déjà évoqué cette affaire) :

binmode FICHIER;

On écrit les octets dans le fichier, que l'on ferme ensuite :

print FICHIER $fichier[4];
close FICHIER;

Vous pensez qu'on a terminé ? Pas tout à fait. Les traitements sont terminés, on a effectivement récupéré un fichier sur le serveur, mais si on s'arrête là, le navigateur de l'utilisateur ne va pas être content (message "document vide", ça vous dit quelque chose ?). Il veut donc une réponse, alors on va la lui donner :

print <<FIN;
Content-type: text/html

<HTML><BODY>Merci</BODY></HTML>
FIN

C'était la version minimum d'un script d'upload, mais il faut s'entourer de plus de sécurité pour les scripts destinés au public. Le contrôle de la taille est impératif, sous peine de saturation du serveur. Pensez aussi au nombre de fichiers que le serveur peut contenir. Il faut également contrôler la nature des fichiers reçus. Les risques de problèmes étant assez nombreux, on réserve souvent ce genre de fonctionalité aux seuls administrateurs du site.