Tutoriel Linux : développement web avec backend en langage C (Episode 4 – L’extraction et le décodage des données du formulaire)

Dans l’épisode 3 nous avons vu comment coupler un formulaire HTML (form) présenté au client web et rempli par lui, et un script écrit en C qui reçoit et traite ces données.

Nous avons vu que le script en C récupère pour le formulaire une chaine de caractère unique, comme par exemple :

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

Cette chaine reprend les champs définis par le formulaire (texte et motdepasse) séparés par le caractère &, et les valeurs envoyées par le formulaire pour ces champs (C%27est+une+vir%C3%A9e+ma%C3%A7onnique+v%C3%A9n%C3%A8re+%23%24* pour texte, et re%C3%A7u pour motdepasse).

Ces valeurs doivent maintenant pouvoir être extraites une par une, et surtout doivent pouvoir être retraitées pour que le codage HTML effectué par le navigateur du client soit décodé pour retrouver les frappes exactes réalisées par l’utilisateur.

C’est pourquoi nous allons ajouter quelques fonctions au programme script.c que nous avons vu à l’épisode 3.

Nous allons notamment ajouter la fonction http_request_body_item qui permet de lire la valeur de rang n dans la chaine de caractère unique (n=1 pour la dernière valeur de la chaine, n=2 pour l’avant dernière valeur de la chaine, etc.), et la fonction http_translate qui décode la valeur passée en paramètre.

Voici donc le nouveau programme script.c complet, avec capacité d’extraction et de décodage des champs :

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.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 http_request_body_item(char *in_string,int pos,char *out_string)
{
int i,n,start;

i=strlen(in_string);
for(n=0; n<pos;n++) 
	{
	while(in_string[i]!='=') i--;
	i--;
	}
i+=2;
start=i;
do
	{
	out_string[i-start]=in_string[i];
	i++;
	}
while(in_string[i-1]!=0 && in_string[i-1]!='&');
out_string[i-start-1]=0;
}

void http_urldecode(char *string)
{
int i,start;
char *res,buf[50];

start=i=0;
res=(char *)malloc(10*strlen(string));
while(i<=strlen(string))
	{
	if(string[i]!='%') 
		{
		res[start]=string[i];
		i++;
		start++;
		}
	else
		{
		if(string[i+1]=='0' && string[i+2]=='A')
			{
			res[start]='\\';
			res[start+1]='\\';
			res[start+2]='n';
			start = start+3;
			i = i+3;
			}
		else
			{
			if(string[i+1]!='3' || string[i+2]!='C')
				{
				sprintf(buf,"0x%c%c",string[i+1],string[i+2]);
				res[start]=strtol(buf,NULL,16);
				start++;
				i = i+3;
				}
			else
				{
				res[start]='&';
				res[start+1]='l';
				res[start+2]='t';
				res[start+3]=';';
				start = start+4;
				i = i+3;
				}
			}
		}
	}
strcpy(string,res);
free(res);
}

void http_prepare(char *string)
{
int i,start;
char *res;

res=(char *)malloc(strlen(string)*2);
i=start=0;
do
	{
	if(string[i]!=';' && string[i]!='\'' && string[i]!='"') res[start]=string[i];
	else res[start]=' ';
	start++;
	i++;
	}
while(i<=strlen(string));
strcpy(string,res);
free(res);
}

void http_translate(char *in_string)
{
int i;

for(i=0;i<strlen(in_string);i++)
	{
	if(in_string[i]=='+') in_string[i]=' ';
	}
http_urldecode(in_string);
http_prepare(in_string);
}

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

request_body=http_post_request_body(); // on récupère les données envoyées par le formulaire
// On initialise la taille des deux variables qui vont accueillir les données tapées par l'utilisateur dans le formulaire.
// Cette manière de faire garantit que le débordement de tampon (buffer overflow) sera impossible
texte=(char *)malloc(strlen(request_body));
motdepasse=(char *)malloc(strlen(request_body));
http_request_body_item(request_body,2,texte); // On récupère l'avant dernière (position 2 par rapport à la fin) information brute transmise par le formulaire, donc le texte tapé codé HTML
http_translate(texte); // On réalise le décodage texte tel que l'utilisateur l'a tapé
http_request_body_item(request_body,1,motdepasse); // On récupère la dernière (position 1 par rapport à la fin) information transmise par le formulaire, donc le mot de passe tapé
http_translate(motdepasse);
// 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>Texte tapé : %s<br>\nMot de passe tapé : %s<br><br>\n",request_body,texte,motdepasse);
printf("Variables d'environnement du formulaire : <br><br>\n");
for(e=environ;*e;e++) printf("%s<br>\n",*e);
printf("</body>\n\
</html>\n");
}

Comme dans l’épisode 3, on le compile et installe par les commandes :

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

Voici ce que donne la réponse du script en C quand on saisit des données dans le formulaire :

Le script a bien extrait et décodé les données du formulaire puisqu’il affiche :

Texte tapé : C est une virée maçonnique vénère #$*
Mot de passe tapé : reçu

Pour être franc, le script a retiré l’apostrophe de C’est. On peut la garder mais c’est le genre de détail dangereux si le script en C est couplé à une base MySQL (ce qui n’est pas le cas ici), car le maintient de l’apostrophe sans ruser ouvrirait la porte à une attaque par injection SQL.

Il est bien évident que les fonctions C d’extraction et de décodage que nous avons ajoutées seront à utiliser dans tous les futurs scripts. Le réflexe naturel est donc de regrouper ces fonctions dans un fichier C à part, réutilisable.

Si l’on met à part ces fonctions, le corps du script en C restant devient finalement très simple, puisque cela se résume à la fonction main :

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

request_body=http_post_request_body(); // on récupère les données envoyées par le formulaire
// On initialise la taille des deux variables qui vont accueillir les données tapées par l'utilisateur dans le formulaire.
// Cette manière de faire garantit que le débordement de tampon (buffer overflow) sera impossible
texte=(char *)malloc(strlen(request_body));
motdepasse=(char *)malloc(strlen(request_body));
http_request_body_item(request_body,2,texte); // On récupère l'avant dernière (position 2 par rapport à la fin) information brute transmise par le formulaire, donc le texte tapé codé HTML
http_translate(texte); // On réalise le décodage texte tel que l'utilisateur l'a tapé
http_request_body_item(request_body,1,motdepasse); // On récupère la dernière (position 1 par rapport à la fin) information transmise par le formulaire, donc le mot de passe tapé
http_translate(motdepasse);
// 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>Texte tapé : %s<br>\nMot de passe tapé : %s<br><br>\n",request_body,texte,motdepasse);
printf("Variables d'environnement du formulaire : <br><br>\n");
for(e=environ;*e;e++) printf("%s<br>\n",*e);
printf("</body>\n\
</html>\n");
}

Dans l’épisode 5 nous verrons un fichier C réutilisable dans les scripts qui contient tout ce dont on a besoin d’y piocher pour gérer les cas de figure les plus courants, et notamment le dialogue avec une base de données MySQL depuis un script en C.

Stay tuned 😎

Laisser un commentaire

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