La mauvaise utilisation des expressions rationnelles pour filtrer les TLD

Sniperovitch
5 min readOct 2, 2016

--

J’ai été gêné cette semaine par un système d’enregistrement sur un service web. Ce dernier vérifiait si le format d’adresse email était correct pour pouvoir continuer l’enregistrement. L’ennui est que la difficulté est de s’entendre sur “correct”. Dans ce cas, après quelques tests, je me suis rendu compte que le service prenait n’importe quoi dans la partie locale de l’adresse (avant @) et le nom de domaine mais était strict sur le TLD. Les concepteurs de la vérification considèrent qu’un TLD ne doit être composé que de lettres de A à Z et ces lettres ne doivent être qu’au nombre de deux ou trois. Dommage si votre adresse est sur un .paris, .pizza, .sncf ou .blog. Dommage également si vous avez une adresse chez .موبايلي une société de téléphonie mobile en Arabie Saoudite.

Je me suis demandé combien de TLD étaient ainsi mis de côté. La réponse courte est plus de 70% des TLD qui existent. J’ai pris une liste de 1500 TLD disponibles aujourd’hui, 2 octobre 2016. http://data.iana.org/TLD/tlds-alpha-by-domain.txt (merci @pbeyssac)
Les TLD avec comme préfixe XN — sont encodés en Punycode de la norme IDNA qui permet d’avoir des caractères Unicode dans les noms de domaine.
Pour ces TLD, j’ai mis dans mon jeu de tests les deux formats, Punycode et Unicode. XN — MGBB9FBPOB par exemple correspond au TLD موبايلي cité plus haut. Stéphane Bortzmeyer @bortzmeyer en parle dans son article sur la RFC3492

Testons sur l’ensemble le filtre utilisé, deux ou trois caractères dans l’alphabet latin. [a-z]{2,3}

Filter: indique l’expression rationnelle utilisée.
Suit la liste des TLD qui existent.
Puis un jeu de tests de TLD fictifs, en unicode (coucou @laquadrature), en ascii, numériques de tailles différentes, un exemple bidon de Punycode. Le but est de trouver une regexp qui ne les valide pas.
Enfin des statistiques :
ALL: Le nombre de TLD en tout (réels et fictifs).
REAL: Le nombre de TLD réels.
FAKE: Le nombre de cas fictifs .
MATCH: Le nombre et % de TLD réels que le filtre permet de valider.
NOMATCH: Le nombre et % de TLD réels que le filtre ne valide pas.
FALSEPOSITIV: Le nombre et % de cas invalides qui trompent le filtre.
Le filtre idéal devrait nous donner “100%;0%;0%”

Le filtre [a-z]{2,3} donne “28.44%;71.56%;5.88%”.Loin de gérer tous les TLD réels. Il ne prend pas en compte les TLD de plus de 3 caractères (.paris .sncf .beer...), les TLD de 2 ou 3 caractères en unicode sont aussi mis de côtés (.佛山, .قطر, .հայ, ou 新加坡).Les TLD en ascii de 2 ou 3 caractères sont tous acceptés, y compris le fictif .sni dans les faux TLD.

Il est évident que limiter à deux ou trois caractère la longueur d’un TLD est une erreur mais quelle est la taille du plus long TLD hors unicode ?

Longueur des plus longs TLD

Elle semble être dix-neuf aujourd’hui, tentons d’agrandir le filtre.

Le filtre [a-z]{2,19} donne “83.49%;16.51%;23.53%”.
Une meilleure couverture des TLD hors unicode et punycode.
En contrepartie le nombre de faux-positifs grimpe vite.

En ajoutant un filtre pour prendre en compte les punycode on dépasse 90% pour les TLD réels, mais on prend le risque de valider tous les XN — * tout en laissant de côté les TLD en Unicode.

Le filtre [a-z]{2,19}|xn--[a-z0-9-]+ donne “91.74%;8.26%;29.41%”.
L'ensemble des TLD hors Unicode est pris en compte.

