Ochrana formulářů proti spamu + řešení kontaktního formuláře

Minule jsem vám prozradil, jak ochránit kontaktní formulář přes posíláním jednoho e-mailu na několik adres a dnes si povíme, jak od tohoto formuláře odlákat roboty úplně. V druhé části článku dokonce naleznete hotové řešení dokonalého kontaktního formuláře !

Byl jednou jeden robot

Robota si můžete představit jako aplikaci nebo nějaký skript, který projíždí stránky a slídí, kde by našel nějaký formulář a mohl ho zběsile vyplnit nějakou reklamou a odeslat. Přičemž si prohlíží HTML stránky čistě textově – nezobrazuje si grafiku nebo kaskádové styly, jenom kouká na HTML tagy a jakmile uvidí <input type="text" name="email" />, tak mu začnou kapat sliny. Ihned vyplní všechny pole formuláře a zmáčkne všechny tlačítka, které odesílají formulář, aby měl jistotu, že nedal třeba jenom náhled.

Takto jednoduše si lze představit robota – bohužel, tyto potvůrky se neustále vylepšují, takže některé si dokonce poradí s obrázkem, ze kterého musíte opsat znaky – tzv. CAPTCHA. I když vytvoříte speciální pole jenom pro roboty a skryjete ho pomocí CSS v domnění, že bude robot tak hloupý a vyplní ho taky, i zde můžete narazit. Avšak nepropadejme panice, řešení existuje !

Jak správně naklást pasti

Kontrolní otázka

Dobře nachytat robota lze na kontrolní otázce, na kterou nemá šanci znát odpověď. Může to být matematický příklad (3 + 4), doplnění do věty (skákal ___ přes oves) a podobně. Tato metoda je účinná, avšak obtěžuje uživatele – musí kvůli nám přemýšlet.

Captcha

I když jsem mluvil o tom, že někteří roboti opravdu umí rozpoznat tyto obrázky, není jich tolik, a proto se dá využít i toto řešení. Já bych ho však nedoporučoval – rozhodně není přístupné a znechucuje uživatele ještě více než kontrolní otázka. Já je přímo nesnáším – někdy jsou tak šílené, že se na ten obrázek opravdu musíte dívat nejmíň půl minuty, abyste odtušili nakreslené znaky.

Náhled

Na tomto blogu mám u přidávání komentářů pouze tlačítko „Náhled“. Odeslat příspěvek lze tedy až poté, co si ho prohlédnete. Věřte nebo ne, pouze toto stačí k odstrašení robotů (a doufám, že mi to ještě nějakou dobu vydrží). Bohužel je takovéto řešení použitelné jen v některých případech – nejčastěji v diskusích.

JavaScript

I když ty potvůrky umí všelicos, JavaScript ještě ne :-) Tedy proč toho nevyužít – dělá se to tak, že pokud uživatel nemá JavaScript nebo ho má vypnutý, tak se standardně nabízí nějaká kontrolní otázka a pokud je JavaScript k dispozici, tak výsledek této otázky vloží do formuláře pomocí <input type="hidden" ... /> a otázka samotná se již nezobrazí a uživatele neobtěžuje.

Řešení kontaktního formuláře

Připravil jsem kontaktní formulář, který využívá poslední pasti na roboty a to JavaScriptu. Jelikož dneska letí XHTML, tak jsem si nemohl dovolit pouhé document.write, jelikož to specifikace nedovoluje. Proto je vkládání JavaScriptem řešeno elegantněji pomocí DOM.

Kdyby se vám ve formuláři pozastavoval zrak nad tagem <label>, tak vězte, že to je svázání popisku s příslušným polem. Po kliknutí na popisek se kurzor přemístí do pole, které k tomuto popisku patří. Každý přístupný formulář musí tento tag obsahovat.

Celé řešení se skládá ze dvou souborů – samotný formulář a třída, která pracuje s daty, které jsou přes tento formulář odeslány.

napiste-nam.php

<?php
require_once "./MailForm.php";

$showForm = true;

