Kontrola e-mailové adresy

Školení, která pořádám

Článek vyšel na serveru Root.cz.

Často po uživateli chceme, aby zadal svou e-mailovou adresu. Tuto adresu lze kontrolovat podle různých kritérií. Pro účely tohoto článku bude za e-mailovou adresu považován řetězec uživatel@doména, který obvykle chceme po uživateli zadat, a ne mnohem komplexnější řetězec, který lze zadat do hlaviček From, To a dalších.

Syntaktická kontrola

V první řadě tu máme syntaktickou kontrolu. Na webu najdete spoustu pokusů, jak tuto kontrolu provádět, já jsem se podílel např. na této, dodatečně jsem v ní ale stejně našel chybu. Proto uvádím řešení, o kterém si aktuálně myslím, že je správné:

<?php
/** Kontrola e-mailové adresy
* @param string e-mailová adresa
* @return bool syntaktická správnost adresy
* @copyright Jakub Vrána, https://php.vrana.cz/
*/
function check_email($email) {
    $atom = '[-a-z0-9!#$%&\'*+/=?^_`{|}~]'; // znaky tvořící uživatelské jméno
    $domain = '[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])'; // jedna komponenta domény
    return eregi("^$atom+(\\.$atom+)*@($domain?\\.)+$domain\$", $email);
}
?>

Ověření, zda adresa přijímá zprávy

Dále je možné kontrolovat, zda uvedená doména přijímá e-maily, což zjistí třeba funkce checkdnsrr. Podle RFC 2821 sice doména může poštu přijímat i bez MX záznamu, ale nebývá to zvykem.

Dále je možné server oťukat a zjistit, zda e-mail přijme. To je asi nejdůkladnější automatické řešení (opraveno):

<?php
/** Odeslání příkazů SMTP serveru
* @param resource otevřený socket k SMTP serveru
* @param array příkazy k odeslání
* @return bool false v případě, že některý příkaz nevrátí 250
*/
function smtp_commands($fp, $commands) {
    foreach ($commands as $command) {
        fwrite($fp, "$command\r\n");
        $s = fgets($fp);
        if (substr($s, 0, 3) != '250') {
            return false;
        }
        while ($s[3] == '-') {
            $s = fgets($fp);
        }
    }
    return true;
}

/** Ověření funkčnosti e-mailu
* @param string adresa příjemce
* @param string adresa odesílatele
* @return bool na adresu lze doručit zpráva, null pokud nejde ověřit
* @copyright Jakub Vrána, https://php.vrana.cz/
*/
function try_email($email, $from) {
    if (!function_exists('getmxrr')) {
        return null;
    }
    $domain = preg_replace('~.*@~', '', $email);
    getmxrr($domain, $mxs);
    if (!in_array($domain, $mxs)) {
        $mxs[] = $domain;
    }
    $commands = array(
        "HELO " . preg_replace('~.*@~', '', $from),
        "MAIL FROM: <$from>",
        "RCPT TO: <$email>",
    );
    $return = null;
    foreach ($mxs as $mx) {
        $fp = @fsockopen($mx, 25);
        if ($fp) {
            $s = fgets($fp);
            while ($s[3] == '-') {
                $s = fgets($fp);
            }
            if (substr($s, 0, 3) == '220') {
                $return = smtp_commands($fp, $commands);
            }
            fwrite($fp, "QUIT\r\n");
            fgets($fp);
            fclose($fp);
            if (isset($return)) {
                return $return;
            }
        }
    }
    return false;
}
?>

Toto řešení ale pochopitelně nebude fungovat u serverů aplikujících greylisting.

Ověření, zda má uživatel adresu pod kontrolou

Pokud zároveň chceme ověřit, že má uživatel adresu pod kontrolou, musíme mu poslat zprávu:

<?php
/** Vygenerování náhodného řetězce
* @param int délka vráceného řetězce
* @param int použité znaky: <=10 číslice, <=36 +malá písmena, <=62 +velká písmena
* @return string náhodný řetězec
* @copyright Jakub Vrána, https://php.vrana.cz/
*/
function rand_chars($count = 8, $chars = 36) {
    $return = "";
    for ($i=0; $i < $count; $i++) {
        $rand = rand(0, $chars - 1);
        $return .= chr($rand + ($rand < 10 ? ord('0') : ($rand < 36 ? ord('a') - 10 : ord('A') - 36)));
    }
    return $return;
}

