From 6860a1da74396fa68cf53fd1006a03fb41cbca15 Mon Sep 17 00:00:00 2001
From: thatside <bturchik@zaraffasoft.com>
Date: Mon, 7 Aug 2017 17:52:13 +0300
Subject: [PATCH 1/4] Implemented cookie protection - some spambots do not use
 cookies at all

---
 DependencyInjection/Configuration.php         | 11 +++
 .../IsometriksSpamExtension.php               | 17 ++++
 EventListener/KernelResponseListener.php      | 35 ++++++++
 .../CookieValidationListener.php              | 64 +++++++++++++++
 .../Spam/Provider/CookieProvider.php          | 36 ++++++++
 .../Spam/Type/FormTypeCookieExtension.php     | 82 +++++++++++++++++++
 Resources/config/cookie.xml                   | 32 ++++++++
 7 files changed, 277 insertions(+)
 create mode 100644 EventListener/KernelResponseListener.php
 create mode 100644 Form/Extension/Spam/EventListener/CookieValidationListener.php
 create mode 100644 Form/Extension/Spam/Provider/CookieProvider.php
 create mode 100644 Form/Extension/Spam/Type/FormTypeCookieExtension.php
 create mode 100644 Resources/config/cookie.xml

diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php
index 98b4c64..444cbc9 100644
--- a/DependencyInjection/Configuration.php
+++ b/DependencyInjection/Configuration.php
@@ -44,6 +44,17 @@ public function getConfigTreeBuilder()
                             ->defaultValue('Form fields are invalid')->end()
                     ->end()
                 ->end()
+            
+            
+                ->arrayNode('cookie')
+                    ->canBeDisabled()
+                    ->children()
+                        ->scalarNode('name')->defaultValue('antispam')->end()
+                        ->booleanNode('global')->defaultFalse()->end()
+                        ->scalarNode('message')
+                            ->defaultValue('Something is wrong, please try again')->end()
+                    ->end()
+                ->end()
             ->end();
 
         return $treeBuilder;
diff --git a/DependencyInjection/IsometriksSpamExtension.php b/DependencyInjection/IsometriksSpamExtension.php
index b2ad811..3783608 100644
--- a/DependencyInjection/IsometriksSpamExtension.php
+++ b/DependencyInjection/IsometriksSpamExtension.php
@@ -25,6 +25,7 @@ public function load(array $configs, ContainerBuilder $container)
 
         $this->processTimedConfig($config['timed'], $container, $loader);
         $this->processHoneypotConfig($config['honeypot'], $container, $loader);
+        $this->processCookieConfig($config['cookie'], $container, $loader);
     }
 
     private function processTimedConfig(array $config, ContainerBuilder $container, XmlFileLoader $loader)
@@ -61,4 +62,20 @@ private function processHoneypotConfig(array $config, ContainerBuilder $containe
             'message' => $config['message'],
         ));
     }
+
+    private function processCookieConfig(array $config, ContainerBuilder $container, XmlFileLoader $loader)
+    {
+        if (!$this->isConfigEnabled($container, $config)) {
+            return;
+        }
+
+        $loader->load('cookie.xml');
+
+        $definition = $container->getDefinition('isometriks_spam.form.extension.type.cookie');
+        $definition->addArgument(array(
+            'name' => $config['name'],
+            'global' => $config['global'],
+            'message' => $config['message'],
+        ));
+    }
 }
