Tutoriel Linux : développement web avec backend en langage C (Episode 3 – Le couple form / script)

Maintenant que notre serveur NginX est installé et configuré pour être en mesure de transférer les requêtes des clients web aux scripts CGI, nous allons voir dans cet épisode 3 comment se fait ce dialogue entre le client web et le script CGI.

Côté client web : la form

La page web affichée sur le poste client web va utiliser des forms (formulaires HTML en français).

Il s’agit d’un formulaire contenant des items à afficher dans le formulaire (de différents types comme des inputs pour saisir du texte, des boutons radio, des cases à cocher, des zones de saisie de mot de passe, des listes déroulantes, etc.), un script cible (qui pour nous sera écrit en langage C), et un bouton pour activer le formulaire.

Activer le formulaire signifie que le navigateur web du client va lire les données saisies ou choisies par l’utilisateur, et les envoyer au script CGI.

La nature de l’envoi vers le script CGI est indiquée dans le formulaire. Elle peut être GET ou POST.

Si GET est utilisé, les données saisies dans la form seront transmises sur l’URL du script appelé, ce qui n’est pas top pour la sécurité. C’est pourquoi nous utiliserons que la méthode POST, qui envoie les données vers le script dans le flux html, donc non visible des hommes du milieu dès lors qu’on est en https.