$rand_chars = rand_chars();
if (mysql_query("INSERT INTO emaily (email, rand_chars) VALUES ('" . mysql_real_escape_string($_POST["email"]) . "', '" . md5($rand_chars) . "')")) {
    $zprava = "Pokud máte zájem o služby serveru $_SERVER[SERVER_NAME], tak prosím navštivte tento odkaz:
http://$_SERVER[SERVER_NAME]/email_overit.php?id=" . mysql_insert_id() . "&rand_chars=$rand_chars
Pokud o služby serveru zájem nemáte, tak tuto zprávu prosím ignorujte.";
    $hlavicky = "MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 8bit";
    mail($_POST["email"], "Overeni adresy", $zprava, $hlavicky);
}

// email_overit.php:
mysql_query("UPDATE emaily SET overeno = 1 WHERE id = " . intval($_GET["id"]) . " AND rand_chars = '" . md5($_GET["rand_chars"]) . "'");
?>

Závěr

Záleží jen na vás, jak moc důkladně chcete kontrolu e-mailové adresy provádět. Nejčastěji se používají krajní varianty – buď pouhá syntaktická kontrola, nebo ověření, zda má uživatel adresu pod kontrolou.

Jakub Vrána, Řešení problému, 5.5.2006, diskuse: 84 (nové: 0)

Diskuse

Rick:

To vsechno je kontrola jedne adresy? Wow, jak vam mohli dat ridicak...

zjoova:

A jak vypada vase kratsi a elegantnejsi reseni?
Priznavam ze jsem zatim vsude videl jen podobne obludy.

Jakub:

Dělám PHP docela dlouho a jako špatné řešení se mi to nezdá. Jestli jsou tady takoví chytráci (takové mám nejraději) tak nám prosím předvěďte lepší kód. Rád se na něj podívám. TOhle přesně znám. Velká huba, žádné činy...

Joshua :

:)

Zdendys:

Programuji PHP taky již nějaký rok a bohužel se při dokonalé kontrole nevyhnete ledajakým zákoutím a proto se script neůměrně zvětšuje.

Nepruďte a navrhněte kratši dokonalé řešení a budeme vá vzívat do programátorského nebe :-)

j.hlava:

Vzývat zásadně s y.

Jirka K.:

a neúměrně s "ú" :-)

Fanda:

Naopak, oceňuji energii vloženou do tohoto skriptu a hlavně vůli podělit se. Díky moc za skript (proti kódu nemám námitek i když nepoužívám tak důkladnou kontrolu). Ještě jednou díky. A chytráci, až dozrají do téhle úrovně programování, potom pochopí...

dustcart:

Me se to naopak velmi libi. Nahodou na slozitost problemu je to velmi kratke a elegantni reseni, takze to vetsinou kritizuji jen blbecci, kteri tomu nerozumi.

Martin:

V druhej verzii mi try_email($email, $from) vracia 1 aj pri nezmyselnej adrese. Moze to byt nastavenim serveru alebo ...?

Dave:

v overeni mailu je potreba jeste dodat kontrolu pripony. pokud by tam nekdo napsal adresat@domena.xxxxxxxxxx, tak to projde. dal bych tam overeni tak na max 4-5 mist. to by mohlo stacit.

Shuster:

<?php
if (!eregi('^[a-z0-9_]{1}[a-z0-9\-_]*(\.[a-z0-9\-_]+)*@[a-z0-9]{1}[a-z0-9\-_]*(\.[a-z0-9\-_]+)*\.[a-z]{2,4}$', $email))
{
    echo "nespravny format adresy";
}

?>

ikona Jakub Vrána OpenID:

Zapomínáte na doménu .museum. V budoucnu můžou přijít i delší domény, v RFC to nijak omezené není.

Kepi:

Tak tak, .museum, .travel zatím jen 6 znaků, ale uvažuje se i o víc a hlavně jsou tu už náznaky, že bude možné registrovat si TLD za určitý poplatek, pak určitě vzniknou i více než 10 znakové koncovky.

wutter:

No.. v týhle době (po asi 3 letech) se zatim nic neděje a doufam že ani nebude...

ikona Jakub Vrána OpenID:

A tohle je nic? http://en.wikipedia.org/wiki/Top-level_domain#IDN_test_domains

Současné platné top-level domény mají třeba 18 znaků.

wutter:

wow, nevěděl jsem.. :D (ale nevim jestli je zrovna žádaný mít v poli registrovanýho e-mailu nejakahatlabatilka@உதாரணம்.பரிட்சை třeba na český stránce, ale budiž.. :D )

Karel:

No, ale já už jedu na doméně česká-kanada.eu. V němčině jsou to přehlásky (a že jich není málo!) - a už to jede. Zajímalo by mě, zda Arabi ty domény píšou zprava doleva nebo jako číslovky zleva doprava. Tohle písmo ale neznám.. to arabština není.. :-)

Michal:

Doména česká-kanada.eu neexistuje

princezna Masturbína:

Pár zpráv o připravovaných nových TLD:
http://domaintyper.com/new-gTLD/applicant/Famous-Four-Media
https://webtrh.cz/249541-nove-tld-tady-guru-camera

ikona Jakub Hejda:

Vynikají cí článek! Je vidět že v PHP opravdu "myslíte".

francesco3:

Mám také jednu poznámku. Dovolil jsem si použít část kódu pro syntaktickou kontrolu v praxi a zjistil j6sem, že lze mít v emailové adrese tečku ( http://www.pilsen-ride.net/index.php?p=profil&jmeno=fast ) i bezprostředně před zavináčem. Což je situace, kterou fce check_email vyhodnotí negativně.

ikona Jakub Vrána OpenID:

RFC 2822 to nicméně jasně zakazuje.

wutter:

e-mail se chová úplně stejně z tečkama i bez nich.. stačí dát <?php strtr($mail, array('.' => '')); ?> (strtr se dá pužít jaká koliv jiná funkce která to ořezává, ale byla to první která mě napadla.. )

ikona Jakub Vrána OpenID:

To je absolutní nesmysl. U některých poskytovatelů se to dá říct o části před zavináčem (např. to deklaruje GMail, i když mě se to nepodařilo potvrdit), ale obecně se to říct rozhodně nedá.

ikona Web Designer:


Vrelá vďaka za podelenie sa o zaujímavé metódy overenia, zaujala ma časť "Ověření, zda adresa přijímá zprávy".

Nutné však podotknúť, že na zrejme ostrieľaného programátora je tu viac nedostatkov než sa patrí, a práve takéto navádzajú novým, snáď nádejných programátorov na zlé cesty.

málo spomeniem:
- Zbytočne nedokončené overovanie formátu (meno@domena.* ???). už bolo spomenuté. (aj keď sa mi ani nepáči dovolenie )
- Neprehľadné a spomalujúce vytváranie stringov v dvojitých úvodzovkách:
<?php
# pomaľšie a neprehľadné:
$str = "nieco sa $deje tam
dole v udoli"
;
# odporúčam:
$str  = 'nieco sa '.$deje;
$str .= 'dole v udoli';
?>
- Neošetrené vstupy $_POST, $_GET. Chápem alebo dúfam, že tento "ukážkový kód" má iba ilustračný charakter, tým však zlý edukačný dopad.
Takže správne pred takýmto použitím funkcie mail určite najprv overiť vstup, nehovoriac o SQL príkazoch. Minimálne takto:
<?php
if (check_email($email = $_POST['email'])) {
    // ...
}

mysql_query('UPDATE emaily SET overeno = 1 WHERE id = '.intval($_GET[id]).' AND rand_chars = "'.mysql_real_escape_string($_GET[rand_chars]).'"');
?>

ikona Jakub Vrána OpenID:

Délka top-level domény není nijak omezena. Nyní máme doménu .museum, v budoucnu mohou přijít i delší.

Zápis "nieco sa $deje tam" je subjektivně podstatně přehlednější než 'nieco sa '.$deje (navíc bez mezer). Vámi uvedené dva příklady navíc nejsou ekvivalentní, v druhém případě chybí "\n" a to už jde přehlednost do háje úplně.

V patičce je jasně uvedeno, že skripty předpokládají zapnutou direktivu magic_quotes_gpc=On, jsou tedy zcela bezpečné.

ikona Jakub Vrána OpenID:

Kód jsem předělal na magic_quotes_gpc = Off a zmínil jsem to v patičce.

ikona Web Designer:

... píšm trochu neskoro, ešte aj ja som tam spravil pár preklepov ako to čítam.

To čo sa mi nepáči, sú povolené znaky podľa spomenutej normy. Nevidí sa mi to veľmi užitočné (ani bezpečné) a praktické ju dodržať striktne. Niečo ako navrhol komentátor Shuster sa mi zdá skoro dostačujúce, a určite bezpečnejšie. Ale to je iba taký môj názor na základe nočných pocitov.

Namiesto <?php $_GET[id]; ?> je určite slušnejšie <?php $_GET['id']; ?>, veď id predsa nie je žiadna definovaná konštanta, všakže.

ikona Jakub Vrána OpenID:

Dodržovat normu je naopak nutné. Jak by mohlo být něco nebezpečného na dodržování normy? Nebo neužitečného a nepraktického? Můžete si třeba říct, že rovnítko v e-mailové adrese obvykle není. Ale podle normy tam být může a některé konference ho skutečně používají.

<?php $_GET[id]; ?> je samozřejmě chybný zápis, <?php "$_GET[id]"; ?> je ale něco úplně jiného. Když už jste si to nepřečetl v manuálu, tak si to alespoň vyzkoušejte.

Vojtech R:

Reqular Expression Library, konkrétně:

http://regexlib.com/DisplayPatterns.aspx?…&categoryid=1&p=1

gogulux:

Kontrola, jestli ma uzivatel nad mailem kontrolu by mohla byt i poslani nahodneho hesla pro prihlaseni, s tim ze si ho uzivatel po prihlaseni zmeni.

b022d:

Nebo prosté, léta známé, odesílání tzv. aktivace účtu. Pokud to ale chci použít k jiným účelům nebo nechci otravovat uživatele se zbyečnými kontrolami, podobnému monstru se asi nevyhnu, bohužel. A tohle je navíc ještě poměrně elegantní monstrum...

Jan Suchánek:

Tak jsem využil vaší funkce a je bezva jen bych doplnil, šlo by ještě upravit před kontrolou $email tak aby i blbě zadaný byl opravený?

např. jmeno . prijmeni@ domena.cz

Proč o tom píš? Napadají Vás i další chyby? Mě třeba že bych na email mohl použít funkci která ho převede na ascii. Jen nevím třeba co když někdo zadá "Jméno Příjmení"<jmeno.prijmeni@domena.cz> existují ještě další varianty jak lidi mohou napsat svoují emailovu adresu?

Ofi:

Mě by zajímalo jak byste udělali to kdyby měl uživatel pouze den na ověření emailu (tzn. bud docasna data v MySQL nebo nejaky skript ktery se sposti v presnou dobu kazdy den a cisti db od zaznamu starsich nez den) - jde to nejak? a jak moc by to bylo praktické?

ikona Jakub Vrána OpenID:

Do tabulky by se ukládalo datum registrace a skript by to pravidelně promazával. Omezit to na jeden den mi ale praktické nepřijde.

Ofi:

no já se ptal na ten script který by to pravidelně promazával - ale už jsem to našel :) pro ty které by to zajímalo tak je to v mySQL (5.1) příkaz CREATE EVENT.