diff --git a/EventListener/KernelResponseListener.php b/EventListener/KernelResponseListener.php
new file mode 100644
index 0000000..5976a25
--- /dev/null
+++ b/EventListener/KernelResponseListener.php
@@ -0,0 +1,35 @@
+<?php
+
+
+namespace Isometriks\Bundle\SpamBundle\EventListener;
+
+use Isometriks\Bundle\SpamBundle\Form\Extension\Spam\Provider\CookieProvider;
+use Symfony\Component\HttpFoundation\Cookie;
+use Symfony\Component\HttpFoundation\Session\Session;
+use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
+
+class KernelResponseListener
+{
+    /** @var CookieProvider */
+    private $cookieProvider;
+    
+    public function __construct($cookieProvider)
+    {
+        $this->cookieProvider = $cookieProvider;
+    }
+
+    public function onKernelResponse(FilterResponseEvent $responseEvent)
+    {
+        $cookieData = $this->cookieProvider->getCookieSettings();
+        if ($cookieData) {
+            if($cookieData['mode'] == 'add') {
+                $cookie = new Cookie($cookieData['name'], 1);
+                
+                $responseEvent->getResponse()->headers->setCookie($cookie);
+            } else if ($cookieData['mode'] == 'remove') {
+                $responseEvent->getResponse()->headers->removeCookie($cookieData['name']);
+                $this->cookieProvider->removeCookieSettings();
+            }
+        }        
+    }
+}
\ No newline at end of file
diff --git a/Form/Extension/Spam/EventListener/CookieValidationListener.php b/Form/Extension/Spam/EventListener/CookieValidationListener.php
new file mode 100644
index 0000000..8fc59bc
--- /dev/null
+++ b/Form/Extension/Spam/EventListener/CookieValidationListener.php
@@ -0,0 +1,64 @@
+<?php
+
+
+namespace Isometriks\Bundle\SpamBundle\Form\Extension\Spam\EventListener;
+
+use Isometriks\Bundle\SpamBundle\Form\Extension\Spam\Provider\CookieProvider;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\Form\FormError;
+use Symfony\Component\Form\FormEvent;
+use Symfony\Component\Form\FormEvents;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Translation\TranslatorInterface;
+
+class CookieValidationListener implements EventSubscriberInterface
+{
+    private $cookieProvider;
+    private $request;
+    private $translator;
+    private $translationDomain;
+    private $cookieName;
+    private $errorMessage;
+
+    public function __construct(CookieProvider $cookieProvider,
+                                Request $request,
+                                TranslatorInterface $translator,
+                                $translationDomain,
+                                $cookieName,
+                                $errorMessage)
+    {
+        $this->cookieProvider = $cookieProvider;
+        $this->request = $request;
+        $this->translator = $translator;
+        $this->translationDomain = $translationDomain;
+        $this->cookieName = $cookieName;
+        $this->errorMessage = $errorMessage;
+    }
+
+    public function preSubmit(FormEvent $event)
+    {
+        $form = $event->getForm();
+        
+        if($form->isRoot() && $form->getConfig()->getOption('compound') && !$this->request->cookies->get($this->cookieName)) {
+            $errorMessage = $this->errorMessage;
+
+            if (null !== $this->translator) {
+                $errorMessage = $this->translator->trans($errorMessage, array(), $this->translationDomain);
+            }
+
+            $form->addError(new FormError($errorMessage));
+            
+            return;
+        }
+        
+        $this->cookieProvider->removeAntispamCookie($this->cookieName);
+    }  
+    
+
+    public static function getSubscribedEvents()
+    {
+        return array(
+            FormEvents::PRE_SUBMIT => 'preSubmit',
+        );
+    }
+}
\ No newline at end of file
diff --git a/Form/Extension/Spam/Provider/CookieProvider.php b/Form/Extension/Spam/Provider/CookieProvider.php
new file mode 100644
index 0000000..01232cc
--- /dev/null
+++ b/Form/Extension/Spam/Provider/CookieProvider.php
@@ -0,0 +1,36 @@
+<?php
+
+
+namespace Isometriks\Bundle\SpamBundle\Form\Extension\Spam\Provider;
+
+use Symfony\Component\HttpFoundation\Session\Session;
+
+class CookieProvider
+{
+    private $session;
+    
+    public function __construct(Session $session)
+    {
+        $this->session = $session;
+    }
+    
+    public function setAntispamCookie($cookieName)
+    {
+        $this->session->set('antispam_cookie', array('mode' => 'add', 'name' => $cookieName));
+    }
+
+    public function removeAntispamCookie($cookieName)
+    {
+        $this->session->set('antispam_cookie', array('mode' => 'remove', 'name' => $cookieName));        
+    }
+
+    public function getCookieSettings()
+    {
+        return $this->session->get('antispam_cookie');
+    }
+
+    public function removeCookieSettings()
+    {
+        $this->session->remove('antispam_cookie');   
+    }
+}
\ No newline at end of file
diff --git a/Form/Extension/Spam/Type/FormTypeCookieExtension.php b/Form/Extension/Spam/Type/FormTypeCookieExtension.php
new file mode 100644
index 0000000..b2f1f40
--- /dev/null
+++ b/Form/Extension/Spam/Type/FormTypeCookieExtension.php
@@ -0,0 +1,82 @@
+<?php
+
+
+namespace Isometriks\Bundle\SpamBundle\Form\Extension\Spam\Type;
+
+use Isometriks\Bundle\SpamBundle\Form\Extension\Spam\EventListener\CookieValidationListener;
+use Isometriks\Bundle\SpamBundle\Form\Extension\Spam\Provider\CookieProvider;
+use Symfony\Component\Form\AbstractTypeExtension;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\Form\FormInterface;
+use Symfony\Component\Form\FormView;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Session\Session;
+use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Component\OptionsResolver\OptionsResolverInterface;
+use Symfony\Component\Translation\TranslatorInterface;
+
+class FormTypeCookieExtension extends AbstractTypeExtension
+{
+    private $cookieProvider;
+    private $request;
+    private $session;
+    private $translator;
+    private $translationDomain;
+    private $defaults;
+
+    public function __construct(CookieProvider $cookieProvider,
+                                Request $request,
+                                Session $session, 
+                                TranslatorInterface $translator,
+                                $translationDomain,
+                                array $defaults)
+    {
+        $this->cookieProvider = $cookieProvider;
+        $this->request = $request;
+        $this->session = $session;
+        $this->translator = $translator;
+        $this->translationDomain = $translationDomain;
+        $this->defaults = $defaults;
+    }
+    
+    public function buildForm(FormBuilderInterface $builder, array $options)
+    {
+        if (!$options['cookie']) {
+            return;
+        }
+
+        $builder
+            ->addEventSubscriber(new CookieValidationListener(
+                $this->cookieProvider,
+                $this->request,
+                $this->translator,
+                $this->translationDomain,
+                $options['cookie_name'],
+                $options['cookie_message']
+            ));
+    }
+
+    public function finishView(FormView $view, FormInterface $form, array $options)
+    {
+        $this->cookieProvider->setAntispamCookie($options['cookie_name']);
+    }
+
+    public function setDefaultOptions(OptionsResolverInterface $resolver)
+    {
+        $this->configureOptions($resolver);
+    }
+
+    public function configureOptions(OptionsResolver $resolver)
+    {
+        $resolver->setDefaults(array(
+            'cookie' => $this->defaults['global'],
+            'cookie_name' => $this->defaults['name'],
+            'cookie_message' => $this->defaults['message'],
+        ));
+    }
+
+    public function getExtendedType()
+    {
+        return 'form';
+    }
+}
\ No newline at end of file
diff --git a/Resources/config/cookie.xml b/Resources/config/cookie.xml
new file mode 100644
index 0000000..40125cc
--- /dev/null
+++ b/Resources/config/cookie.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" ?>
+
+<container xmlns="http://symfony.com/schema/dic/services"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
+
+    <parameters>
+        <parameter key="isometriks_spam.form.extension.type.cookie.class">Isometriks\Bundle\SpamBundle\Form\Extension\Spam\Type\FormTypeCookieExtension</parameter>
+        <parameter key="isometriks_spam.form.extension.provider.cookie.class">Isometriks\Bundle\SpamBundle\Form\Extension\Spam\Provider\CookieProvider</parameter>
+        <parameter key="isometriks_spam.event_listener.kernel_response.class">Isometriks\Bundle\SpamBundle\EventListener\KernelResponseListener</parameter>
+    </parameters>
+
+    <services>
+        <service id="isometriks_spam.form.extension.provider.cookie" class="%isometriks_spam.form.extension.provider.cookie.class%">
+            <argument type="service" id="session" />
+        </service>
+        
+        <service id="isometriks_spam.form.extension.type.cookie" class="%isometriks_spam.form.extension.type.cookie.class%" scope="request">
+            <tag name="form.type_extension" alias="form" />
+            <argument type="service" id="isometriks_spam.form.extension.provider.cookie" />
+            <argument type="service" id="request" />
+            <argument type="service" id="session" />
+            <argument type="service" id="translator.default" />
+            <argument>%validator.translation_domain%</argument>
+        </service>
+        
+        <service id="isometriks_spam.exception_listener" class="%isometriks_spam.event_listener.kernel_response.class%">
+            <tag name="kernel.event_listener" event="kernel.response" method="onKernelResponse" />
+            <argument type="service" id="isometriks_spam.form.extension.provider.cookie" />
+        </service>
+    </services>
+</container>