Si on reprend le premier filtre, en acceptant les caractères Unicode et pas seulement a-z on voit que tout est pris en compte sauf les Punycode. Nous venons de voir le filtre pour valider ça, xn — [a-z0–9-]+, mais on arrive à un point où le filtre ne sert plus car il accepte trop de faux positifs.

On ne maîtrise pas les expressions rationnelles si on ne sait pas quand NE PAS les utiliser. Je pense que la validation d’un TLD et a fortiori d’un nom de domaine n’est pas une tâche à déléguer à une regexp. J’ai tout de même une regexp “100%;0%;0%” qui permet de matcher tous les TLD de la liste sans matcher un seul TLD qui n’existe pas. Cette regexp ne m’est pas utile pour valider des TLD, mais pour extraire dans de grandes portions de texte des noms de domaines, grâce à elle je récupère *beer mais pas *snip.
Alors pourquoi ne pas l’utiliser pour valider ? Il faudrait vérifier tous les jours la liste des TLD, tous les jours mettre à jour la regexp dans le code qui l’utilise. On peut aussi, récupérer la liste, la mettre en base et requêter dessus mais c’est le rôle de DNS, qui le fait bien. Alors appuyez vous dessus pour valider des domaines ou TLD.

Cette regexp est un clin d’oeil à David Landgren @dlandgren, un de mes mentors en Perl, j’ai utilisé son module Regexp::Assemble pour la générer. Je lui laisse en exercice la gestion du backtracking :)
Voici la regexp et dessous le résultat.