ikona Jakub Vrána OpenID:

Obvykle se to řeší cronem, ale CREATE EVENT je samozřejmě lepší volba (pokud je k dispozici).

Prdlořeznictví Krkovička, n. p.:

S verzí PHP 5.2 přibyla i možnost využití filterů:
<?php
function check_email($email){
return
filter_var($email, FILTER_VALIDATE_EMAIL)!=false;
}
?>

Prdlořeznictví Krkovička, n. p.:

popřípadě bez toho !=false protože "", "0" nebo 0 nemůžou být validní e-mailové adresy.

ikona Jakub Vrána OpenID:

Aby se to rozlišovalo, tak by tam stejně muselo být !==.

ikona Jakub Vrána OpenID:

Jen pozor na to, že FILTER_VALIDATE_EMAIL uznává i lokální domény. Takže třeba info@example validací projde. A pak mám taky za to, že starší verze kontrolovaly celý řetězec, který jde umístit do hlavičky (takže třeba i včetně jména), ale to už zdá se neplatí.

Michal:

V ofic. PHP manuálu je, že fce eregi v PHP 5.3.0 již nepůjde a v 6.0.0 bude odstraněna. Nemáte tedy k dispozici nějakou jinou, podobnou formu kontroly emailové adresy, jelikož tato funkce se mi velmi líbí ?

ikona Jakub Vrána OpenID:

Funkce eregi("$s", $email) se dá v tomto případě nahradit za preg_match("($s)i", $email).

Miloš Němec:

Nebo lze použít case sensitivní ereg a doplnit regulární výrazy pro uživatelské jméno a doménu o velká písmena.
<?php
    $atom
= '[-a-zA-Z0-9!#$%&\'*+/=?^_`{|}~]'; // znaky tvořící uživatelské jméno
    $domain = '[a-zA-Z0-9]([-a-zA-Z0-9]{0,61}[a-zA-Z0-9])'; // jedna komponenta domény
    return ereg("^$atom+(\\.$atom+)*@($domain?\\.)+$domain\$", $email);

?>

ikona Wlezley:

Nesouhlasím. ereg půjde taky pryč. Viz.: http://php.net/manual/en/function.ereg.php

majo:

<?php
if (preg_match('~^[-a-z0-9!#$%&\'*+/=?^_`{|}\~]+(\.[-a-z0-9!#$%&\'*+/=?^_`{|}\~]+)*@([a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?\.)+[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])$~i', $emailova_adresa)) {
// OK
}
?>

Viktor:

Toto neni moje vlastni reseni. Jen jsem ho nekde drive nasel a nejakou dobu k plne spokojenosti vyuzivam. Je to vicemene synonymum k te prvni variante. Myslis, ze zbytecne slozite nebo je na nem neco zasadne spatne?

<?php
   
function ZkontrolujEmail($email) {
        // First, we check that there's one @ symbol, and that the lengths are right
        if (!ereg("^[^@]{1,64}@[^@]{1,255}$", $email))
            return false; // Email invalid because wrong number of characters in one section, or wrong number of @ symbols.
        // Split it into sections to make life easier
        $email_array = explode("@", $email);
        $local_array = explode(".", $email_array[0]);
        for ($i = 0; $i < sizeof($local_array); $i++)
            if (!ereg("^(([A-Za-z0-9!#$%&'*+/=?^_`{|}~-][A-Za-z0-9!#$%&'*+/=?^_`{|}~\.-]{0,63})|(\"[^(\\|\")]{0,62}\"))$", $local_array[$i]))
                return false;
        if (!ereg("^\[?[0-9\.]+\]?$", $email_array[1])) { // Check if domain is IP. If not, it should be valid domain name
            $domain_array = explode(".", $email_array[1]);
            if (sizeof($domain_array) < 2) return false; // Not enough parts to domain
            for ($i = 0; $i < sizeof($domain_array); $i++)
                if (!ereg("^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]+))$", $domain_array[$i])) return false;
        }
        return true;
    }
?>

ikona Jakub Vrána OpenID:

Je to zbytečně složité.

Jakub Laušman:

Zdravim,

jen jsem ti chtěl poděkovat za rychlé a elegantní řešení mého problému s ověřováním formátu uvedeného emailu...

Tvůj kód mi ušetřil fůrmu času ;-)

Ahoj Kuba

Petr Konůpek:

Díky Jakube za schůdné řešení tohoto problému. Já vyvýjím aplikace ve Windowsovém prostředí a tam bohužel do verze   5.3 PHP nepodporuje funkci getmxrr. Na PHP.net jsem ale našel v komentářích alternativu, jak si tam tuto funkci dodat sám. Zde přeposílám:

<?php

// getmxrr() support for Windows by HM2K <php [spat] hm2k.org>
function win_getmxrr($hostname, &$mxhosts, &$mxweight=false) {
    if (strtoupper(substr(PHP_OS, 0, 3)) != 'WIN') return;
    if (!is_array ($mxhosts) ) $mxhosts = array();
    if (empty($hostname)) return;
    $exec='nslookup -type=MX '.escapeshellarg($hostname);
    @exec($exec, $output);
    if (empty($output)) return;
    $i=-1;
    foreach ($output as $line) {
        $i++;
        if (preg_match("/^$hostname\tMX preference = ([0-9]+), mail exchanger = (.+)$/i", $line, $parts)) {
          $mxweight[$i] = trim($parts[1]);
          $mxhosts[$i] = trim($parts[2]);
        }
        if (preg_match('/responsible mail addr = (.+)$/i', $line, $parts)) {
          $mxweight[$i] = $i;
          $mxhosts[$i] = trim($parts[1]);
        }
    }
    return ($i!=-1);
}
?>
Neznám funkci getmxrr() na tak dokonalé úrovni, abych byl schopen posoudit, zda-li jí tato dokáže plně nahradit, ale pokud ano, pak by to Widlákům usnadnilo život...

ikona IvoSn:

Chtělo by to výraz upravit kvůli novým vymoženostem:
- ereg depreceated
- domény s diakritikou
Místo eregi můžeme použít mb_eregi, ale jak upravit ten regulár?

Jaroslav Kavan:

zdravim v php se vůbec nevyznám,ale na jednom serveru se nám vysktl problém.Jedná se oto že na stránce zadáte jméno účtu a email a tím se posílá nové heslo na email. Vím že je to programování v php a chtěl bys se zeptat jestly by jste sem někdo nemohl ukázat jak by to mnělo vypadat aby to fungovalo.
Protože když jsem chtěl zaslat nové heslo z té stránky napsalo mi to že heslo bylo odesláno ale mail mi nepřišel.

Honza:

A nedal by se obejít ten graylisting pokud by se to samé provedlo dvakrát po sobě?

Michal Bláha:

Ahoj,
kvůli zrušení podpory POSIX výrazů v PHP 5.3 si dovoluji přidat verzi pro preg rozšířenou o kontrolu existenci domény.

<?php
   
public function checkMailAddres($addres) {

        // preg pattern for user name
        // http://tools.ietf.org/html/rfc2822#section-3.2.4
        $atext = "[a-z0-9\!\#\$\%\&\'\*\+\-\/\=\?\^\_\`\{\|\}\~]";
        $atom = "$atext+(\.$atext+)*";

        // preg pattern for domain
        // http://tools.ietf.org/html/rfc1034#section-3.5
        $dtext = "[a-z0-9]";
        $dpart = "$dtext+(-$dtext+)*";
        $domain = "$dpart+(\.$dpart+)+";

        if(preg_match("/^$atom@$domain$/i", $addres)) {
            list($username, $host)=split('@', $addres);
            if(checkdnsrr($host,'MX')) {
                return TRUE;
            }
        }
        return FALSE;
    }
?>

jirka:

Děkuji za efektivní řešení díky němu ušetřím spoustu času

Karel:

Amatérský pokus o modernizaci s IDN - viz níže:
Zkoušel jsem využít výše uvedených příkladů, ale narazil jsem na IDN. Vzal jsem tedy příklad jiný kdesi na webu a doplnil o české a německé IDN znaky prostě tím, že jsem je vyjmenoval (sice amatérské, ale dál se s tím už párat nebudu, funguje to a hotovo!)
Původně tam byl ještě test funkčnosti adresy, ale házelo mi to chybovém hlášení, tak jsem se s tím nepáral a hodil to do poznámek. Berte to tedy, prosím, jako fungující polotovar.

<?php
function CheckMailAddress($address) {
  $atext = "[a-z0-9\!\#\$\%\&\'\*\+\-\/\=\?\^\_\`\{\|\}\~]";
  $atom = "$atext+(\.$atext+)*";
  $dtext = "[a-z0-9\č\ď\ň\ř\š\ť\ž\á\é\ě\í\ó\ů\ú\ý\Č\Ď\Ň\Ř\Š\Ť\Ž\Á\É\Ě\Í\Ó\Ú\Ý\ä\ë\ü\Ä\Ë\Ü\ß]";
  $dpart = "$dtext+(-$dtext+)*";
  $domain = "$dpart+(\.$dpart+)*\.[a-z]{2,6}$";

//  if(preg_match("/^$atom@$domain$/i", $address)) {
//    list($username, $host)=split('@', $address);
//    if(checkdnsrr($host,'MX')) { return TRUE; }
//  }

  if(preg_match("/^$atom@$domain$/i", $address)) { return TRUE; }

  return FALSE;
}
?>

Martin:

Dobrý den! Předem bych se chtěl omluvit - jsem v této problematice začátečník. Mám problém s tím, že když mi na email přijde vygenerovaný odkaz, znak = je nahrazen znakem ? a ještě navíc za rovnítkem jsou vynechané dva následující znaky... Nemáte někdo tušení, čím by to mohlo být? Předem děkuji za odpovědi.

Harold:

Ahoj, nechce mi to fungovať, malo by to fungovať aj na locale a aj na servery? mne to nejde na locale, viete niekto o nejakom celom skripte, ktorý to overí? ďakujem.

Neviem:

A ako sa kontroluje http adresa? napr. uzivatel ma zadan adresu aj z http:// a tato adresa napr. neexistuje tak mu napise ze Neplatna adresa?

ikona Jakub Vrána OpenID:

Dva tipy na toto téma jsou v mé knize: http://php.vrana.cz/hledat.php?search=https%3F#kniha

Mirek:

Jak je to s tím, že doména může přijímat poštu i bez MX záznamu? Teď mě třeba konkrétně zajímá email cv@trabj.net, na který lze poštu poslat (bez chybové hlášky), ale pro tuto doménu jsem nenalezl žádný záznam v DNS, pouze, že je vedena na dvou DNS serverech

Michal Špaček:

Bohužel, daná adresa už vrací chybovou hlášku při pokusu o odeslání mailu.

Nicméně, pokud neexistuje žádný MX záznam, tak se posílá na A záznam. Viz první odstavec kapitoly 5. Address Resolution and Mail Handling z RFC 2821 http://rfc-ref.org/RFC-TEXTS/2821/chapter5.html

ikona Jan Tojnar:

První odkaz už nefunguje. Třeba změnit na http://www.bobocop.cz/blog/0505archiv.php#n143580

Martin Lonský:

Opět úvahy nad nesmrtelností brouka :-) Už jsem řekl hodně lidem, aby nedělali něco, co už někdo udělal, s vizí, že to snad udělají lepší. Pravděpodobně NE?! Zend_Validate_Emailaddress

ikona Jakub Vrána OpenID:

Možná sis nevšiml data vydání článku. V té době Zendí implementace vypadala takto:

<?php

   
/**
     * Returns value if it is a valid email format, FALSE otherwise.
     *
     * @param mixed $value
     * @return mixed
     */
    public static function isEmail($value)
    {
        /**
         * @todo RFC 2822 (http://www.ietf.org/rfc/rfc2822.txt)
         */
    }

?>

Martin Lonský:

Pravda, tím se takový článek vysvětluje. my fault

František Musil:

Vysledkem takovych uvah je spousta programatoru na baterky, kteri umi jen ctrl c a ctrl v...

Tomáš Benda:

Nebylo by lepší na validaci emailové adresy používat toto?
if(!filter_var($email, FILTER_VALIDATE_EMAIL)){
  echo "E-mail is not valid";
}
else{
  echo "E-mail is valid";
}
?>

ikona Jakub Vrána OpenID:

Viz http://php.vrana.cz/kontrola-e-mailove-adresy.php#d-13502.

Martin Lonský:

(PHP 5 >= 5.2.0)

Milsa (msx):

Dovolil som si upraviť prvú funkciu s použitím preg_match():
http://milsa.info/programovanie/internet/…-preg-match

Michal Jirát:

Neuměl byste poradit, jak upravit script tak, aby správně otestovat příjemce na volny.cz? Mě to vždy vrací chybu 450 4.7.1 Client host rejected: cannot find your hostname  na dotaz RCPT TO, pro jakoukoliv (i zaručeně platnou) adresu končící na @volny.cz a ze všech v DNS uvedených MX serverů. Přitom na této stránce http://verify-email.org/ se to otestuje správně.
Předem děkuji.

nechapu:

Jako je to asi hezký... ale jak se to používá?

nechapu:

hm aha eregi už se od php 5.3 nepoužívá nebo co. skvělej aktualizovanej článek.

ikona Jakub Vrána OpenID:

eregi() je pouze zastaralé, stále funguje.

Vrrrhafhaf:

Já používám řešení, které se obejde bez dodatečné tabulky "emaily" a jejích atributů ID a rand_chars. K tomu mi slouží pouze MD5 hash a ostatní údaje o uživateli jehož jedinečný email chci ověřit.

Tedy třeba takto ID = MD5($username), rand_chars = MD5($email.$username.$userId). Tyto dva údaje pošlu v URL a pravost ověřím SQL dotazem pomocí funkcí CONCAT a MD5.

SELECT  id FROM user WHERE MD5(CONCAT(email,username,userId)) = ".$POST['rand_chars']." AND WHERE MD5(username) = ".$post['id']."

ikona Jakub Vrána OpenID:

Jedná se o Security Through Obscurity. Pokud někdo zjistí, jak se hash počítá, tak si může nechat potvrdit jakýkoliv mail. Tomuto řešení je vhodné se vyhnout.

Ostatně ani řešení popsané v článku není ideální, protože pokud se někdo dostane k obsahu databáze, tak může nechat potvrdit všechny nepotvrzené e-maily. Upravil jsem to tak, aby to nešlo (zahašováním řetězce uloženého v databázi).

v6ak:

To řešení má více vad. Nakousnul to tu Jakub.

0. Předpokládejme, že se zbavíme problému SbO, o kterém se zmiňoval Jakub, tím, že budeme mít nějaký tajný klíč pro tento účel (v rámci aplikace pro všechny uživatele stejný), třeba $secret = '7eATIE/MQcOfV4PPvDsytQ'. Počítali bychom potom md5('7eATIE/MQcOfV4PPvDsytQ'.$email.$username.$userId).

1. Podobné věci mohou být velmi snadno náchylné na tzv. length extension attack. Obecně na to bacha při pokusech generování různých ověřovacích hashů. Z jednoho ověřovacího hashe si potom může do jisté míry vypočítat jiný. Je dokonce rozdíl mezi hash($secret.$message) a hash($message.$secret). Viz https://en.wikipedia.org/wiki/Length_extension_attack . Většinou se vyplatí použít hmac, kde se (nejen) tento problém řeší.

2. Plaintext injection. Protože "foo@example.co"."martina" dá totéž co "foo@example.com"."artina". Místo $email.$username.$userId by bylo lepší mít implode('|', array_map('base64_encode', [$email, $username, $userId]));

3. Last but not least, zmíněný kód je náchylný na SQL injection a to i s magic_quotes_gpc = on. Naštěstí to ale vypadá, že nejde o používaný kód, protože by pak nemohl fungovat.

v6ak:

4. Plus bych ještě do toho řetězce asi přidal čas expirace (ten by se pak musel přenášet i zvlášť, jinak bychom nic neověřili).

ikona Jakub Vrána OpenID:

Přidání $secret považuji za polovičaté řešení. Musí to být někde uloženo a pokud se k tomu někdo dostane (můj oblíbený příklad je bývalý nespokojený zaměstnanec), tak je řešení nefunkční. Uložení haše náhodně vygenerovaného řetězce tímto problémem netrpí, takže nevidím důvod, proč vymýšlet něco jiného.

v6ak:

Pokud budeme mít i expiraci, není problém měnit $secret třeba jednou za hodinu. Jinak výhody - mělo to být lepší škálování (nepotřebuje ukládání do centrální DB a vyhnu se tím dilematu s CAP teorémem), ale je otázka, jestli si tím opravdu v praxi pomůžu, když vedle řeším centrální tabulku uživatelů. Pokud to tuto výhodu skutečně přinese (asi záleží dost na detailech), bude se to týkat spíš u hodně používaných aplikací.

Asi je tedy fakt, že na začátek je ta tabulka většinou ideální řešení. Je tam relativně těžké udělat chybu (typu hash($secret.$message)), pokud nedělám vyloženě kraviny (typu SQL injection). Jakmile začne být škálování problém, potom může mít smysl uvažovat o HMAC apod. Pokud to v konkrétní aplikaci bude vypadat jako dobrý nápad, bude stát za to, aby to implementovat nebo aspoň zkontroloval člověk, který se v tom opravdu vyzná a pořeší detaily.

kar5el:

Ahoj, eregi je zastaralé a v php 7 odebrané, jak to mám udělat v preg_match?

Patrik:

<?php

function check_email($email) {
   
$user = '[-a-z0-9!#$%&\'*+/=?^_`{|}~]';
   
$domain = '[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])';
    return
preg_match("(^$user+(\.$user+)*@($domain?\.)+$domain\$)i", $email);
}

?>

Diskuse je zrušena z důvodu spamu.

avatar © 2005-2024 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.