Pewnie nie jednemu programiście aplikacji webowych zdarzyło się, że po wystawieniu swojej księgi gości na świat, po paru dniach znajduje na niej kilkanaście spamowych wpisów. Ja również doświadczyłem tego ostatnio. Większość w takim przypadku decyduje się na zastosowanie CAPTCHA, czyli obrazka z zdeformowanym tekstem, który użytkownik musi podać przy dodawaniu posta. Jest to rozwiązanie średnio przyjemne, coraz mniej skuteczne i bardzo odstraszające potencjalnych gości, którzy są ludźmi. Postanowiłem więc napisać prosty filtr (w tym przypadku został on akurat wykorzystany jako validator w Symfony), który będzie wyszukiwał słowa, które na 99% znajdą się tylko w niechcianych wiadomościach.

Pewnie nie jednemu programiście aplikacji webowych zdarzyło się, że po wystawieniu swojej księgi gości na świat, po paru dniach znajduje na niej kilkanaście spamowych wpisów. Ja również doświadczyłem tego ostatnio. Większość w takim przypadku decyduje się na zastosowanie CAPTCHA, czyli obrazka z zdeformowanym tekstem, który użytkownik musi podać przy dodawaniu posta. Jest to rozwiązanie średnio przyjemne, coraz mniej skuteczne i bardzo odstraszające potencjalnych gości, którzy są ludźmi. Postanowiłem więc napisać prosty filtr (w tym przypadku został on akurat wykorzystany jako validator w Symfony), który będzie wyszukiwał słowa, które na 99% znajdą się tylko w niechcianych wiadomościach. Po stwierdzeniu przez validator, że wiadomość jest spamem, możemy wyświetlić komunikat, że odrzucamy post, albo zaserwować CAPTCHA – wtedy prawdopodobnie będziemy mieli do czynienia z automatem, więc jego CAPTCHA powinna odstraszyć, a jeśli validator niesłusznie sklasyfikował wiadomość od człowieka, to dajemy szansę na przepuszczenie jej. Idea jest więc prosta – żądaj wprowadzenia kodu z obrazka tylko wtedy, gdy jest podejrzenie, że treść którą właśnie wprowadzono do twojego formularza może być spamem.

Klasa validatora jest dość prosta, wymaga tylko pliku z zabronionymi słowami i progu tolerancji (u mnie próg 0.01 wystarcza w zupełności). Mój plik badwords.txt wygląda natomiast tak:

Viagra,Cailis,Xanax,meds,
sale,cheap,
fuck,sex,lesbian,gay,homosexual,chubby,

Poniżej kod klasy oraz przykład użycia. Kod jest na licencji beerware 🙂

Po stwierdzeniu przez validator, że wiadomość jest spamem, możemy wyświetlić komunikat, że odrzucamy post, albo zaserwować CAPTCHA – wtedy prawdopodobnie będziemy mieli do czynienia z automatem, więc jego CAPTCHA powinna odstraszyć, a jeśli validator niesłusznie sklasyfikował wiadomość od człowieka, to dajemy szansę na przepuszczenie jej. Idea jest więc prosta – żądaj wprowadzenia kodu z obrazka tylko wtedy, gdy jest podejrzenie, że treść którą właśnie wprowadzono do twojego formularza może być spamem.

Klasa validatora jest dość prosta, wymaga tylko pliku z zabronionymi słowami i progu tolerancji (u mnie próg 0.01 wystarcza w zupełności). Mój plik badwords.txt wygląda natomiast tak:

Viagra,Cailis,Xanax,meds,
sale,cheap,
fuck,sex,lesbian,gay,homosexual,chubby,

Poniżej kod klasy oraz przykład użycia. Kod jest na licencji beerware 🙂

 * @licence beerware
 */
class spamValidator
{
  /**
   * Próg tolerancji - obliczany jako liczba słow zabronionych występujących w sprawdzanej wartości  dzielona przez liczbę wszystkich słów
   *
   * @var float
   * @access private
   */
  private $threshold = 0.01;
  /**
   * Zabronione słowa po których powinniśmy określać czy wartość jest spamem, wielkość liter jest ignorowana
   *
   * @var array
   * @access private
   */
  private $badwords  = array();
  /**
   * Zabronione słowa, które zostały znalezione w sprawdzanym tekście
   *
   * @var array
   * @access public
   */
  public $badwordsFound = array();
  /**
   * Poziom spamu w sprawdzanej wartości
   *
   * @var float
   * @access public
   */
  public $badwordsLevel = 0;
  /**
   * Konstruktor
   *
   * @param double $threshold
   * @param string $badwordsPath
   * @access public
   * @return void
   */
  public function spamValidator ( $threshold = NULL, $badwordsPath = 'badwords.txt')
  {
    if ( isset($threshold) )
      $this->thresold = $threshold;

    $this->getBadwords($badwordsPath);
  }

  /**
   * Pobiera listę zabronionych słów z pliku, słowa mogą być oddzielone przecinkami i nowymi liniami
   *
   * @param string $path
   * @access private
   * @return void
   */
  private function getBadwords ( $path )
  {
    if ( !is_file($path) ||  !is_readable ($path) )
      throw new Exception("Nie mogę otworzyć pliku z zabronionymi słowami");

    $file = file_get_contents($path);
    $file = str_replace("\n",",",$file);
    $file = str_replace(",,",",",$file);
    $file = rtrim($file,',');
    $this->badwords = explode(',',$file);
  }

  /**
   * Sprawdza wartość $value pod względem zawartości spamu (czyli pod względem wystąpień zabronionych słów), przy określonym progu tolerancji
   *
   * @param string $value
   * @access public
   * @return bool Jeśli próg liczby zabronionych słów nie przekracza zdefionowanego progru, to zwraca true, inaczej false
   */
  public function execute ($value)
  {
    $value = str_replace( array(',','.',':',';','?','!','-','_','*','<','>','/',"\\",'@',')','(','&', "\n", "\t", "\r"), ' ', $value);
    $value = str_replace( '  ',' ', $value);
    $value = strtolower( $value );
    $value = explode(' ', $value);

    if ( count($value) == 0 )
      return true; //wartość do sprawdzenia nie posiada żadnych słów, zwracamy true

    $badwordsFound = array();
    foreach( $value as $word) {
      foreach ( $this->badwords as $badword ) {
        if (stripos( $word, $badword) !== false  )  {
          $badwordsFound[] = $badword;
        }
      }
    }

    $level = count($badwordsFound) / count($value);
    if ( $level >= $this->thresold ) {
       $this->badwordsFound = array_unique($badwordsFound);
       $this->badwordsLevel = $level;
       return false;
    } else {
      return true;
    }
  }
}

Przykład użycia:

execute( $msg )) {
  echo "Twoja wiadomość jest spamem 
";
  echo "Poziom spamu: " . $validator->badwordsLevel . "
";
  echo "Zabronione słowa: ". implode(', ', $validator->badwordsFound) . "
";
}

Powyższa klasa validatora mimo swojej prostoty okazała się bardzo skuteczna – poziom spamu zredukował się praktycznie do zera 🙂