(?^u:(?:x(?:n — (?:m(?:gb(?:a(?:(?:7c0bbn0|yh7gp)a|3a(?:4f16a|3ejt)|am7a8h|b2bd)|c(?:0a9azcg|a7dzdo)|b(?:9fbpob|h1a71e)|t(?:3dhd|x2b)|erp4a5d4ar|x4cd0ab|9awbf|pl2fh)|k1bu44c|ix891f|xtq1m)|f(?:iq(?:(?:228c5h|s8|z9)s|64b)|z(?:ys8d69uvgm|c2c9e2c)|pcrj9c3d|ct429k|jq720a|lw351e|hbei)|c(?:zr(?:694b|s0t|u2d)|lchc0ea0b2g2a9gcd|(?:2br7|1av)g|ck2b3b|g4bki)|3(?:oq18vl8pn36a|e0b707e|bst00m|ds443g|0rr7y|pxu8k)|n(?:gb(?:c5azd|e9e0a)|qv7f(?:s00ema)?|yqy26a|ode)|v(?:(?:ermgensberat(?:ung-pw|er-ct)|uq861)b|hquv)|k(?:p(?:r(?:w13|y57)d|u(?:716f|t3i))|crx77d1x4a)|w(?:4r(?:85el8fhu5dnra|s40l)|gb(?:h1c|l6a))|8(?:0a(?:s(?:ehdb|wg)|dxhks|o21a)|y0a063a)|5(?:(?:su34j936bgs|tzm5)g|5q(?:w42g|x5d))|9(?:(?:krt00|dbq2)a|0a(?:3ac|is|e)|et52u)|j(?:1a(?:ef|mh)|lq61u9w7b|6w193g|vr189m)|x(?:kc2(?:dl3a5ee0h|al3hye2a)|hq521b)|g(?:(?:2xx48|ecrj9)c|ckr3f0f|k3at1e)|p(?:1a(?:cf|i)|bt977c|gbs0dh|ssy2u)|4(?:5(?:brj9|q11)c|2c2d9a|gbrim)|e(?:ckvdtc9d|fvy88h|stv75g|1a4c)|i(?:1b6b1a6a2e|mr513n|o0a7i)|y(?:fro4i67o|gbi2ammx|9a3aq)|b(?:ck1b9a5dre4c|4w605ferd)|q(?:(?:cka1pm|9jyb4)c|xam)|1(?:1b4c3d|ck2e1b|qqw23a)|6(?:qq986b3xl|frz82g)|l(?:gbbat1ad8j|1acc)|h(?:2brj9c|xt814e)|o(?:gbpf8fl|3cw4h)|r(?:hqv96g|ovu88b)|s(?:9brj9c|es554g)|t(?:60b56a|ckwe)|d1a(?:cj3b|lf)|zfr164b|unup4y)|(?:(?:er|b)o|x)x|i(?:hua)?n|finity|peria|yz)|s(?:[dgjvxz]|t(?:a(?:t(?:e(?:bank|farm)|oil)|r(?:hub)?|ples|da)|o(?:r(?:ag)?e|ckholm)|c(?:group)?|ud(?:io|y)|ream|yle)?|a(?:n(?:dvik(?:coromant)?|ofi)|ms(?:club|ung)|fe(?:ty)?|l(?:on|e)|arland|kura|po?|rl|ve|xo|s)?|h(?:o(?:p(?:ping)?|w(?:time)?|uji|es)|a(?:ngrila|rp|w)|i(?:ksh)?a|riram|ell)?|c(?:[ab]|h(?:o(?:larships|ol)|aeffler|midt|warz|ule)|johnson|ience|o[rt])?|o(?:ft(?:bank|ware)|l(?:utions|ar)|c(?:cer|ial)|n[gy]|hu|y)?|e(?:cur(?:ity|e)|(?:rvice)?s|(?:lec|a)t|ner|ven|xy?|ek|w)?|u(?:pp(?:l(?:ies|y)|ort)|r(?:gery|f)|zuki|cks)?|p(?:readbetting|iegel|ace|ot)|w(?:i(?:ftcover|ss)|atch)|i(?:n(?:gles|a)|lk|te)?|y(?:mantec|stems|dney)?|k(?:y(?:pe)?|in?)?|m(?:art|ile)?|l(?:ing)?|n(?:cf)?|b[is]?|r[lt]?|fr)|c(?:[dgkmnvwxz]|o(?:m(?:p(?:a(?:ny|re)|uter)|m(?:unity|bank)|cast|sec)?|n(?:s(?:truction|ulting)|t(?:ractors|act)|dos)|o(?:[lp]|king(?:channel)?)|(?:l(?:leg|ogn)|ffe)e|u(?:pons?|ntry|rses)|rsica|ach|des)?|a(?:r(?:e(?:ers?)?|avan|tier|d?s)?|p(?:ital(?:one)?|etown)|n(?:cerresearch|on)|l(?:vinklein|l)?|s(?:[ah]|ino)|m(?:era|p)?|t(?:ering)?|fe|b)?|h(?:r(?:istmas|ysler|ome)|a(?:n?nel|se|t)|intai|urch|eap|loe)?|l(?:i(?:ni(?:que|c)|ck)|o(?:thing|ud)|ub(?:med)?|eaning|aims)?|i(?:t(?:y(?:eats)?|adel|ic?)|priani|rcle|sco)?|r(?:edit(?:union|card)?|(?:uise)?s|icket|own)?|e(?:[bo]|nter|rn)|y(?:(?:mr|o)u)?|u(?:isinella)?|b(?:[ans]|re)|f[ad]?|s?c)|a(?:l(?:l(?:finanz|state|y)|i(?:baba|pay)|s(?:ace|tom)|faromeo)?|m(?:e(?:rican(?:express|family)|x)|(?:sterd|f)am|ica)?|c(?:c(?:ountants?|enture)|t(?:ive|or)|ademy|o)?|u(?:di(?:ble|o)?|t(?:hor|os?)|ction|spost)?|b(?:b(?:ott|vie)?|udhabi|ogado|arth|le|c)|i(?:r(?:force|bus|tel)|go?)?|n(?:alytics|droid|quan|z)|r(?:amco|chi|te?|my|pa)?|p(?:artments|p(?:le)?)|f(?:amilycompany|l)?|s(?:sociates|[di]a)?|t(?:torney|hleta)?|g(?:akhan|ency)?|d(?:ult|ac|s)?|e(?:tna|ro|g)?|q(?:uarelle)?|a(?:rp|a)|z(?:ure)?|vianca|kdn|ws?|xa?|o)|b(?:[dfgjstvwy]|a(?:r(?:c(?:lay(?:card|s)|elona)|efoot|gains)?|n(?:[dk]|a(?:narepublic|mex))|uhaus|yern|idu|by)?|o(?:[mt]|o(?:k(?:ing)?|ts)?|s(?:tik|ch)|ehringer|utique|ats|fa|nd)?|l(?:o(?:(?:omber)?g|ckbuster)|a(?:ck(?:friday)?|nco)|ue)|r(?:o(?:(?:th|k)er|adway)|idgestone|adesco|ussels)?|e(?:a(?:uty|ts)|st(?:buy)?|ntley|rlin|er|t)?|u(?:ild(?:ers)?|dapest|siness|gatti|zz|y)|i(?:[doz]|(?:bl|k)e|ngo?)?|n(?:pparibas|l)?|b(?:[ct]|va)?|h(?:arti)?|m[sw]?|c[gn]|zh?)|m(?:[dghknpqrvwxyz]|o(?:[eim]|n(?:tblanc|ster|ash|ey)|v(?:i(?:star|e))?|r(?:tgage|mon)|bi(?:ly)?|torcycles|scow|par|da)?|a(?:r(?:ket(?:ing|s)?|shalls|riott)|n(?:agement|go)?|i(?:son|f)|serati|drid|keup|ttel|cys)?|e(?:(?:lbourn|tlif)e|m(?:orial|e)|d(?:ia)?|nu?|et|o)?|i(?:t(?:subishi)?|crosoft|n[it]|ami|l)|c(?:d(?:onalds)?|kinsey)?|u(?:tu(?:elle|al)|seum)?|t(?:[nr]|pc)?|l[bs]?|ma?|sd?|ba)|t(?:[fglntwz]|r(?:a(?:vel(?:ers(?:insurance)?|channel)?|d(?:ing|e)|ining)|ust|v)?|e(?:l(?:e(?:fonica|city))?|ch(?:nology)?|masek|nnis|am|va)|o(?:(?:ol|ur)s|y(?:ota|s)|[dr]ay|shiba|kyo|tal|wn|p)?|a(?:t(?:a(?:motors|r)|too)|ipei|obao|rget|xi?|lk|b)|i(?:(?:cket|p)s|(?:end|a)a|r(?:es|ol)|ffany)|h(?:eat(?:er|re)|d)?|u(?:nes|shu|be|i)|j(?:(?:max)?x)?|k(?:maxx)?|m(?:all)?|ci?|dk?|vs?)|p(?:[gkmsty]|r(?:o(?:d(?:uctions)?|pert(?:ies|y)|gressive|tection|mo|f)?|a(?:merica|xi)|u(?:dential)?|ess|ime)?|a(?:r(?:t(?:(?:ner)?s|y)|i?s)|n(?:asonic|erai)|mperedchef|ssagens|ge|y)?|i(?:c(?:t(?:ures|et)|s)|n[gk]?|oneer|aget|zza|d)|h(?:oto(?:graphy|s)?|armacy|ilips|ysio)?|l(?:a(?:y(?:station)?|ce)|u(?:mbing|s))?|o(?:litie|ker|hl|rn|st)|f(?:izer)?|ccw|et?|nc?|wc?|ub)|l(?:[bckrvy]|a(?:n(?:c(?:aster|ome|ia)|d(?:rover)?|xess)|m(?:borghini|er)|t(?:robe|ino)?|w(?:yer)?|dbrokes|caixa|salle)?|i(?:fe(?:(?:insuranc|styl)e)?|n(?:coln|de|k)|m(?:ited|o)|(?:ll|ps)y|v(?:ing|e)|(?:xi|d)l|ghting|aison|ke)?|o(?:c(?:ker|us)|tt[eo]|ans?|ndon|ft|ve|l)|e(?:g(?:al|o)|clerc|frak|ase|xus)|u(?:x(?:ury|e)|ndbeck|pin)?|p(?:lfinancia)?l|t(?:da?)?|d?s|gbt)|f(?:[jkm]|i(?:r(?:(?:eston)?|mdal)e|na(?:nc(?:ial|e)|l)|d(?:elity|o)|sh(?:ing)?|t(?:ness)?|at|lm)?|a(?:i(?:rwinds|th|l)|s(?:hion|t)|rm(?:ers)?|mily|ns?|ge)|o(?:o(?:dnetwork|tball)?|r(?:sale|ex|um|d)|undation|x)?|l(?:i(?:(?:ck)?r|ghts)|o(?:rist|wers)|y)|r(?:o(?:nt(?:doo|ie)r|gans)|esenius|l)?|u(?:ji(?:xerox|tsu)|rniture|tbol|nd)|e(?:rr(?:ari|ero)|edback|dex)|tr|yi)|g(?:[fhnpqstwy]|o(?:[ptv]|o(?:d(?:hands|year)|g(?:le)?)?|l(?:d(?:point)?|f)|daddy)|r(?:a(?:(?:phic|ti)s|inger)|een|ipe|oup)?|a(?:l(?:l(?:ery|up|o))?|mes?|rden|p)?|u(?:i(?:tars|de)|ardian|cci|ge|ru)?|l(?:a(?:de|ss)|ob(?:al|o)|e)?|e(?:nt(?:ing)?|orge|a)?|i(?:v(?:ing|es)|fts?)?|m(?:[ox]|ail|bh)?|b(?:iz)?|g(?:ee)?|dn?)|d(?:[jmz]|e(?:l(?:ivery|oitte|ta|l)|nt(?:ist|al)|al(?:er|s)?|si(?:gn)?|mocrat|gree|v)?|i(?:s(?:co(?:unt|ver)|h)|rect(?:ory)?|amonds|gital|et|y)|o(?:[gt]|c(?:tor|s)|wnload|mains|dge|ha)?|a(?:[dy]|t(?:ing|sun|e)|bur|nce)|u(?:n(?:lop|s)|pont|rban|bai|ck)|v(?:ag|r)|(?:cl)?k|rive|ds|hl|np|tv)|h(?:[mnr]|o(?:me(?:s(?:ense)?|depot|goods)|l(?:dings|iday)|t(?:eles|mail)?|n(?:eywell|da)|st(?:ing)?|[ru]se|ckey|w)|e(?:alth(?:care)?|l(?:sinki|p)|r(?:mes|e))|i(?:samitsu|tachi|phop|v)|a(?:mburg|ngout|us)|y(?:undai|att)|dfc(?:bank)?|u(?:ghes)?|gtv|kt?|sbc|tc?|bo)|r(?:e(?:a(?:l(?:t(?:or|y)|estate)|d)|d(?:umbrella|stone)?|p(?:ublican|air|ort)|n(?:t(?:als)?)?|s(?:tauran)?t|i(?:sen?|t)|views?|cipes|xroth|hab)?|i(?:[op]|c(?:h(?:ardli)?|oh)|ghtathome)|o(?:c(?:her|ks)|gers|deo|om)?|a(?:cing|id)|u(?:hr|n)?|s(?:vp)?|yukyu|we?)|v(?:[cg]|i(?:s(?:ta(?:print)?|ion|a)|(?:aje|lla)s|(?:kin)?g|(?:rgi)?n|v[ao]|deo|p)?|e(?:r(?:mögensberat(?:ung|er)|sicherung|isign)|(?:nture|ga)s|t)?|o(?:t(?:[eo]|ing)|lkswagen|yage|dka)|a(?:n(?:guard|a)|cations)?|(?:laandere)?n|u(?:elos)?)|i(?:[dlq]|n(?:[gk]|t(?:e(?:rnationa)?l|uit)?|s(?:ur(?:anc)?|titut)e|(?:vestment|dustrie)s|f(?:initi|o))?|s(?:t(?:anbul)?|elect|maili)?|m(?:mo(?:bilien)?|amat|db)?|c(?:[eu]|bc)|t(?:au|v)?|(?:kan)?o|r(?:ish)?|(?:ee)?e|piranga|[bf]m|inet|wc)|e(?:[eg]|x(?:(?:traspac|chang)e|p(?:osed|ress|ert))|n(?:gineer(?:ing)?|terprises|ergy)|s(?:(?:uranc|tat)e|q)?|d(?:u(?:cation)?|eka)|u(?:rovision|s)?|r(?:icsson|ni)?|ve(?:rbank|nts)|(?:quipmen)?t|m(?:erck|ail)|p(?:ost|son)|a(?:rth|t)|co?)|n(?:[lpuz]|e(?:t(?:(?:ban|wor)k|flix)?|x(?:(?:tdirec)?t|us)|ustar|ws?|c)?|o(?:rt(?:hwesternmutual|on)|w(?:ruz|tv)?|kia)?|a(?:t(?:ionwide|ura)|goya|dex|me|vy|b)?|i(?:k(?:on|e)|ssa[ny]|nja|co)?|r[aw]?|fl?|go?|y?c|ba|hk|tt)|w(?:[fs]|e(?:ather(?:channel)?|b(?:site|cam|er)|d(?:ding)?|i(?:bo|r))|a(?:l(?:mart|ter|es)|ng(?:gou)?|tch(?:es)?|rman)|i(?:n(?:(?:dow|ner)s|e)?|lliamhill|en|ki)|o(?:lterskluwer|r(?:ks?|ld)|odside|w)|hoswho|t[cf]|me)|o(?:r(?:i(?:entexpres|gin)s|a(?:cl|ng)e|g(?:anic)?)|n(?:(?:yoursid)?e|l(?:ine)?|g)|l(?:ayan(?:group)?|dnavy|lo)|(?:kinaw|sak)a|b(?:server|i)|t(?:suka|t)|ff(?:ice)?|m(?:ega)?|pen|oo|vh)|k(?:[gmwz]|e(?:rry(?:propertie|logistic|hotel)s)?|i(?:[am]|nd(?:er|le)|tchen|wi)?|o(?:matsu|sher|eln)|(?:aufe)?n|p(?:mg|n)?|r(?:e?d)?|y(?:oto)?|uokgroup|ddi|f?h)|j(?:o(?:[ty]|b(?:urg|s))?|e(?:welry|tzt|ep)?|p(?:morgan|rs)?|u(?:niper|egos)|a(?:guar|va)|c[bp]|l[cl]|mp?|nj)|y(?:[et]|o(?:(?:koham|g)a|u(?:tube)?|dobashi)|a(?:maxun|chts|ndex|hoo)|un)|ا(?:ل(?:(?:عليا|ارد)ن|سعودية|جزائر|مغرب)|بوظبي|رامكو|مارات|یران)|u(?:[agkyz]|n(?:i(?:versity|com)|o)|b(?:ank|s)|connect|p?s|ol)|z(?:[mw]|a(?:ppos|ra)?|ip(?:po)?|uerich|ero|one)|م(?:و(?:بايلي|قع)|ليسيا|صر)|q(?:ue(?:bec|st)|pon|vc|a)|м(?:о(?:сква|н)|кд)|ب(?:ازار|ھارت|يتك)|இ(?:ந்தியா|லங்கை)|о(?:нлайн|рг)|سو(?:دان|رية)|中(?:[信国國]|文网)|ع(?:راق|مان)|(?:グーグ|セー)ル|с(?:айт|рб)|சிங்கப்பூர்|嘉里(?:大酒店)?|大(?:众汽车|拿)|香(?:格里拉|港)|(?:组织)?机构|б(?:ел|г)|р(?:ус|ф)|新(?:加坡|闻)|网[址店站络]|فلسطين|ファッション|商[城店标]|همراه|संगठन|భారత్|公[司益]|台[湾灣]|手[机表]|政[务府]|닷[넷컴]|дети|تونس|شبكة|भारत|ভারত|ਭਾਰਤ|ભારત|ලංකා|クラウド|ポイント|電訊盈科|ком|укр|қаз|հայ|קום|قطر|كوم|कॉम|नेट|คอม|ไทย|みんな|ストア|我爱你|淡马锡|诺基亚|飞利浦|ελ|ею|გე|コム|世界|企业|佛山|信息|健康|八卦|在线|娱乐|家電|工行|广东|微博|慈善|时尚|書籍|游戏|澳門|点看|珠宝|移动|联通|谷歌|购物|通販|集团|食品|餐厅|삼성|한국))

--

--