Contrôles des connaissances de aci
3. Examen de rattrapage du 24 juin 2009 : un client Subversion minimal


On dispose d’un petit gestionnaire de versions distribuées accessible exclusivement par les requêtes GET et POST du protocole HTTP. En l’absence de la requête PUT utilisée abondamment par WebDav, on est donc contraint d’utiliser des formulaires POST utilisant des balises Input de type File pour lui communiquer des fichiers de toute nature.

Le but de l’examen est d’écrire un client HTTP capable d’envoyer de tels formulaires et de réagir aux réponses du gestionnaire. On trouvera un canevas en C qui reprend beaucoup d’algorithmes vu pendant le semestre, mais avec des implémentations parfois différentes.

Le gestionnaire vous est fourni sous la forme d’un script. Sa lecture n’est pas nécessaire, ses spécifications étant indiquées dans les question à venir. Il devra être installé à la racine de votre public_html sous le nom SurVersion.php. Vous devrez créer également le répertoire public_html/reposoir auquel vous donnerez les droits 770, afin que votre serveur HTTP local, en exécutant le script, puisse écrire dans ce répertoire. Vous vérifiez d’abord que les opérations d’export (envoi d’un fichier vers le serveur), d’import (réception d’un fichier présent dans le reposoir) et de modification (envoi de la sortie de la commande diff sur un fichier présent) fonctionnent bien à travers votre navigateur.

Comme l’indique le Makefile fourni, les fichiers à écrire seront nommés n_sendversion.c, où n est le numéro de la question. Ils devront être placés dans votre public_html et seront ramassés automatiquement en fin d’épreuve.

L’épreuve dure trois heures. Le barème n’est qu’indicatif.

Makefile
peroraison.c
peroraison.h
sendversion.c
SurVersion.php

1 La balise input de type file (2 point(s))

Lorsqu’un formulaire comporte une balise input de type file, avec les attributs name et value non vides, le formulaire doit impérativement être envoyé avec la méthode POST et un enctype de valeur multipart/form-data.

Définir la fonction qui manque au canevas pour gérer une telle balise, selon les spécifications énoncées. On testera sa fonction en sauvegardant le formulaire envoyé par le gestionnaire de versions et en remplissant les champs de la balise concernée.

Fichier attendu : 1_sendversion.c

2 La balise form (3 point(s))

La balise form n’est pas gérée par le canevas. La page HTML envoyée par le gestionnaire de versions se permet de ne pas préciser l’attribut action (qui est donc implicitement égal au script émetteur), et pas toujours les attributs method (alors implicitement égal à GET) ni enctype (implicitement vide pour GET, ou égal à application/x-www-form-urlencoded pour POST).

Ecrire une fonction complétant le canevas de sorte qu’il remplisse les champs action, host, script, method, enctype de la structure own avec les informations implicites ou explicites trouvées dans le n-ième formulaire du flux d’entrée du programme C, n étant fourni en argument de celui-ci. On utilisera les constantes du fichier peroraison.h.

Fichier attendu : 2_sendversion.c

3 Réception d’un fichier (3 point(s))

En réponse à une requête GET avec le paramètre action valant import, le gestionnaire de versions renvoie le contenu d’un fichier, en précisant son nom dans un en-tête Content-Disposition : attachment ; filename="nom".

Ecrire la fonction tire_reponse_get s’attendant à recevoir une telle réponse, et sauvegardant le fichier transmis sous le nom demandé, à la racine de votre public_html. Elle renvoie également dans le flux de sortie le nom et la taille du fichier créé.

Fichier attendu : 3_sendversion.c

4 L’envoi form-urlencoded (3 point(s))

La fonction lance_requete distingue trois requêtes possibles, mais le canevas n’implèmente que le cas GET. Définir la fonction envoyer_param_post_application envoyant une requête POST pour un enctype égal à application/x-www-form-urlencoded, c’est-à-dire une requête où le flux d’entrée contient la liste de couples des noms et valeurs indiqués dans le formulaire. En l’occurrence, il s’agit de la saisie dans un balise textarea ; de nouveau, on testera en la remplissant à la main dans la page envoyée par le gestionnaire.

Une difficulté supplémentaire est liée à l’envoi d’un cookie par le gestionnaire de versions, qu’il exige de retrouver lors des requêtes POST pour honorer la demande. La valeur de ce cookie est simplement l’adresse IP du serveur concaténée à celle du client, avec un souligné entre les deux. On n’oubliera donc pas de transmettre l’en-tête HTTP fournissant ce cookie.

Fichier attendu : 4_sendversion.c

5 Réception de notification (2 point(s))

Définir la fonction tire_reponse_post_application s’attendant à recevoir une réponse limitée à un statut. Cette fonction se contentera de renvoyer dans le flux de sortie un message signalant que la modification a été acceptée ou non. On vérifiera évidemment que tel est bien le cas dans le répertoire Reposoir.

On définira également la fonction tire_reponse_post_multipart qui a en fait les mêmes spécifications.

Fichier attendu : 5_sendversion.c

6 La borne (4 point(s))

Un fichier pouvant contenir des & et des =, ces caractères ne peuvent servir de séparateurs pour envoyer le contenu d’un formulaire avec fichier. On peut avoir un aperçu de la méthode employée par les clients HTTP en transmettant un tel formulaire au script CGI-Shell suivant à placer dans public_html/cgi-bin/echo.cgi :

#!/bin/sh
echo "Content-Type: text/plain; charset=iso-8859-15"
echo
cat /dev/stdin
exit

qu’on invoque par exemple avec :

<html><head><title>Miroir</title></head><body>
<form action='cgi-bin/echo.cgi' method='post' enctype='multipart/form-data'>
  <input name='bar' type='text' />
  <input name='fichier' type='file' />
  <input name='foo' type='submit' value='voir' />
</form>
</body>
</html>

On verra ainsi que chaque couple nom, valeur est précédé d’une chaîne arbitraire, nommée borne (boundary), commençant par deux tirets et toujours suivie d’un saut de ligne. La ligne qui suit est ainsi formée :

Content-Disposition : form-data ; name="nom1" ; filename="nom2"

nom1 est la valeur de l’attribut name dans le formulaire ayant provoqué l’envoi. La fin de cette ligne, à partir de filename, n’est présente que si la balise correspondante était Input de type file.

Ecrire la fonction envoyer_param_post, recevant en argument la structure own, un descripteur de flux et une borne, et envoyant sur ce flux les couples présents dans cette structure, selon la spécification décrite ci-dessus.

Fichier attendu : 6_sendversion.c

7 L’envoi form-data (3 point(s))

Définir la fonction envoyer_param_post_multipart, envoyant une requête POST avec un en-tête Content-Type : égal à multipart/form-data, ainsi que le cookie. On choisira arbitrairement mais judicieusement une borne que l’on transmettra à la fonction définie précédemment pour provoquer l’envoi des couples nom, valeur. L’envoi se terminera par une dernière occurrence de la borne, immédiatement suivi de deux tirets et d’un saut de ligne.

Fichier attendu : 7_sendversion.c