From 8e0311eb1fbde3d5fb4c59706e315d698a84bd28 Mon Sep 17 00:00:00 2001
From: thatside <bturchik@zaraffasoft.com>
Date: Tue, 8 Aug 2017 11:31:25 +0300
Subject: [PATCH 2/4] Clearing cookie after protection

---
 EventListener/KernelResponseListener.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/EventListener/KernelResponseListener.php b/EventListener/KernelResponseListener.php
index 5976a25..df2a531 100644
--- a/EventListener/KernelResponseListener.php
+++ b/EventListener/KernelResponseListener.php
@@ -5,7 +5,6 @@
 
 use Isometriks\Bundle\SpamBundle\Form\Extension\Spam\Provider\CookieProvider;
 use Symfony\Component\HttpFoundation\Cookie;
-use Symfony\Component\HttpFoundation\Session\Session;
 use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
 
 class KernelResponseListener
@@ -28,6 +27,7 @@ public function onKernelResponse(FilterResponseEvent $responseEvent)
                 $responseEvent->getResponse()->headers->setCookie($cookie);
             } else if ($cookieData['mode'] == 'remove') {
                 $responseEvent->getResponse()->headers->removeCookie($cookieData['name']);
+                $responseEvent->getResponse()->headers->clearCookie($cookieData['name']);
                 $this->cookieProvider->removeCookieSettings();
             }
         }        

