Outils pour utilisateurs

Outils du site


verif_adresse_mail

Ceci est une ancienne révision du document !


Vérification des adresses mail

Vérification syntaxique des adresses mail

Le mieux pour vérifier que l'adresse mail est syntaxiquement correcte, est de passer par les expressions régulières.

Le problème:

  • c'est que les adresses permises par la norme peuvent être très complexes, voire complètement farfelues pour un être humain “normal” (par exemple, l'adresse !d!x\\y~z%abc@example.com est valide!!!)
  • mais que, par ailleurs, la plupart des serveurs mail n'acceptent que des adresses plus simples (par exemple toto.machin-truc@orange.fr).

Voici les normes de référence d'après wikipedia (http://fr.wikipedia.org/wiki/Adresse_%C3%A9lectronique): bon courage!:

Par exemple, d'après la norme rfc3696, les caractères “! # $ % & ' * + - / = ? ^ _ ` . { | } ~” sont acceptables pour la partie locale (celle à gauche du “@”), avec une double contrainte sur le point: il ne doit pas commencer ou terminer cette partie locale, et à l'intérieur de cette partie, il ne peut pas y avoir de double point.

Autre exemple, certains caractères supplémentaires sont acceptés dans la partie locale, à condition de les quoter avec “\”. Par exemple: “\ ” (un espace), “\@” (un arrobas) ou “\\” (un backslash).


Mais bon, on va simplifier un peu parce qu'en fait, sur la plupart des adresses courantes, un caractère comme “#” ou “}” dans une adresse est plus souvent une faute de frappe qu'un caractère valide…

Voilà l'expression régulière retenue ici:

r"^[a-zA-Z0-9_\-]+(\.[a-zA-Z0-9_\-]+)*@[a-zA-Z0-9_\-]+(\.[a-zA-Z0-9_\-]+)*(\.[a-zA-Z]{2,6})$"

Et sa signification détaillée:

^                               => on commence au début de la chaine
[a-zA-Z0-9_\-]+                 => il y a un premier mot composé de caractères alphanumériques et/ou le blanc souligné et/ou le tiret (mot éventuellement réduit à 1 seul caractère)
(\.[a-zA-Z0-9_\-]+)*            => il peut y avoir éventuellement d'autres mots de même composition, chacun précédé d'un seul point
@                               => l'inévitable arrobas
[a-zA-Z0-9_\-]+                 => il y a un premier mot composé de caractères alphanumériques et/ou le blanc souligné et/ou le tiret (mot éventuellement réduit à 1 seul caractère)
(\.[a-zA-Z0-9_\-]+)*            => il peut y avoir éventuellement d'autres mots de même composition, chacun précédé d'un seul point
(\.[a-zA-Z]{2,6})               => il y a forcément un dernier mot uniquement alphabétique, de longueur pouvant aller de 2 (ex: "fr") à 6 (ex: "museum") et précédé par un seul point
$                               => on termine à la fin de la chaine

Selon cette expression, l'adresse suivante est syntaxiquement valide: “albert-TOTO.machin55_truc@tata.bidule2048.COM”

Remarques:

  • on pourrait faire plus court. Par exemple, [\w] est équivalent à [a-zA-Z0-9_]. Mais le problème des expressions régulières, c'est qu'elles sont difficilement lisibles. Et moi, je veux encore comprendre ce que j'ai fait dans 3 mois… :-D
  • n'oubliez pas le “r” devant la chaine représentant l'expression régulière! En Python, cela désigne une “chaine brute”. Sinon, le '\', par exemple, sera interprété de façon différente et ça ne marchera plus.

Avec quelques connaissances des expressions régulières, on peut, bien sûr, modifier facilement l'expression. Par exemple, empêcher que le 1er caractère soit un tiret. Ou accepter le caractère “+” à l'intérieur d'un des mots. Ou obliger le nom de domaine avant l'extension à avoir au moins 2 caractères. Etc…


Dans les adresses qui “marchent” dans les protocoles du mail, il y a la possibilité d'avoir un nom de présentation avant l'adresse elle-même. Par exemple: "Alain Toto <toto@machin.fr>". Avant de pouvoir tester si l'adresse “toto@machin.fr” est syntaxiquement valide, il faut l'extraire de la chaine complète.

Voilà le code proposé pour cette extraction:

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
import re
 
 
def extractionadrmail(ch):
    """extrait l'adresse mail d'une chaine avec ou sans le nom de présentation"""
    motif = r"^[^<>]*<([^<>]+)>$|(^[^<>]+$)"
    a = re.findall(motif, ch.strip())
    if len(a)>0:
        return ''.join(a[0]).strip()
 
    else:
        return ''

Ce code retourne l'adresse extraite si elle a été trouvée, ou une chaine vide si elle n'a pas été trouvée, ou si le format de la chaine est invalide. Par exemple, s'il y a un “<”, il doit y avoir un “>” après, et aucun autre “<” ou “>”. S'il n'y a pas de “<”, la fonction renvoie la chaine complète, éventuellement débarrassée de ses espaces avant et après.


Vérification de syntaxe d'une adresse mail à la fois

Voilà le code proposé pour vérifier la syntaxe d'une adresse mail à la fois. Pour être autonome, cette fonction intègre la fonction d'extraction précédente:

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
import re
 
def verifadrmail(ch):
    """verifie la syntaxe d'une adresse mail donnée sous forme de chaine"""
    # extraction de l'adresse même dans le cas 'yyyyy <xxxx@xxxx.xxx>' 
    motif = r"^[^<>]*<([^<>]+)>$|(^[^<>]+$)"
    a = re.findall(motif, ch.strip())
    if len(a)>0:
        adr = ''.join(a[0]).strip()
    else:
        adr = ''
    # vérification de syntaxe de l'adresse mail extraite
    if adr=='':
        return False
    else:
        motif = r"^[a-zA-Z0-9_\-]+(\.[a-zA-Z0-9_\-]+)*@[a-zA-Z0-9_\-]+(\.[a-zA-Z0-9_\-]+)*(\.[a-zA-Z]{2,6})$"
        return re.match(motif, adr)!=None

Cette fonction retourne True ou False selon que l'adresse transmise en paramètre est syntaxiquement valide ou pas.


Vérification de syntaxe d'une liste d'adresses mail

Quand on a une liste d'adresses à vérifier, il vaut mieux compiler l'expression régulière une fois pour toute la liste.

La fonction ci-dessous vérifie une liste d'adresses et retourne une double liste composée d'une liste des adresses valides, et d'une liste des adresses invalides.

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
import re
 
def veriflisteadrmail(L):
    """vérifie la syntaxe d'une liste d'adresses mail données sous forme de chaine"""
    R = [] # future liste des adresses valides
    E = [] # future liste des adresses invalides
    reExt = re.compile(r"^[^<>]*<([^<>]+)>$|(^[^<>]+$)")
    reVerif = re.compile(r"^[a-zA-Z0-9_\-]+(\.[a-zA-Z0-9_\-]+)*@[a-zA-Z0-9_\-]+(\.[a-zA-Z0-9_\-]+)*(\.[a-zA-Z]{2,6})$")
    for ch in L:
        # extraction de l'adresse même dans le cas 'yyyyy <xxxx@xxxx.xxx>' 
        a = reExt.findall(ch.strip())
        if len(a)>0:
            adr = ''.join(a[0]).strip()
        else:
            adr = ''
        # vérification de syntaxe de l'adresse mail extraite
        if adr=='':
            E.append(ch)
        else:
            if reVerif.match(adr)!=None:
                R.append(ch)
            else:
                E.append(ch)
    return [R,E]

Et voilà des exemples pour tester la fonction:

L = ["xxxx@xxx.xxx",
     "xxxx.xxx.xxx@xxx.xxx.xxx",
     "toto <xxxx.xxx.xxx@xxx.xxx.xxx>",
     "toto <xxxx.xxx.xxx@xxx.xxx.xxx",
     "toto xxxx.xxx.xxx@xxx.xxx.xxx>",
     "to<to <xxxx.xxx.xxx@xxx.xxx.xxx>",
     "tot>o xxxx.xxx.xxx@xxx.xxx.xxx>",
     "toto <xxxx.xxx.xxxxxx.xxx.xxx>",
     "toto <xxxx..xxx.xxx@xxx.xxx.xxx>",
     ".xxxx@xxx.xxx",
     "xxxx.@xxx.xxx"
     ]

res = veriflisteadrmail(L)

print res[0]  # affichage des adresses valides

print res[1]  # affichage des adresses invalides

Ce qui affiche:

pour les adresses valides:
['xxxx@xxx.xxx', 'xxxx.xxx.xxx@xxx.xxx.xxx', 'toto <xxxx.xxx.xxx@xxx.xxx.xxx>']

pour les adresses invalides:

['toto <xxxx.xxx.xxx@xxx.xxx.xxx',   => manque un '>' à la fin alors qu'il y a un '<' au début
'toto xxxx.xxx.xxx@xxx.xxx.xxx>',    => manque un '<' au début alors qu'il y a un '>' à la fin
'to<to <xxxx.xxx.xxx@xxx.xxx.xxx>',  => trop de '<'
'tot>o xxxx.xxx.xxx@xxx.xxx.xxx>',   => trop de '>'
'toto <xxxx.xxx.xxxxxx.xxx.xxx>',    => manque un arrobas
'toto <xxxx..xxx.xxx@xxx.xxx.xxx>']  => les 2 points successifs sont interdits
'.xxxx@xxx.xxx',                     => on ne peut pas commencer par un point
'xxxx.@xxx.xxx'                      => on ne peut pas terminer la partie locale par un point


Vérification de syntaxe d'une ou plusieurs adresses mail

Dernier code proposé, une fonction qui reçoit les adresses sous différentes formes (chaine, liste, tuple, …) et qui renvoie systématiquement la double liste: [[liste des adresses valides], [liste des adresses anormales]].

Elle accepte même une chaine composée de plusieurs adresses séparées par des “;”.

Cette fonction est intéressante, parce qu'elle permet de faire des envois en masse, en garantissant le format d'entrée (=liste) dans la fonction d'envoie SMTP, et en signalant, sans générer d'exception, les adresses syntaxiquement anormales.

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
import re
 
##############################################################################
# Vérifie la syntaxe d'une adresse mail (chaine) ou d'une liste d'adresses mail
#
# Entrée pour une chaine, accepte:
#   - cas 1: une adresse simple type "xxxx@xxxx.xxx"
#   - cas 2: une adresse précédée par le nom de présentation: "xxxx <xxxx@xxxx.xxx>"
#   - cas 3: une suite d'adresses cas 1 et cas 2, séparées par un ";"
#
# Entrée pour une liste ou un tuple, accepte comme membres: les adresses chaine cas 1 et 2
#
# Si l'entrée n'est ni une chaine str, ni une liste ni un tuple => déclenchement d'une exception
#
# Renvoie une liste de 2 listes: la liste corrigée et la liste des adresses anormales
#
def verifadrmails(L):
    """vérifie la syntaxe d'adresses données sous forme de chaine, de liste ou de tuple"""
    # vérification du paramètre
    if type(L)!=list:
        if type(L)==str:
            L = L.split(';')
        elif type(L)==tuple:
            L = list(L)
        else:
            raise ValueError (u"erreur: format anormal de la liste d'adresses mail")
    # préparation du traitement
    L = [x.strip() for x in L]
    extract = re.compile(r"^[^<>]*<([^<>]+)>$|(^[^<>]+$)")
    verif = re.compile(r"^[a-zA-Z0-9_\-]+(\.[a-zA-Z0-9_\-]+)*@[a-zA-Z0-9_\-]+(\.[a-zA-Z0-9_\-]+)*(\.[a-zA-Z]{2,6})$")
    R = [] # future liste des adresses valides
    E = [] # future liste des adresses invalides
    # traitement de la liste
    for ch in L:
        # extraction de l'adresse même dans le cas 'yyyyy <xxxx@xxxx.xxx>' 
        a = extract.findall(ch)
        if len(a)>0:
            adr = ''.join(a[0]).strip()
        else:
            adr = ''
        # vérification de syntaxe de l'adresse mail extraite
        if adr=='':
            E.append(ch)
        else:
            if verif.match(adr)!=None:
                R.append(ch)
            else:
                E.append(ch)
    return [R,E]


Les vérifications se font avec les mêmes codes que la fonction précédente, à part qu'on peut tester aussi des adresses comme:

# adresse sous forme de chaine
'toto <xxxx.xxx.xxx@xxx.xxx.xxx'
 
# liste d'adresses présentée dans une chaine de caractères:
'toto <xxxx.xxx.xxx@xxx.xxx.xxx; yyyy@yyy.yyy; zzzz.zzz@zzzz.zzz'
 
# liste d'adresses présentée dans un tuple:
('toto <xxxx.xxx.xxx@xxx.xxx.xxx', 'yyyy@yyy.yyy', 'zzzz.zzz@zzzz.zzz')


Vérification d'existence des adresses mail

Dans le protocole SMTP, il y a une fonction qui permet la vérification de l'existence de l'adresse mail: “VRFY”

Et dans le module Python “smtplib”, il y a une méthode de la classe SMTP qui permet cette vérification: “verify(adresse)”.

Mais… car il y a un mais (et un gros), cette vérification est devenue inutilisable parce que pratiquement tous les serveurs mail désactivent cette fonction pour lutter contre le spam!

Pire encore, si vous avez une longue liste d'adresses à vérifier (comme les spammeurs, mais vous, c'est peut-être dans le cadre d'un photo-club!) votre serveur mail (celui de votre FAI) coupera peut-être la connexion smtp au bout d'une dizaine de vérifications (pour smtp.orange.fr, c'est 12). Ceci en espérant que votre adresse IP ne sera pas mise en liste noire :-( .

Alors, pour mémoire, je donne quand même un exemple de code (vous mettez l'adresse de votre serveur mail!):

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
import smtplib
 
def existencemail(admail):
    smtp = smtplib.SMTP("smtp.orange.fr:25")
    res = smtp.verify(admail)
    smtp.quit()
    return res

Ce qui renverra dans quasiment tous les cas le tuple:

(502, 'VRFY command is disabled')

verif_adresse_mail.1228897473.txt.gz · Dernière modification: 2008/12/10 09:24 de tyrtamos

Outils de la page