if ( $_POST ) {
    $form = new MailForm( $_POST[ "email" ], $_POST[ "text" ], $_POST[ "spamCheck" ],
        $_POST[ "numbers" ] );

    echo "<p>";

    if ( $form->isValid( ) ) {
        $showForm = false;
        $form->sendMail( );
    }
    else {
        $form->printErrors( );
    }

    echo "</p>\n";
}

if ( $showForm ) {
    $first = rand( 0, 9 );
    $second = rand( 0, 9 );
?>
<h2>Napište nám</h2>

<form action="napiste-nam.php" method="post">
<div id="myform">
    <label>
        Váš e-mail:
        <input type="text" name="email" size="40"<?php
            if ( !empty( $_POST[ "email" ] ) ) {
                echo " value=\"".$_POST[ "email" ]."\"";
            } ?> />
    </label><br />
    (není povinný, vyplňte pokud chcete dostat odpověď)
    <br /><br />
    <label>
        Text e-mailu:<br />
        <textarea name="text" cols="42" rows="10"><?php
            if ( !empty( $_POST[ "text" ] ) ) {
                echo $_POST[ "text" ];
            }
        ?></textarea>
    </label>
    <br /><br />
    <noscript>
        <label title="Vyplňte výsledek součtu těchto dvou čísel (ochrana před roboty)">
            Kolik je <?php echo $first." + ".$second ?>:
            <input type="text" name="spamCheck" size="10" />
        </label>
        <br /><br />
    </noscript>
    <script type="text/javascript">
    /* <![CDATA[ */
        spamcheck = document.createElement( 'input' );
        spamcheck.setAttribute( 'type', 'hidden' );
        spamcheck.setAttribute( 'name', 'spamCheck' );
        spamcheck.setAttribute( 'value', <?php echo $first ?> + <?php echo $second ?> );
        document.getElementById( 'myform' ).appendChild( spamcheck );
    /* ]]> */
    </script>
    <input type="hidden" name="numbers" value="<?php echo $first.$second ?>" />
    <input type="submit" name="btn" value="Odeslat" />
</div>
</form>
<?php
}
?>

MailForm.php

<?php
class MailForm {
    private $mail;
    private $text;
    private $spamCheck;
    private $numbers;
    private $errors;

    public function __construct( $mail, $text, $spamCheck, $numbers ) {
        $this->mail = $mail;
        $this->text = $text;
        $this->spamCheck = $spamCheck;
        $this->numbers = $numbers;
        $errors = array();
    }

    public function isValid( ) {
        if ( empty( $this->mail ) ) {
            $this->mail = "robot@domena.cz";
        }
        elseif ( !ereg( "^[_a-zA-Z0-9\.\-]+@[_a-zA-Z0-9\.\-]+\.[a-zA-Z]{2,4}$",
                $this->mail ) ) {
            $this->errors[] = "E-mail byl vyplněn ve špatném formátu !";
        }

        if ( empty( $this->text ) ) {
            $this->errors[] = "Nebyl vyplněn text e-mailu !";
        }

        if ( empty( $this->spamCheck ) ) {
            $this->errors[] = "Nebyla vyplněna kontrolní otázka !";
        }
        elseif ( $this->spamCheck != ( $this->numbers[ 0 ] + $this->numbers[ 1 ] ) ) {
            $this->errors[] = "Nebyla správně zodpovězena kontrolní otázka !";
        }

        if ( empty( $this->errors ) ) {
            return true;
        }
        else {
            return false;
        }
    }

    public function sendMail( ) {
        $obsah = iconv( "utf-8", "iso-8859-2", $this->text );

        if ( mail( "info@domena.cz", "E-mail z webu", $obsah, "From: ".
                $this->mail."\nContent-Type: text/plain; charset=iso-8859-2\n" ) ) {
            echo "E-mail byl odeslán.";
        }
        else {
            echo "E-mail se nepodařilo odeslat !";
        }
    }

