Le mieux pour vérifier que l'adresse mail est syntaxiquement correcte, est de passer par les expressions régulières.
Le problème:
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:
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.
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.
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
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')
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')