/*
Bibliothèque de fonctions qui manipulent un fichier html pour l'envoyer
sur la carte éthernet après des transformations éventuelles
*/
#include <string.h>
#include <SPI.h>
#include <Ethernet.h>
#include <SD.h>
#include "trthtml.h"
#define TAILLEMAX_LIGNE 100
/*
Valeurs possibles pour DEBUG
// pas de #define DEBUG : même fonctionnement qu'avec DEBUG à 0
#define DEBUG 0 // pas de messages sur le moniteur série
#define DEBUG 1 // affichage seulement des message indiquant un envoi ou une réception
#define DEBUG 2 // on affiche en plus les caractères reçus par la carte arduino
#define DEBUG 3 // on affiche également les caractères envoyés par la carte arduino
*/
//#define DEBUG 3
char ligne [TAILLEMAX_LIGNE + 2];
#ifdef DEBUG
#if DEBUG > 0
// variables pour afficher < ou > au début des lignes de debug
char carprec_env = '\n';
char carprec_rec = '\n';
#endif
#endif
/*
Partie 1 : fonctions permettant a réception de données depuis la carte éthernet
*/
// lecture d'un caractère envoyé par le client et affichage éventuel sur le moniteur série
char lire_car_client (EthernetClient client)
{
char c; // caractère lu
// lire un caractère sur le port éthernet
c = client.read ();
#ifdef DEBUG
// si affichage des messages reçus
#if DEBUG > 1
// si nouvelle ligne
if (carprec_rec == '\n')
// l'indiquer sur le moniteur série
Serial.print ("< ");
// afficher le caractère reçu sur le moniteur série
if (c != EOF)
Serial.print (c);
// le mémoriser pour le cas où se serait un changement de ligne
carprec_rec = c;
#endif
#endif
// retourner le caractère lu
return (c);
}
// lecture et mémorisation de l'url de la page web demandée par le client
void lire_url (EthernetClient client, char *url, int taille)
{
int i; // compteur, pour récupérer l'url
char c; // caractère lu
// lire la première ligne jusqu'au /
do
c = lire_car_client (client);
while (c != '/' && c != EOF);
// position maximale du dernier caractère du nom de fichier
taille = taille - 2;
// se positionner au début de l'url
i = 0;
// lire le caractère qui suit le /
c = lire_car_client (client);
// tant que le nom du fichier n'est pas fini
while (c != ' ' && c != '?' && c != EOF && i < taille)
{
// mémoriser le dernier caractère lu
url [i++] = c;
// lire le caractère suivant
c = lire_car_client (client);
}
// fin du nom de fichier
url [i++] = '\0';
// si paramètres get
if (c == '?')
{
// lire et mémoriser la fin de ligne
c = lire_car_client (client);
while (c != ' ' && c != '\n' && c != EOF && i <= taille)
{
// mémoriser le dernier caractère lu
url [i++] = c;
// lire le caractère suivant
c = lire_car_client (client);
}
}
// terminer l'url
url [i] = '\0';
}
// se positionner sur la zone des cookies de la trame envoyée par le client
int trouve_cookies (EthernetClient client)
{
char mot_cle [] = "cookie:";
int i; // compteur;
char c; // caractère lu
// lire un caractère
c = lire_car_client (client);
// tant qu'on n'a pas trouvé de ligne vide
while (c != '\r' && c != EOF)
{
// se positionner au début du mot clé cherché
i = 0;
// tant qu'on trouve des caractères du mot clé
while (tolower (c) == mot_cle [i])
{
// passer au caractère suivant
c = lire_car_client (client);
i++;
}
// si mot clé trouvé en entier
if (mot_cle [i] == '\0')
// succès dans la recherche des cookies
return (1);
// sinon
else
{
// aller en fin de ligne
while (c != '\n' && c != EOF)
// lire un caractère
c = lire_car_client (client);
}
// lire le premier caractère de la ligne suivante
c = lire_car_client (client);
}
// échec : cookies cherchés mais non trouvés
return (-1);
}
// lecture et mémorisation des cookies envoyés par le client
void lire_cookies (EthernetClient client, char *cookie, int taille)
{
int i; // compteur, pour récupérer les cookies
char c; // caractère lu
char carprec; // caractère précédent
// lire un caractère
c = lire_car_client (client);
// se positionner au début de la liste des cookies
i = 0;
// position maximale du dernier caractère de la liste
taille --;
// tant que non fin de trame et non fin de ligne
while (c != EOF && c != '\r' && c != '\n' && i < taille)
{
// mémoriser le caractère lu
cookie [i++] = c;
carprec = c;
// lire le caractère suivant
c = lire_car_client (client);
// sauter l'espace entre un ; et le variable suivante
if (c == ' ' && carprec == ';')
c = lire_car_client (client);
}
// terminer la chaine de caractères des cookies
cookie [i] = '\0';
}
// se positionner sur la zone POST de la trame envoyée par le client
void posit_zone_post (EthernetClient client)
{
char c; // dernier caractère lu
char carprec; // caractère précédent
// lire un caractère
c = lire_car_client (client);
// rechercher un \n suivi d'un \r , ce qui indique une ligne vide
while ((c != '\r' || carprec != '\n') && c != EOF)
{
// mémoriser le dernier caractère lu
carprec = c;
// lire le suivant
c = lire_car_client (client);
}
// si on a bien trouvé cette séquence de caractères
if (c == '\r')
// passer à la ligne suivante
c = lire_car_client (client);
}
// lecture et mémorisation des variables POST dans la trame envoyée par le client
void lire_post (EthernetClient client, char *post, int taille)
{
int i; // compteur, pour récupérer les variables post
char c; // caractère lu
// lire un caractère
c = lire_car_client (client);
// se positionner au début de la chaine post
i = 0;
// position maximale du dernier caractère de la liste des variables post
taille --;
// tant que non fin de trame et non fin de ligne
while (c != EOF && c != '\r' && c != '\n' && i < taille)
{
// mémoriser le caractère lu
post [i++] = c;
// lire le caractère suivant
c = lire_car_client (client);
}
// terminer la chaine de caractères post
post [i] = '\0';
}
// lecture d'une demande de page web par le client
void lire_trame_client (EthernetClient client, char *url=0, int taille=14,
char *post=0, int tailpost=0, char *cookie=0, int tailcook=0)
{
char c; // caractère lu
char cardeb; // tout premier caractère du message (pour savoir si get ou post)
int cooktrouve = 0; // indique si on a cherché et trouvé des cookies
#ifdef DEBUG
#if DEBUG > 0
Serial.println (F("Réception demande du client"));
// initialisation
carprec_rec = '\n';
#endif
#endif
// lire le premier caractère reçu
c = lire_car_client (client);
// si récupération de l'url
if (url)
{
// mémoriser si get ou post
cardeb = c;
// le faire
lire_url (client, url, taille);
// si cookies à récupérer
if (cookie)
{
// rechercher si des cookies ont été envoyés par le client
cooktrouve = trouve_cookies (client);
// si cookies trouvés
if (cooktrouve > 0)
// en mémoriser la liste
lire_cookies (client, cookie, tailcook);
// sinon
else
// liste des cookies vide
*cookie = '\0';
}
// si variables post à récupérer
if (post)
{
// si trame envoyée avec la méthode post
if (toupper (cardeb) == 'P')
{
// si on n'est pas déjà à l'endroit de la zone post
// (on y est si on a cherché des cookies alors qu'il n'y en a pas)
if (cooktrouve >= 0)
// se positionner sur la zone post
posit_zone_post (client);
// mémoriser la liste
lire_post (client, post, tailpost);
}
// sinon
else
// liste des variables post vide
*post = '\0';
}
}
// tant qu'on trouve des caractères à lire
while (c != EOF)
{
// lire le caractère suivant
c = lire_car_client (client);
}
}
/*
Partie 2 : fonctions permettant l'envoi de données sur la carte éthernet
*/
// envoi d'un caractère sur le port ethernet avec copie éventuelle sur le moniteur série
void envoie_car (char car, EthernetClient client)
{
client.print (car);
#ifdef DEBUG
// si affichage des messages envoyés
#if DEBUG > 2
// si nouvelle ligne
if (carprec_env == '\n')
// l'indiquer sur le moniteur série
Serial.print ("> ");
// afficher le caractère recu sur le moniteur série
Serial.print (car);
// le mémoriser pour le cas où se serait un changement de ligne
carprec_env = car;
#endif
#endif
}
// envoi d'un message sur le port ethernet
void envoie_trame (String trame, EthernetClient client)
{
int i; // numéro du caractère dans la trame
// initialisation
i = 0;
// tant que non fin de trame
while (trame [i])
{
// envoyer un caractère
envoie_car (trame [i], client);
// passer au caractère suivant
i++;
}
}
// envoi d'un message sur le port ethernet suivi d'un passage à la ligne
void envoieln_trame (String trame, EthernetClient client)
{
envoie_trame (trame, client);
envoie_car ('\n', client);
}
// envoi de l'entête d'une page web acceptée par le serveur
void envoie_entete_reponse (EthernetClient client, char *url=0)
{
int pospoint; // pour localiser le suffixe du nom de fichier
#ifdef DEBUG
#if DEBUG > 0
Serial.println (F("On envoie au client"));
#endif
#endif
// Tout d'abord le code de réponse 200 = réussite
envoieln_trame (F("HTTP/1.1 200 OK"), client);
// Puis le type mime du contenu renvoyé, du html
envoie_trame (F("Content-Type: text/"), client);
// si on a passé l'url en paramètre
if (url)
{
// localiser le suffixe du fichier
pospoint = strpos (url, ".");
// si c'est un fichier css, le préciser
if (strcmp (tolower (url + pospoint + 1), "css") == 0)
envoieln_trame (F("css"), client);
// sinon
else
// par défaut, c'est un fichier HTML
envoieln_trame (F("html"), client);
}
// idem si l'url n'est pas précisée
else
envoieln_trame (F("html"), client);
// fin d'envoi de l'entête
envoieln_trame (F("Connection: close"), client);
envoie_car ('\n', client);
}
/*
Partie 3 : fonction permettant :de manipuler les chaines de caractères
*/
// recherche la position de la sous chaine rech dans la chaine de caractères chaine
int strpos (char *chaine, char *rech)
{
int i, j;
// cas particulier : recherche d'une sous chaine vide
if (*rech == '\0')
return (0);
// commencer au début de la chaine à analyser
i = 0;
// bouche jusqu'à sous chaine trouvée ou chaine analysée en entier
while (1)
{
// commencer au début de la sous chaine
j = 0;
// tant qu'on trouve des caractères identiques dans les 2 chaines
while (chaine [i + j] == rech [j])
{
// avancer d'un caractère
j++;
// si on est arrivé en fin de sous chaine
if (rech [j] == '\0')
// retourner sa position dans la chaine à analyser
return i;
}
// si on est arrivé en fin de chaine à analyser
if (chaine [i + j] == '\0')
// retourner "sous chaine non trouvée"
return -1;
// sinon
else
// recommencer à partir du caractère suivant de la chaine à analyser
i++;
}
}
/* extraction d'une sous-chaine de caractères */
void substr (char *orig, char *dest, int debut, int fin)
{
int i, j;
// initialisation
j = 0;
// copie de la sous-chaine
for (i = debut; i <= fin && orig [i]; i++)
dest [j++] = orig [i];
// terminer la chaine
dest [j] = '\0';
}
/*
Partie 4 : fonctions permettant de récupérer le valeurs des variables
passées par les méthodes GET et POST
*/
// convertir un caractère hexadécimal en valeur numérique
int valhexa (char car)
{
// transformer le caractère en chiffre
car = car - '0';
// si c'était une lettre (on a dépassé 9)
if (car > 9)
// corriger la valeur
car = car - 7;
// retourner le résultat
return car;
}
// retourne la valeur d'une variable mémorisée dans une liste
// indique au niveau du code de retour si la variable a été trouvée
int lecvar (char *nom, char *liste, char *result, char separateur)
{
int i, j; // compteurs
char car; // caractère de la variable liste
// se positionner au début de la liste des variables
j = 0;
// tant que non fin de liste
while (liste [j])
{
// se positionner au début du nom de la variable
i = 0;
// tant que le nom carrespond à celui de la liste
while (nom [i] == liste [j])
{
// avancer d'un caractère
i++;
j++;
}
// si fin du nom et on est arrivé sur un = dans la liste
if (! nom [i] && liste [j] == '=')
{
// on va mémoriser la valeur de la variable
i = 0;
// avancer d'un caractère dans la liste
j++;
// tant qu'on n'est ni en fin de liste ni sur l'annonce d'une autre variable
while (liste [j] && liste [j] != separateur)
{
car = liste [j++];
if (car == '+' && separateur == '&')
car = ' ';
else if (car == '%' && separateur == '&')
car = (valhexa (liste [j++]) * 16) + valhexa (liste [j++]);
// mémoriser un caractère de la valeur de la variable et passer au suivant
result [i++] = car;
}
// terminer la chaine de caractère contenant la valeur cherchée
result [i] = '\0';
// variable trouvée
return 1;
}
// sinon
else
{
// se positionner sur la variable suivante
while (liste [j] && liste [j] != separateur)
j++;
}
// s'il y a encore des variables à tester
if (liste [j] == separateur)
// aller sur le nom de la suivante
j++;
}
// variable non trouvée, retourner une chaine vide
*result = '\0';
return 0;
}
// retourne la valeur d'une variable reçue par ma méthode GET
int lecvar_get (char *nom, char *liste, char *result)
{
// la liste des paramètres est après le nom de la page web
return (lecvar (nom, liste + strlen (liste) + 1, result, '&'));
}
// retourne la valeur d'une variable reçue par ma méthode POST
int lecvar_post (char *nom, char *liste, char *result)
{
return (lecvar (nom, liste, result, '&'));
}
// retourne la valeur d'un cookie
int lecval_cookie (char *nom, char *liste, char *result)
{
return (lecvar (nom, liste, result, ';'));
}
/*
Partie 5 : fonctions permettant d'envoyer la page html
dont la structure est mémorisée sur la carte SD en
modifiant certaines parties de cette page.
*/
// lit une ligne dans le fichier HTML, la mémorise dans ligne
void lire_ligne (File descfic)
{
char car; // dernier caractère lu
int poslig; // position dans la ligne
// initialisation
poslig = 0;
// lire un caractère
car = descfic.read();
// tant que non fin de ligne et non fin de fichier et place disponible dans ligne
while (car != '\n' && car != EOF && poslig < TAILLEMAX_LIGNE)
{
// mémoriser le caractère lu
ligne [poslig ++] = car;
// lire le caractère suivant
car = descfic.read();
}
// si on n'est pas arrivé en fin de fichier
if (car != EOF)
// rajouter ce caractère
ligne [poslig ++] = car;
// terminer proprement la chaine de caractères
ligne [poslig] = '\0';
}
// envoie sur le port éthernet le fichier HTML jusqu'à la ligne contenant chaine
// cette dernière ligne est copiée si le paramètre cop_derlig est à 1
void copie_jusque_chaine (File descfic, EthernetClient client, char *chaine, int cop_derlig = 0)
{
// lire une ligne du fichier HTML
lire_ligne (descfic);
// tant qu'on a lu une ligne qui ne contient pas la chaine cherchée
while (*ligne && strpos (ligne, chaine) < 0)
{
// envoyer cette ligne sur le port éthernet
envoie_trame (ligne, client);
// lire la ligne suivante
lire_ligne (descfic);
}
// si demande d"envoi de la ligne contenant la chaine cherchée
if (cop_derlig)
// le faire
envoie_trame (ligne, client);
}
// saute dans le fichier HTML les lignes jusqu'à celle contenant chaine
void sauter_jusque_chaine (File descfic, char *chaine)
{
// lire une ligne du fichier HTML
lire_ligne (descfic);
// tant qu'on a lu une ligne qui ne contient pas la chaine cherchée
while (*ligne && strpos (ligne, chaine) < 0)
// lire la ligne suivante
lire_ligne (descfic);
}
// envoie sur le port éthernet le fichier HTML jusqu'à la ligne contenant chaineorig
// et envoie également cette ligne en remplaçant chaineorig par chainedest
void coprep_chaine (File descfic, EthernetClient client, char *chaineorig, char *chainedest)
{
int posit; // position de la chaine cherchée dans la ligne
int i;
// lire une ligne du fichier HTML
lire_ligne (descfic);
// tant qu'on a lu une ligne qui ne contient pas la chaine cherchée
while (*ligne && (posit = strpos (ligne, chaineorig)) < 0)
{
// envoyer cette ligne sur le port éthernet
envoie_trame (ligne, client);
// lire la ligne suivante
lire_ligne (descfic);
}
// si non fin de fichier
if (*ligne)
{
// envoyer la partie de la ligne avant le chaine trouvée
for (i = 0; i < posit; i++)
envoie_car (ligne [i], client);
// envoyer la chaine de remplacement
envoie_trame (chainedest, client);
// envoyer la fin de la ligne
i = i + strlen (chaineorig);
envoie_trame (& ligne [i], client);
}
}
// envoie sur le port éthernet la fin du fichier HTML
void copie_jusque_fin (File descfic, EthernetClient client)
{
char car; // dernier caractère lu
// on simplifie le traitement en lisant caractère par caractère au lieu de ligne par ligne
car = descfic.read ();
// tant que non fin de fichier
while (car != EOF)
{
// envoyer le dernier caractère lu
envoie_car (car, client);
// lire le suivant
car = descfic.read ();
}
}
/*
Partie 6 : fonctions permettant de générer un menu commun aux pages du site à
partir d'un fichier texte qui contient les noms des liens suivi de leur libellé
*/
// génère une indentation du nombre de caractères demandé
void indente (EthernetClient client, int indent=4)
{
while (indent-- > 0)
envoie_car (' ', client);
}
// génère le menu en HTML à partir du fichier texte qui le contient
void ajoute_menu (char *ficmenu, char *url, EthernetClient client, int indent=4, char *couleur="#FF8000")
{
File descmenu; // descripteur du fichier texte contenant la menu
int posblanc; // position de l'espace entre le nom du lie et son libellé
char *libelle; // libellé du lien
// ouvrir le fichier texte contenant le menu
descmenu = SD.open (ficmenu, FILE_READ);
// si l'ouverture s'est bien passée
if (descmenu)
{
// lire la première ligne
lire_ligne (descmenu);
// tant que non fin de fichier
while (*ligne)
{
// générer un saut de ligne (pour aérer le menu généré)
indente (client, indent);
envoieln_trame ("<br />", client);
// si la ligne lue n'est pas vide
if (*ligne != '\n')
{
// supprimer le passage à la ligne après le libellé du lien
ligne [strlen (ligne) - 1] = '\0';
// chercher la fin du nom du lien
posblanc = strpos (ligne, " ");
// et la marquer comme fin de chaine de caractères
ligne [posblanc] = '\0';
// cherché le début du libellé du lien
libelle = ligne + posblanc + 1;
while (*libelle == ' ')
libelle ++;
// générer l'indentation en débur de ligne HTML
indente (client, indent);
// si on est sur la page correspondant au lien dans le menu
if (strcmp (tolower (ligne), tolower (url)) == 0)
{
// générer lien vers cette page comme page active
envoie_trame (F("<font color=\""), client);
envoie_trame (couleur, client);
envoie_trame ("\">", client);
envoie_trame (libelle, client);
envoie_trame (F("</font>"), client);
}
// sinon
else
{
// générer lien vers la page
envoie_trame (F("<a href=\""), client);
envoie_trame (ligne, client);
envoie_trame ("\">", client);
envoie_trame (libelle, client);
envoie_trame ("</a>", client);
}
// générer la fin de ligne
envoieln_trame ("<br />", client);
}
// lire la ligne suivante dans le fichier menu
lire_ligne (descmenu);
}
// terminé avec le fichier menu
descmenu.close ();
}
// sinon message d'erreur
else
{
envoieln_trame (F("Fichier menu "), client);
envoieln_trame (ficmenu, client);
envoieln_trame (F(" manquant"), client);
}
}