    public function printErrors( ) {
        for ( $i = 0; $i < count( $this->errors ); $i++ ) {
            echo $this->errors[ $i ]."<br />\n";
        }
    }
}
?>

Kódování e-mailu převádím z utf-8 na iso-8859–2, protože jsem zjistil, že některé webmaily si s unicode neporadí.

Vloženo: 20. 6. 2007 14.15RSS komentářů tohoto článku

Komentáře:

[1] Radek Tomášek 20. 6. 2007 14.42

Skvělý článek, díky.. Věřím, že se to bude čas od času hodit :)


[2] Petr Tichý 24. 6. 2007 08.52

Jsi si jistý, že používáš element „label“ správně?

Nic se s ním neobaluje, ale umísťuje se většinou nad políčko a musí obsahovat atribut „for“ s hodnotou stejnou jako má atribut „name“ u dotyčného popsaného pole.

Navíc se popisek píše klasicky mezi počáteční a koncovou značku. Nikoliv jako atribut title.

Jinak pěkný článek, ale slyšel jsem, že si někteří roboti stahují už vygenerovanou stránku včetně výpisů JS. Je to možné?


[3] Martin Grames (martin.grames@chapadlo.cz) 24. 6. 2007 12.20

[2] Petr Tichý: Ano, element label používám správně – má totiž dvojí použití:

  • s atributem for
<label for="jmeno">Jméno: </label><input id="jmeno" type="text" name="jmeno" />
  • bez atributu for
<label>Jméno: <input type="text" name="jmeno" /></label>

První verze se hodí spíše ke složitějším formulářům.

K druhému dotazu – je to možné, ale řešení JavaScriptem je zatím nejúčinnější.


[4] Petr Tichý 24. 6. 2007 13.55

[3] Martin Grames: To jsem nevěděl. Děkuji za informaci.


[5] dsm (dysmusax@centrum.cz) 21. 8. 2007 13.11

Co říkáte na tenhle způsob?

http://www.tutorialtastic.co.uk/…nd_protected


[6] Martin Grames (martin.grames@chapadlo.cz) 21. 8. 2007 17.29

[5] dsm: Tahle metoda mi příjde lepší, než testovat text na známé slova, které používají roboti. To pak příjdete zkrátka, i když budete do formuláře chtít napsat, že jsme včera hráli poker.


[7] noname 20. 4. 2008 01.32

Ahoj, co je v tom skriptu tohle: $this->mail = „robot@domena.cz“; tam mám zadat svůj mejl na kterej to chci ?


[8] Martin Grames (martin.grames@chapadlo.cz) 22. 5. 2008 12.57

[7] noname: Ne, to je e-mail odesílatele, který se použije pokud uživatel nevyplní svůj e-mail.

Adresa, kam se má e-mail posílat je uvedena v metodě sendMail.


[9] dd 1. 9. 2008 09.21

Jak by vypadal kod pro PHP 444? Nedari se mi ten zdrojak prepsat tak, aby fungoval. Diky


[9] dd: Pro PHP 4 stačí v definici proměnných nahradit private za var (private $mailvar $mail) a smazat klíčové slovo public před definicí funkcí (public function isValidfunction isValid). Nakonec je třeba přejmenovat konstruktor na jméno třídy, tedy __constructMailForm. Pokud budete i nadále tápat, zkuste použít nějaký nástroj na převod PHP 5 na PHP 4.


[11] Anonym 13. 12. 2008 16.03

Hezký článek, jsem si jistý že jej využiji


[12] Martin (martin.stanik@gmail.com) 10. 1. 2010 21.56

Dobrý script má jen jednu chybku nefunguje když je zpráva s diakritikou, nevíte v čem je chyba? Díky


[12] Martin: A máte skript uložen v kódování UTF-8 ?


[14] marek 24. 7. 2010 00.26

Úžasný script, už jej láduju na web :) Mám jeden malý dotaz – jak bych musel script upravit, aby pole email bylo povinné? Stačila by jen nějaká drobná úprava, nebo by bylo nutné script z větší části přepsat? Díky za odpověď


Přidat komentář