From d7328eff89643d0b83269801c39295ca50142651 Mon Sep 17 00:00:00 2001
From: thatside <bturchik@zaraffasoft.com>
Date: Wed, 11 Oct 2017 16:29:04 +0300
Subject: [PATCH 3/4] Updated cookie protection to work with Symfony 3

---
 .../Spam/Type/FormTypeCookieExtension.php     | 27 ++++++++++---------
 .../Spam/Type/FormTypeHoneypotExtension.php   | 12 ++++-----
 .../Spam/Type/FormTypeTimedSpamExtension.php  | 13 ++++-----
 Resources/config/cookie.xml                   | 12 ++++-----
 Resources/config/honeypot.xml                 |  2 +-
 Resources/config/timed.xml                    |  4 +--
 6 files changed, 36 insertions(+), 34 deletions(-)

diff --git a/Form/Extension/Spam/Type/FormTypeCookieExtension.php b/Form/Extension/Spam/Type/FormTypeCookieExtension.php
index b2f1f40..12ea1cf 100644
--- a/Form/Extension/Spam/Type/FormTypeCookieExtension.php
+++ b/Form/Extension/Spam/Type/FormTypeCookieExtension.php
@@ -6,13 +6,13 @@
 use Isometriks\Bundle\SpamBundle\Form\Extension\Spam\EventListener\CookieValidationListener;
 use Isometriks\Bundle\SpamBundle\Form\Extension\Spam\Provider\CookieProvider;
 use Symfony\Component\Form\AbstractTypeExtension;
+use Symfony\Component\Form\Extension\Core\Type\FormType;
 use Symfony\Component\Form\FormBuilderInterface;
 use Symfony\Component\Form\FormInterface;
 use Symfony\Component\Form\FormView;
-use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
 use Symfony\Component\HttpFoundation\Session\Session;
 use Symfony\Component\OptionsResolver\OptionsResolver;
-use Symfony\Component\OptionsResolver\OptionsResolverInterface;
 use Symfony\Component\Translation\TranslatorInterface;
 
 class FormTypeCookieExtension extends AbstractTypeExtension
@@ -24,21 +24,22 @@ class FormTypeCookieExtension extends AbstractTypeExtension
     private $translationDomain;
     private $defaults;
 
-    public function __construct(CookieProvider $cookieProvider,
-                                Request $request,
-                                Session $session, 
-                                TranslatorInterface $translator,
-                                $translationDomain,
-                                array $defaults)
-    {
+    public function __construct(
+        CookieProvider $cookieProvider,
+        RequestStack $requestStack,
+        Session $session,
+        TranslatorInterface $translator,
+        $translationDomain,
+        array $defaults
+    ) {
         $this->cookieProvider = $cookieProvider;
-        $this->request = $request;
+        $this->request = $requestStack->getCurrentRequest();
         $this->session = $session;
         $this->translator = $translator;
         $this->translationDomain = $translationDomain;
         $this->defaults = $defaults;
     }
-    
+
     public function buildForm(FormBuilderInterface $builder, array $options)
     {
         if (!$options['cookie']) {
@@ -61,7 +62,7 @@ public function finishView(FormView $view, FormInterface $form, array $options)
         $this->cookieProvider->setAntispamCookie($options['cookie_name']);
     }
 
-    public function setDefaultOptions(OptionsResolverInterface $resolver)
+    public function setDefaultOptions(OptionsResolver $resolver)
     {
         $this->configureOptions($resolver);
     }
@@ -77,6 +78,6 @@ public function configureOptions(OptionsResolver $resolver)
 
     public function getExtendedType()
     {
-        return 'form';
+        return FormType::class;
     }
 }
\ No newline at end of file
diff --git a/Form/Extension/Spam/Type/FormTypeHoneypotExtension.php b/Form/Extension/Spam/Type/FormTypeHoneypotExtension.php
index 378eab1..1318171 100644
--- a/Form/Extension/Spam/Type/FormTypeHoneypotExtension.php
+++ b/Form/Extension/Spam/Type/FormTypeHoneypotExtension.php
@@ -10,7 +10,6 @@
 use Symfony\Component\Form\FormInterface;
 use Symfony\Component\Form\FormView;
 use Symfony\Component\OptionsResolver\OptionsResolver;
-use Symfony\Component\OptionsResolver\OptionsResolverInterface;
 use Symfony\Component\Translation\TranslatorInterface;
 
 class FormTypeHoneypotExtension extends AbstractTypeExtension
@@ -19,10 +18,11 @@ class FormTypeHoneypotExtension extends AbstractTypeExtension
     private $translationDomain;
     private $defaults;
 
-    public function __construct(TranslatorInterface $translator,
-                                $translationDomain,
-                                array $defaults)
-    {
+    public function __construct(
+        TranslatorInterface $translator,
+        $translationDomain,
+        array $defaults
+    ) {
         $this->translator = $translator;
         $this->translationDomain = $translationDomain;
         $this->defaults = $defaults;
@@ -74,7 +74,7 @@ public function finishView(FormView $view, FormInterface $form, array $options)
         }
     }
 
-    public function setDefaultOptions(OptionsResolverInterface $resolver)
+    public function setDefaultOptions(OptionsResolver $resolver)
     {
         $this->configureOptions($resolver);
     }
diff --git a/Form/Extension/Spam/Type/FormTypeTimedSpamExtension.php b/Form/Extension/Spam/Type/FormTypeTimedSpamExtension.php
index 05eeb11..92b73bd 100644
--- a/Form/Extension/Spam/Type/FormTypeTimedSpamExtension.php
+++ b/Form/Extension/Spam/Type/FormTypeTimedSpamExtension.php
@@ -20,11 +20,12 @@ class FormTypeTimedSpamExtension extends AbstractTypeExtension
     private $translationDomain;
     private $defaults;
 
-    public function __construct(TimedSpamProviderInterface $timeProvider,
-                                TranslatorInterface $translator,
-                                $translationDomain,
-                                array $defaults)
-    {
+    public function __construct(
+        TimedSpamProviderInterface $timeProvider,
+        TranslatorInterface $translator,
+        $translationDomain,
+        array $defaults
+    ) {
         $this->timeProvider = $timeProvider;
         $this->translator = $translator;
         $this->translationDomain = $translationDomain;
@@ -59,7 +60,7 @@ public function finishView(FormView $view, FormInterface $form, array $options)
         }
     }
 
-    public function setDefaultOptions(OptionsResolverInterface $resolver)
+    public function setDefaultOptions(OptionsResolver $resolver)
     {
         $this->configureOptions($resolver);
     }
diff --git a/Resources/config/cookie.xml b/Resources/config/cookie.xml
index 40125cc..d8b444f 100644
--- a/Resources/config/cookie.xml
+++ b/Resources/config/cookie.xml
@@ -14,16 +14,16 @@
         <service id="isometriks_spam.form.extension.provider.cookie" class="%isometriks_spam.form.extension.provider.cookie.class%">
             <argument type="service" id="session" />
         </service>
-        
-        <service id="isometriks_spam.form.extension.type.cookie" class="%isometriks_spam.form.extension.type.cookie.class%" scope="request">
-            <tag name="form.type_extension" alias="form" />
+
+        <service id="isometriks_spam.form.extension.type.cookie" class="%isometriks_spam.form.extension.type.cookie.class%" public="true">
+            <tag name="form.type_extension" extended-type="Symfony\Component\Form\Extension\Core\Type\FormType" alias="form" />
             <argument type="service" id="isometriks_spam.form.extension.provider.cookie" />
-            <argument type="service" id="request" />
+            <argument type="service" id="request_stack" />
             <argument type="service" id="session" />
-            <argument type="service" id="translator.default" />
+            <argument type="service" id="translator" />
             <argument>%validator.translation_domain%</argument>
         </service>
-        
+
         <service id="isometriks_spam.exception_listener" class="%isometriks_spam.event_listener.kernel_response.class%">
             <tag name="kernel.event_listener" event="kernel.response" method="onKernelResponse" />
             <argument type="service" id="isometriks_spam.form.extension.provider.cookie" />
diff --git a/Resources/config/honeypot.xml b/Resources/config/honeypot.xml
index aa51b12..801881b 100644
--- a/Resources/config/honeypot.xml
+++ b/Resources/config/honeypot.xml
@@ -9,7 +9,7 @@
     </parameters>
 
     <services>
-        <service id="isometriks_spam.form.extension.type.honeypot" class="%isometriks_spam.form.extension.type.honeypot.class%">
+        <service id="isometriks_spam.form.extension.type.honeypot" class="%isometriks_spam.form.extension.type.honeypot.class%" public="true">
             <tag name="form.type_extension" extended-type="Symfony\Component\Form\Extension\Core\Type\FormType" />
             <argument type="service" id="translator" />
             <argument>%validator.translation_domain%</argument>
diff --git a/Resources/config/timed.xml b/Resources/config/timed.xml
index 1fb6518..d95c0c3 100644
--- a/Resources/config/timed.xml
+++ b/Resources/config/timed.xml
@@ -14,8 +14,8 @@
             <argument type="service" id="session" />
         </service>
 
-        <service id="isometriks_spam.form.extension.type.timed_spam" class="%isometriks_spam.form.extension.type.timed_spam.class%">
-            <tag name="form.type_extension" extended-type="Symfony\Component\Form\Extension\Core\Type\FormType" />
+        <service id="isometriks_spam.form.extension.type.timed_spam" class="%isometriks_spam.form.extension.type.timed_spam.class%" public="true">
+            <tag name="form.type_extension" extended-type="Symfony\Component\Form\Extension\Core\Type\FormType" alias="form" />
             <argument type="service" id="isometriks_spam.form.extension.provider.timed_spam" />
             <argument type="service" id="translator" />
             <argument>%validator.translation_domain%</argument>

From 39771181b757ca60d41306d8334cb838ef7805b9 Mon Sep 17 00:00:00 2001
From: thatside <bturchik@zaraffasoft.com>
Date: Wed, 11 Oct 2017 16:39:24 +0300
Subject: [PATCH 4/4] Updated README

---
 README.md | 33 +++++++++++++++++++++++++++++++--
 1 file changed, 31 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index e318022..7828896 100644
--- a/README.md
+++ b/README.md
@@ -89,7 +89,7 @@ If the field is filled out, then the form is invalid. You can optionally
 choose to use a class name to hide the form element as well in case the
 bot tries to check the style attribute.
 
-```yml
+```YAML
 isometriks_spam:
     honeypot:
         field: email_address
@@ -123,9 +123,38 @@ public function configureOptions(OptionsResolver $resolver)
 }
 ```
 
-### Twig Form Error message rendering
+### Cookie Spam Prevention
+
+Most of spam bots can't use cookies so we could check cookie value on each request.
+
+If there is no cookie set - then the request is invalid and set by bots. 
+These cookies are not used for any tracking and are deleted after form is submitted and handled.
+
+Configuration:
 
+```YAML
+isometriks_spam:
+    cookie:
+        name: antispam_cookie
+        global: false
+        message: Something is wrong, please try again
 ```
+
+Usage:
+
+```php
+public function configureOptions(OptionsResolver $resolver)
+{
+    $resolver->setDefaults(array(
+        'cookie' => true,
+        // ...
+    ));
+}
+```
+
+### Twig Form Error message rendering
+
+```twig
 {% if form.vars.errors is not empty %}
     <div class="alert alert-danger has-error">{{ form_errors(form) }}</div>
 {% endif %}