Dans l’exemple que nous allons utiliser, voici la page web que l’on affiche sur le client web :

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Formulaire CGI</title>
    <style>
        body { font-family: sans-serif; max-width: 500px; margin: 50px auto; padding: 20px; }
        input[type="text"] { width: 100%; padding: 8px; margin: 10px 0; box-sizing: border-box; }
	input[type="password"] { width: 100%; padding: 8px; margin: 10px 0; box-sizing: border-box; }
        input[type="submit"] { padding: 8px 20px; cursor: pointer; }
        .result { padding: 15px; background: #eef; border-radius: 5px; }
    </style>
</head>
<body>
    <h1>Mon formulaire</h1>
    <form action="/cgi-bin/script.cgi" method="post">
        <label for="texte">Entrez votre texte :</label>
        <input type="text" placeholder="Entrez du texte ici" id="texte" name="texte" required>
	<input type="password" placeholder="Entrez un mot de passe ici" id="mdp" name="motdepasse" required>
        <input type="submit" value="Envoyer">
    </form>
</body>
</html>

Cette page web comporte un petit script CSS pour l’esthétique, mais surtout elle contient une form (visible en gras), dans laquelle on voit que le formulaire contient comme items à afficher un label (du texte), un input requis (ie on ne pourra pas déclencher le formulaire si ce champ est vide) pour saisir du texte, un mot de passe, et un bouton submit pour déclencher le formulaire.

Comme on peut le voir, ce formulaire a pour cible le script dont l’URL est /cgi-bin/script.cgi en utilisant la méthode POST.

On met ce fichier test.html dans /var/www/html sur le serveur web, ce qui affiche la page suivante sur le navigateur du client lorsqu’il se connecte sur cette URL https://serveur/test.html :

Voici ce qui s’affiche quand l’utilisateur saisit les données :

Côté serveur : le script CGI

Comme on l’a vu dans les épisodes précédents, on peut écrire le script dans le langage qu’on veut. Tout ce qui compte est que ce script soit un binaire ELF Linux situé dans /usr/lib/cgi-bin/script.cgi avec des droits en exécution (x). Il faut que ces contraintes soient satisfaites pour que le formulaire html envoie bien ses données au script.

Nous allons donc écrire en C ce premier script très simple (script.c) :

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

char* http_post_request_body()
{
int query_size;

query_size=atoi(getenv("CONTENT_LENGTH"));
char* query_string = (char*) malloc(query_size+10);
if (query_string != NULL) fread(query_string,query_size,1,stdin);
return query_string;
}

int main()
{
char *request_body;
extern char **environ;
char **e;

request_body=http_post_request_body(); // on récupère les données envoyées par le formulaire
// on construit maintenant la page html de réponse, en écrivant sur la sortie standard. NginX intercepera cette sortie et l'enverra tout seul au client web
printf("Content-type: text/html\n\n"); // Cette ligne d'entête est fondamentale. Sans elle le format de réponse ne serait pas respecté
printf("<!DOCTYPE html>\n\
<html lang=\"fr\">\n\
<head>\n\
	<meta charset=\"UTF-8\">\n\
    <title>Page de réponse du script</title>\n\
</head>\n\
<body>\n");
printf("Données reçues du formulaire : %s<br><br>\n",request_body);
printf("Variables d'environnement du formulaire : <br><br>\n");
for(e=environ;*e;e++) printf("%s<br>\n",*e);
printf("</body>\n\
</html>\n");
}

On compile ce script par les commandes :

gcc script.c -o script.cgi
sudo mv ./script.cgi /usr/lib/cgi-bin

Voici ce que répond ce script comme page html sur l’écran du client web dès lors que celui-ci appuie sur le bouton « Envoyer » :

Ce script renvoie deux choses :

Tout d’abord la chaine de caractères exacte qu’il a reçu du formulaire du client. Ici cette chaine est

texte=C%27est+une+vir%C3%A9e+ma%C3%A7onnique+v%C3%A9n%C3%A8re+%23%24*&motdepasse=re%C3%A7u

Attardons nous quelques secondes sur la structure de cette chaine de caractères. Ce qu’on peut constater au premier abord que la forme a envoyé une unique chaine de caractères, qui concatène tout ce que l’utilisateur y a saisi. Pour récupérer cette chaine de caractères nous avons simplement lu l’entrée standard du processus dans lequel tourne le script.

Deuxièmement on constate que cette chaine de caractères est structurée selon les champs présents dans le formulaire (texte=, et motdepasse=), les champs du formulaire étant tous séparés par le caractère & (qu’on peut voir dans &motdepasse=).

Troisièmement, on peut constater que tous les espaces saisis par l’utilisateur dans le formulaire on été remplacés par le caractère +.

Enfin, et ce n’est pas la moindre des informations, tous les caractères spéciaux (comme ‘éèêàçùü#$, etc.) ont été remplacés par leurs codages en HTML (par exemple ‘ remplacé par %27, é remplacé par %C3%A9).

Conclusion de ces observations : la chaine de caractères brute qui est envoyée par les formulaires HTML va devoir passer dans des fonctions en C, restant à écrire (que nous verrons dans le prochain épisode), pour en extraire proprement tous les champs, y compris en remplaçant tous les codages HTML par les caractères initiaux. Il y a une fonction d’extraction des champs et une fonction de retraitement à écrire.

La deuxième chose qu’envoie notre script est la vision de ses variables d’environnement (transmises au script par NginX). Parmi elles, certaines seront utiles à nos futurs scripts. Il suffira de lire ces variables pour accéder aux informations.

Les variables d’environnement qui nous servirons sont :

QUERY_STRING : c’est la variable dans laquelle arriveront les données du formulaire si la méthode utilisée est GET. Avec POST on a vu que les données des formulaires arrivent sur l’entrée standard et la variable QUERY_STRING est vide.

REQUEST_METHOD : contient GET si la méthode GET est utilisée par le formulaire, POST sinon.

CONTENT_LENGTH : contient le nombre de caractères présents dans la chaine de caractères envoyée par le formulaire

REMOTE_ADDR : donne l’adresse IP du client web qui a envoyé le formulaire

HTTP_USER_AGENT : donne les caractéristiques du navigateur du client web ayant déclenché le script

HTTP_COOKIE : donne une chaine contenant les cookies

HTTPS : C’est une variable qui n’est pas présente ici car j’ai fait mon exemple sur localhost en http. Mais si le HTTPS est utilisé pour la connexion, la variable d’environnement HTTPS existe est sa valeur vaut « on ». En testant l’existence de cette variable et son contenu, un script CGI en C peut donc parfaitement savoir si l’appel a utilisé https ou non.

Voilà, nous venons de réaliser notre premier lien form <-> script en C.

Nous verrons dans l’épisode suivant comment extraire en C chacun des champs de la chaine de caractères unique reçue, et comment remplacer tous les codages HTML pour remettre dans le script les données telles que l’utilisateur les a tapées dans son navigateur.

Stay tuned 😎

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *