Introduction
Vous venez de développer un formulaire de contact sur vos pages Web pour recevoir des emails d’internautes, et à peine quelques jours plus tard, votre boîte aux lettres est inondée de spams, encore appelés pourriels.
Il existe bien Captcha qui demande à l’internaute de retranscrire les lettres et chiffres d’une image pour s’assurer qu’il ne s’agit pas d’un robot de spam :
cependant pour avoir été contraint plusieurs fois sur différents sites à retranscrire les caractères, vous vous êtes surpris à vous demander si il s’agissait d’une minuscule ou d’une majuscule, si le chiffre était placé avant ou après la lettre, parfois vous devez plisser des yeux pour reconnaître un caractère. Bref, vous ne souhaitez pas mettre en œuvre cette technologie non seulement pour les malvoyants, mais aussi pour les astigmates, hypermetropes… qui n’ont pas leurs lunettes à portée de main.
En glanant ça et là sur le Web : oui, une méthode anti spam existe sans avoir recours à Captcha. Elle est simple et en pur HTML / Javascript. Elle a fait ses preuves, depuis la mise en place de celle-ci, plus aucun spam ! en provenance directe du formulaire.
Code d’origine du formulaire
Le code HTML 5 du formulaire est on ne peut plus basique.
<form id="formcontact" method="post" accept-charset="UTF-8">
<label>Sujet</label>
<input name="subject" required="true" placeholder="Sujet...">
<label>E-mail</label>
<input name="email" required="true"
pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$"
placeholder="nom@domaine.com">
<label>Message</label>
<textarea id="message" name="message"
placeholder="Votre message" required="true">
</textarea>
<cite>* Champs obligatoires</cite>
<input name="sendmail" required="true" id="sendmail"
type="submit" value="Envoyer">
</form>
Code anti spam
Bien entendu, l’attribut action="mailto:nomprenom@domaine.com"
doit être banni du formulaire. Cette directive est un nid à spams.
Un robot de spam remplit dans leur majorité tous les champs d’un formulaire, donc la technique anti spam va consister à ajouter un champ dans le formulaire, non visible pour l’internaute, et appliquer quelques astuces pour inciter très fortement le robot à remplir le champ. Dans le traitement des données reçues par le formulaire, si ce champ est rempli alors il s’agit d’un spam.
Dans le code HTML du formulaire, ajouter ce champ "fantôme" (champ remarque
ici) :
<label class="remarque">Remarque</label>
<input class="remarque" name="remarque"
pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$"
placeholder="nom@domaine.com">
- définir les attributs
name
et/ouid
, mais surtoutpattern
etplaceholder
, ces deux derniers attributs vont encore plus tromper le robot de spam puisqu’ils ont vocation à améliorer l’ergonomie et la lisibilité pour un vrai internaute. Difficile de dire si l’attributpattern
qui définit les masques de saisie a une incidence bénéfique pour mieux bloquer les spams, la littérature sur le Web n’est pas fournie sur ce point mais il y a fort à parier que les robots de spam intègrent les expressions régulières des masques dans leurs algorithmes. - insérer une balise
label
avant le champinput
, les robots savent détecter un champ caché anti spam avec l’absence d’une baliselabel
. - pour les attributs
name
etid
du champ, ne pas appliquer de termes qui seraient trop faciles à interpréter comme des champs anti spams par le robot, comme par exemple :hidden, antispam, cache, fantome, ghost
… Utiliser des identifiants classiques d’un formulaire :email%, remarque, description, complement
… toujours afin d’inciter le robot à remplir le champ. - Bannir l’attribut
type="hidden"
pour cacher le champ et le label à l’internaute, les robots sont en mesure d’interpréter la raison de la présence de cet attribut. Pour cacher le champ et le label, la feuille de style externe pour la classeremarque
prend le relai avec la propriétédisplay:none;
. Éviter de coder cette propriété de style dans le corps de la page au sein de balisesstyle
afin que cela ne soit pas "parsé" et interprété par les robots comme ils le font pour l’attributtype="hidden"
.
#formcontact > .remarque { display:none; }
Et c’est fini, PHP ou votre langage préféré de développement Web prend le relai : si le champ remarque
contient des données, c’est un spam, on s’arrête là pour le traitement des données du formulaire.
if ($_POST['remarque'] != "") { die(); }
Le code PHP peut être améliorié si on souhaite conserver une trace des spams avant la commande die
, les variables $_POST
sont sauvegardées dans un fichier de log
en utilisant var_export
et file_put_contents
:
if ($_POST['remark'] != "") {
$msg = strftime('%Y-%m-%d %H:%M:%S')."\n";
$msg .= var_export($_POST,true)."\n";
file_put_contents('../logs/mailspams.log', $msg, FILE_APPEND | LOCK_EX);
die();
}
Renforcer encore l’anti spam avec Javascript
Javascript multiplie les chances de lutter contre les spams. Pourquoi ? Le volume des pages est énorme sur la toile, aussi la plupart des robots de spam se contentent uniquement de charger la réponse HTTP
pour rechercher des formulaires avec des champs à remplir et des éléments submit
.
Exemple de réponse HTTP
HTTP/1.1 200 OK Set-Cookie: 60gpBAK=R1224190331; path=/; expires=Tue, 20-Sep-2016 18:30:38 GMT Date: Tue, 20 Sep 2016 17:09:54 GMT Content-Type: text/html; charset=UTF-8 Transfer-Encoding: chunked Set-Cookie: 60gp=R446942833; path=/; expires=Tue, 20-Sep-2016 18:20:59 GMT Server: Apache X-Powered-By: PHP/7.0.8 Cache-Control: max-age=0 Expires: Tue, 20 Sep 2016 17:09:54 GMT Vary: Accept-Encoding
<!DOCTYPE html> <html lang="fr"> ... <body> <form id="formcontact" method="POST" accept-charset="UTF-8"> ... <input name="sendmail" required="true" id="sendmail" type="submit" value="Envoyer"> </form> </body> ... </html>
Création dynamique du formulaire après chargement de la page
Lorsque le formulaire est créé dynamiquement en Javascript dans l’arbre DOM de la page HTML après la réponse HTTP, les robots ont encore moins de chance de détecter le formulaire sauf pour les plus évolués d’entre eux, mais ils sont rares. Le code de création du formulaire est plus lourd certes, mais il permet d’échapper à un pourcentage énorme de robots de spams.
Dans l’exemple, le formulaire est ajouté dynamiquement à la fin de la balise article
qui est unique dans la page.
<article>
...
<form id="formcontact" method="POST" accept-charset="UTF-8">
...
</form>
</article>
Le code Javascript de création dynamique du formulaire avec les méthodes createElement, setAttribute et appendChild
est encapsulé dans une fonction appelée display_contact_form
(l’intégralité du code de création des balises du formulaire n’est pas retranscrite pour la lisibilité) :
display_contact_form = function() {
/** Récupération de la balise article */
v_footer = document.getElementsByTagName('article')[0];
/** Création du formulaire */
v_form_contact=document.createElement('form');
v_form_contact.setAttribute('id','formcontact');
v_form_contact.setAttribute('method','post');
v_form_contact.setAttribute('accept-charset','UTF-8');
/** Création du champ Sujet */
v_input_sujet = document.createElement('input');
v_input_sujet.setAttribute('name','subject');
v_input_sujet.setAttribute('required',true);
v_input_sujet.setAttribute('placeholder','Sujet...');
/** Ajout du champ sujet dans le formulaire */
v_form_contact.appendChild(v_input_sujet);
...
v_input_label = document.createElement('label');
v_input_label.appendChild(document.createTextNode('Remarque'));
v_input_label.setAttribute('class','remarque');
v_input_antispam = document.createElement('input');
v_input_antispam.setAttribute('name','remarque');
v_input_antispam.setAttribute('pattern','[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$');
v_input_antispam.setAttribute('placeholder','nom@domaine.com');
v_input_antispam.setAttribute('class','remarque');
v_form_contact.appendChild(v_input_label);
v_form_contact.appendChild(v_input_antispam);
...
/** Ajout du formulaire dans la balise article à la fin */
v_footer.appendChild(v_form_contact);
};
La fonction display_contact_form
est invoquée à la fin du chargement de la page avec la méthode addEventListener
de l’objet window
.
display_contact_form = function() {
...
};
if(window.addEventListener) {
window.addEventListener("load",display_contact_form, false); // IE9, Chrome, FireFox
}
Cette cinématique est insérée directement dans l’entête de la page (<head>...</head>
) ou dans un script javascript (lib.js
) qui est chargé en entête de la page.
<head>
...
<script type="text/javascript" src="../js/lib.js"></script>
...
</head>
Alternative plus légère avec Javascript
Si le code de création du formulaire semble trop lourd ou surtout si le formulaire est généré par un script PHP non modifiable (plug in d’un logiciel…), ce qui implique dans ce cas de figure la présence du formulaire dans la réponse HTTP, une alternative plus légère, toujours avec Javascript, est possible.
Le champ "caché" anti spam et son label évoqués précédemment sont ajoutés dynamiquement en Javascript dans le formulaire après le chargement de la page.
La classe remarque
(display:none
dans la feuille de style) est bien entendu
appliquée sur ces deux éléments pour cacher ces champs à l’internaute.
add_antispamfield = function() {
v_form_contact = document.getElementById('formcontact');
v_input_label = document.createElement('label');
v_input_label.appendChild(document.createTextNode('Remarque'));
v_input_label.setAttribute('class','remarque');
v_input_antispam = document.createElement('input');
v_input_antispam.setAttribute('name','remarque');
v_input_antispam.setAttribute('pattern','[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$');
v_input_antispam.setAttribute('placeholder','nom@domaine.com');
v_input_antispam.setAttribute('class','remarque');
v_form_contact.appendChild(v_input_label);
v_form_contact.appendChild(v_input_antispam);
};
if(window.addEventListener) {
window.addEventListener("load",add_antispamfield, false);
}
Si le champ remarque
est saisi, c’est un spam : les données sont écartées.
if ($_POST['remarque'] != "") { die(); }
Si le champ remarque
n’existe pas, et dans ce cas le formulaire a été traité par un robot de spam à partir de la
réponse HTTP, les données sont également écartées :
if ( ! isset($_POST['remarque']) ) { die(); }
Une astuce supplémentaire avec IntersectionObserver
Si c’est possible, afficher les formulaires le plus tard possible dans le chargement de la page. Lorsque le formulaire est en bas de page,
une autre astuce pour encore mieux protéger le formulaire des robots de spams : les API IntersectionObserver
sont très utiles
pour n’afficher le formulaire que lorsque la fenêtre d’affichage (viewport) croise l’emplacement du formulaire.
Cela réduit encore la probabilité que les robots de spams découvrent le formulaire dans le DOM.
Dans le code exemple, le formulaire de contact n’est plus construit intégralement puis ajouté via l’évènement load
.
- L’élément
form
est créé et ajouté dans le DOM, mais le formulaire est vide, aucun élément de soumission. - La fonction
display_contact_form()
ne fait à présent qu’ajouter les éléments enfants dans le formulaire (champs de saisie…). - En utilisant
IntersectionObserver
, si le formulaire croise la fenêtre d’affichage (viewport), la fonctiondisplay_contact_form()
est déclenchée.
/** Getting the tag article */
v_footer = document.getElementsByTagName('article')[0];
/** Form element creation */
v_form_contact=document.createElement('form');
v_form_contact.setAttribute('id','formcontact');
v_form_contact.setAttribute('method','post');
v_form_contact.setAttribute('accept-charset','UTF-8');
/** Adding the form at the end of the tag article*/
v_footer.appendChild(v_form_contact);
display_contact_form = function() {
/** Subject field creation */
v_input_sujet = document.createElement('input');
v_input_sujet.setAttribute('name','subject');
v_input_sujet.setAttribute('required',true);
v_input_sujet.setAttribute('placeholder','Subject...');
/** Adding the field subject in the form */
v_form_contact.appendChild(v_input_sujet);
…
v_input_label = document.createElement('label');
v_input_label.appendChild(document.createTextNode('Remark'));
v_input_label.setAttribute('class','remark');
v_input_antispam = document.createElement('input');
v_input_antispam.setAttribute('name','remark');
v_input_antispam.setAttribute('pattern','[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$');
v_input_antispam.setAttribute('placeholder','name@domain.com');
v_input_antispam.setAttribute('class','remark');
v_form_contact.appendChild(v_input_label);
v_form_contact.appendChild(v_input_antispam);
…
};
obsform = new IntersectionObserver(
(entries, observer) => { entries.forEach( entry => {
if (entry.isIntersecting===true) {
display_contact_form();
obsform.unobserve(entry.target);
}
});
}
, {root: null, rootMargin: '0px', threshold: 0.1} );
obsform.observe(v_form_contact);
Évidemment, cette astuce n’a aucun sens si le formulaire est en haut de la page: dans cette configuration, l’observateur déclencherait immédiatement la fonction display_contact_form()
.
Conclusion
Pour contrer les robots de spam, leur dissimuler les formulaires par une création dynamique et/ou inviter fortement ces robots à saisir un champ dédié à leur détection, les astuces et techniques HTML 5 / Javascript sont ultra simples, sans avoir à recours à des outils tiers comme Captcha. Plus aucun spam depuis.