_binary From 57c0e5f7d5da8300d4cd305ed83150258b56ab08 Mon Sep 17 00:00:00 2001
From: Akash Patel <akash3664@yahoo.com>
Date: Thu, 6 Mar 2014 00:45:24 -0800
Subject: [PATCH] Added hash-based captcha and code to toggle between
 hash-based captcha and text-based captcha

---
 configs/config.php                          |   3 +-
 controllers/components/system_component.php |   6 +
 controllers/register_controller.php         | 138 +++++++++---
 locale/en-US/configure.ini                  |   6 +
 locale/en-US/statistics.txt                 |   2 +-
 models/profile_model.php                    |   7 +-
 scripts/hashcash.js                         | 277 ++++++++++++++++++++++++
 tests/hash_table_test.php                   |   4 +-
 tests/index.php                             |   9 +-
 tests/sha1_test.php                         | 313 ++++++++++++++++++++++++++++
 views/elements/serversettings_element.php   |  14 ++
 views/suggest_view.php                      |  38 +++-
 12 files changed, 762 insertions(+), 55 deletions(-)
 create mode 100644 scripts/hashcash.js
 create mode 100644 tests/sha1_test.php

diff --git a/configs/config.php b/configs/config.php
index 894037a..aeeb173 100755
--- a/configs/config.php
+++ b/configs/config.php
@@ -104,7 +104,7 @@ if(MAINTENANCE_MODE && $_SERVER["SERVER_ADDR"] != $_SERVER["REMOTE_ADDR"]) {
 }
 
 if(!defined('WORK_DIRECTORY')) {
-/*+++ The next block of code is machine edited, change at
+/*+++ The next block of code is machine edited, change at 
 your own risk, please use configure web page instead +++*/
 define('WORK_DIRECTORY', '');
 /*++++++*/
@@ -152,6 +152,7 @@ if(file_exists(WORK_DIRECTORY.PROFILE_FILE_NAME)) {
     /** @ignore */
     define('PROFILE', false);
     define('DBMS', 'sqlite3');
+    define('CAPTCHA_MODE', 'hashbased_captcha');
     define('DEBUG_LEVEL', NO_DEBUG_INFO);
     define('USE_FILECACHE', false);
     define('WEB_ACCESS', true);
diff --git a/controllers/components/system_component.php b/controllers/components/system_component.php
index 4ea1fd8..f5f7769 100644
--- a/controllers/components/system_component.php
+++ b/controllers/components/system_component.php
@@ -606,6 +606,12 @@ class SystemComponent extends Component
                 'admin_activation' =>
                     tl('system_component_configure_admin_activation'),
             );
+        $data['CAPTCHA_MODES'] = array (
+                'text_captcha' => 
+                   tl('serversettings_element_text_captcha'),
+                'hash_captcha' => 
+                   tl('serversettings_element_hash_captcha'),
+            );
         $data['show_mail_info'] = "false";
         if(isset($data['REGISTRATION_TYPE']) &&
             in_array($data['REGISTRATION_TYPE'], array(
diff --git a/controllers/register_controller.php b/controllers/register_controller.php
index 0c16268..4184033 100755
--- a/controllers/register_controller.php
+++ b/controllers/register_controller.php
@@ -110,7 +110,22 @@ class RegisterController extends Controller implements CrawlConstants
      * @var int
      */
     const NUM_RECOVERY_QUESTIONS = 3;
-
+    /**
+     * Use to check the captcha mode 
+     * @var string
+     */
+    const  TEXT_CAPTCHA = 'text_captcha';
+    const  HASH_CAPTCHA = 'hash_captcha';
+    /**
+     * Define the number of seconds till hash code is valid
+     * @var int
+     */
+    const HASH_TIMESTAMP_TIMEOUT = 300;
+    /**
+     * Use to match the leading zero in the sha1 of the string
+     * @var int
+     */
+    const HASH_CAPTCHA_LEVEL = 2;
     /**
      *  Besides invoking the base controller, sets up in field variables
      *  the captcha and recovery question and possible answers.
@@ -593,44 +608,68 @@ class RegisterController extends Controller implements CrawlConstants
      */
     function suggestUrl()
     {
+        $profile_model = $this->model("profile");
+        $profile =  $profile_model->getProfile( WORK_DIRECTORY);    
         $data["REFRESH"] = "suggest";
-        $num_captchas = self::NUM_CAPTCHA_QUESTIONS;
         $visitor_model = $this->model("visitor");
         $clear = false;
-        if(!isset($_SESSION['BUILD_TIME']) || !isset($_REQUEST['build_time']) ||
+        if(strcasecmp($profile['CAPTCHA_MODE'],self::HASH_CAPTCHA) == 0) {
+            $data['INCLUDE_SCRIPTS'] = array("hashcash");
+        }
+        if(strcasecmp($profile['CAPTCHA_MODE'],self::TEXT_CAPTCHA) == 0) {
+            $num_captchas = self::NUM_CAPTCHA_QUESTIONS;
+            unset($_SESSION["request_time"]);
+            unset($_SESSION["level"] );
+            unset($_SESSION["random_String"] );
+        }
+        if(!isset($_SESSION['BUILD_TIME']) || !isset($_REQUEST['build_time'])||
             $_SESSION['BUILD_TIME'] != $_REQUEST['build_time']) {
+            if(strcmp($profile['CAPTCHA_MODE'],self::HASH_CAPTCHA) == 0) {
+                $time = time();
+                $_SESSION["request_time"] = $time;
+                $_SESSION["level"] = self::HASH_CAPTCHA_LEVEL;
+                $_SESSION["random_string"] = md5( $time . AUTH_KEY );
+            }
             $clear = true;
-            if(isset($_REQUEST['url'])) { unset($_REQUEST['url']); }
+            if(isset($_REQUEST['url'])) {
+                unset($_REQUEST['url']);
+            }
             if(isset($_REQUEST['arg'])) {
                 unset($_REQUEST['arg']);
             }
             $data['build_time'] = time();
             $_SESSION['BUILD_TIME'] = $data['build_time'];
         }
-        for($i = 0; $i < $num_captchas; $i++) {
-            $data["question_$i"] = "-1";
-            if($clear && isset($_REQUEST["question_$i"])) {
-                unset($_REQUEST["question_$i"]);
+        if(strcmp($profile['CAPTCHA_MODE'],self::TEXT_CAPTCHA) == 0) { 
+            for($i = 0; $i < $num_captchas; $i++) {
+                $data["question_$i"] = "-1";
+                if($clear && isset($_REQUEST["question_$i"])) {
+                    unset($_REQUEST["question_$i"]);
+                }
             }
         }
         $data['url'] = "";
         if(isset($_REQUEST['url'])) {
             $data['url'] = $this->clean($_REQUEST['url'], "string");
         }
-        if(!isset($_SESSION['CAPTCHAS'])||!isset($_SESSION['CAPTCHA_ANSWERS'])){
-            list($captchas, $answers) = $this->selectQuestionsAnswers(
+        if(strcmp($profile['CAPTCHA_MODE'],self::TEXT_CAPTCHA) == 0) {
+            if(!isset($_SESSION['CAPTCHAS']) ||
+               !isset($_SESSION['CAPTCHA_ANSWERS'])){
+                list($captchas, $answers) = $this->selectQuestionsAnswers(
                 $this->captchas_qa, $num_captchas, self::NUM_CAPTCHA_CHOICES);
-            $data['CAPTCHAS'] = $captchas;
-            $data['build_time'] = time();
-            $_SESSION['BUILD_TIME'] = $data['build_time'];
-            $_SESSION['CAPTCHA_ANSWERS'] = $answers;
-            $_SESSION['CAPTCHAS'] = $data['CAPTCHAS'];
-        } else {
-            $data['CAPTCHAS'] = $_SESSION['CAPTCHAS'];
+                $data['CAPTCHAS'] = $captchas;
+                $data['build_time'] = time();
+                $_SESSION['BUILD_TIME'] = $data['build_time'];
+                $_SESSION['CAPTCHA_ANSWERS'] = $answers;
+                $_SESSION['CAPTCHAS'] = $data['CAPTCHAS'];
+            } else {
+                $data['CAPTCHAS'] = $_SESSION['CAPTCHAS'];
+            }
         }
         $missing = array();
         $save = isset($_REQUEST['arg']) && $_REQUEST['arg'];
-        for($i = 0; $i < $num_captchas; $i++) {
+        if(strcmp($profile['CAPTCHA_MODE'],self::TEXT_CAPTCHA) == 0) {
+            for($i = 0; $i < $num_captchas; $i++) {
             $field = "question_$i";
             $captchas = isset($_SESSION['CAPTCHAS'][$i]) ?
                 $_SESSION['CAPTCHAS'][$i] : array();
@@ -642,6 +681,7 @@ class RegisterController extends Controller implements CrawlConstants
                     $data[$field] = $_REQUEST[$field];
                 }
             }
+          }
         }
         $data['MISSING'] = $missing;
         if($save && isset($_REQUEST['url'])) {
@@ -662,8 +702,18 @@ class RegisterController extends Controller implements CrawlConstants
                     tl('register_controller_error_fields')."</h1>');";
                 return $data;
             }
-            if(!$this->checkCaptchaAnswers()) {
-                $data['SCRIPT'] = "doMessage('<h1 class=\"red\" >".
+            if(strcmp($profile['CAPTCHA_MODE'],self::HASH_CAPTCHA) == 0) {
+                if(!$this->validateHashCode()) {
+                    $data['SCRIPT'] = "doMessage('<h1 class=\"red\" >".
+                    tl('register_controller_failed_hashcode')."</h1>');";
+                    $visitor_model->updateVisitor(
+                    $_SERVER['REMOTE_ADDR'], "captcha_time_out");
+                    return $data;
+                }
+            }
+            if(strcmp($profile['CAPTCHA_MODE'],self::TEXT_CAPTCHA) == 0) {
+                if(!$this->checkCaptchaAnswers()) {
+                    $data['SCRIPT'] = "doMessage('<h1 class=\"red\" >".
                     tl('register_controller_failed_human')."</h1>');";
                 for($i = 0; $i < $num_captchas; $i++) {
                     $data["question_$i"] = "-1";
@@ -671,12 +721,13 @@ class RegisterController extends Controller implements CrawlConstants
                 unset($_SESSION['CAPTCHAS']);
                 unset($_SESSION['CAPTCHA_ANSWERS']);
                 $visitor_model->updateVisitor(
-                    $_SERVER['REMOTE_ADDR'], "captcha_time_out");
+                $_SERVER['REMOTE_ADDR'], "captcha_time_out");
                 return $data;
+                }
             }
             if(!$this->model("crawl")->appendSuggestSites($url)) {
                 $data['SCRIPT'] = "doMessage('<h1 class=\"red\" >".
-                    tl('register_controller_suggest_full')."</h1>');";
+                tl('register_controller_suggest_full')."</h1>');";
                 return $data;
             }
             $data['SCRIPT'] = "doMessage('<h1 class=\"red\" >".
@@ -684,16 +735,18 @@ class RegisterController extends Controller implements CrawlConstants
             $visitor_model->updateVisitor(
                 $_SERVER['REMOTE_ADDR'], "suggest_day_exceeded",
                 self::ONE_DAY, self::ONE_DAY, MAX_SUGGEST_URLS_ONE_DAY);
-            for($i = 0; $i < $num_captchas; $i++) {
+            if(strcmp($profile['CAPTCHA_MODE'],self::TEXT_CAPTCHA) == 0) {
+                for($i = 0; $i < $num_captchas; $i++) {
                 $data["question_$i"] = "-1";
-            }
-            unset($_SESSION['CAPTCHAS']);
-            unset($_SESSION['CAPTCHA_ANSWERS']);
-            list($captchas, $answers) = $this->selectQuestionsAnswers(
+                }
+                unset($_SESSION['CAPTCHAS']);
+                unset($_SESSION['CAPTCHA_ANSWERS']);
+                list($captchas, $answers) = $this->selectQuestionsAnswers(
                 $this->captchas_qa, $num_captchas, self::NUM_CAPTCHA_CHOICES);
-            $data['CAPTCHAS'] = $captchas;
-            $_SESSION['CAPTCHA_ANSWERS'] = $answers;
-            $_SESSION['CAPTCHAS'] = $data['CAPTCHAS'];
+                $data['CAPTCHAS'] = $captchas;
+                $_SESSION['CAPTCHA_ANSWERS'] = $answers;
+                $_SESSION['CAPTCHAS'] = $data['CAPTCHAS'];
+            }
             $data['build_time'] = time();
             $_SESSION['BUILD_TIME'] = $data['build_time'];
             $data['url'] ="";
@@ -1000,7 +1053,28 @@ class RegisterController extends Controller implements CrawlConstants
         $data['MISSING'] = $missing;
     }
 
-
+     /**
+     *  Calculates the sha1 of a string consist of a randomString,request_time
+     *  send by a server and the nonce send by a client.It checks 
+     *  whether the sha1 produces expected number of a leading zeroes
+     *
+     *  @return bool true if the sha1 produces expected number 
+     *  of a leading zeroes.
+     */
+    function validateHashCode()
+    {
+        $hex_key = $_SESSION["random_string"].':'.$_SESSION["request_time"].
+                   ':'.$_REQUEST['nonce_for_string'];
+        $pattern = '/^0{'.$_SESSION['level'].'}/';
+        $time = time();
+        $_SESSION["request_time"] = $time;
+        $_SESSION["random_string"] =  md5( $time . AUTH_KEY );
+        if((time()- $_SESSION["request_time"] < self::HASH_TIMESTAMP_TIMEOUT)
+           && (preg_match($pattern, sha1($hex_key) ))){
+            return true;
+        }
+        return false;
+    }  
 }
 
-?>
\ No newline at end of file
+?>
diff --git a/locale/en-US/configure.ini b/locale/en-US/configure.ini
index 11b75a4..32bea98 100755
--- a/locale/en-US/configure.ini
+++ b/locale/en-US/configure.ini
@@ -834,6 +834,12 @@ system_component_configure_email_activation = "Email Activation"
 ; system_component.php line: 606
 system_component_configure_admin_activation = "Admin Activation"
 ;
+; system_component.php line: 611
+serversettings_element_text_captcha = "Text Captcha"
+;
+; system_component.php line: 613
+serversettings_element_hash_captcha = "Hash Captcha"
+;
 ; system_component.php line: 710
 system_component_configure_use_absolute_path = "Must use an Absolute path for Work Directory"
 ;
diff --git a/locale/en-US/statistics.txt b/locale/en-US/statistics.txt
index b6bef56..5a165df 100755
--- a/locale/en-US/statistics.txt
+++ b/locale/en-US/statistics.txt
@@ -1 +1 @@
-d:99;
\ No newline at end of file
+d:100;
\ No newline at end of file
diff --git a/models/profile_model.php b/models/profile_model.php
index efdf3bd..03ead68 100755
--- a/models/profile_model.php
+++ b/models/profile_model.php
@@ -50,9 +50,9 @@ require_once(BASE_DIR.'/lib/url_parser.php');
 class ProfileModel extends Model
 {
     var $profile_fields = array('API_ACCESS', 'AUTH_KEY', 'CACHE_LINK',
-        'DEBUG_LEVEL', 'DESCRIPTION_WEIGHT', 'DB_HOST', 'DBMS', 'DB_NAME',
-        'DB_PASSWORD', 'DB_USER', 'DEFAULT_LOCALE', 'GROUP_ITEM', 'IN_LINK',
-        'IP_LINK', 'LINK_WEIGHT', 'MAIL_PASSWORD', 'MAIL_SECURITY',
+        'CAPTCHA_MODE','DEBUG_LEVEL', 'DESCRIPTION_WEIGHT', 'DB_HOST', 'DBMS',
+        'DB_NAME','DB_PASSWORD', 'DB_USER', 'DEFAULT_LOCALE', 'GROUP_ITEM', 
+        'IN_LINK','IP_LINK', 'LINK_WEIGHT', 'MAIL_PASSWORD', 'MAIL_SECURITY',
         'MAIL_SENDER', 'MAIL_SERVER', 'MAIL_SERVERPORT', 'MAIL_USERNAME',
         'MEMCACHE_SERVERS', 'MIN_RESULTS_TO_GROUP', 'NAME_SERVER', 'NEWS_MODE',
         'PROXY_SERVERS', 'REGISTRATION_TYPE', 'ROBOT_DESCRIPTION',
@@ -499,7 +499,6 @@ EOT;
             $profile['ROBOT_DESCRIPTION'] =
                 file_get_contents($robot_path);
         }
-
         return $profile;
     }
 
diff --git a/scripts/hashcash.js b/scripts/hashcash.js
new file mode 100644
index 0000000..2a498c5
--- /dev/null
+++ b/scripts/hashcash.js
@@ -0,0 +1,277 @@
+/**
+ *  SeekQuarry/Yioop --
+ *  Open Source Pure PHP Search Engine, Crawler, and Indexer
+ *
+ *  Copyright (C) 2009 - 2014  Chris Pollett chris@pollett.org
+ *
+ *  LICENSE:
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *  END LICENSE
+ *
+ * @author Chris Pollett chris@pollett.org
+ * @package seek_quarry
+ * @subpackage javascript
+ * @license http://www.gnu.org/licenses/ GPL3
+ * @link http://www.seekquarry.com/
+ * @copyright 2009 - 2014
+ * @filesource
+ */
+
+/*
+ *  To find the nonce for the inpute paramaters given by the server.
+ * 
+ *  @param Object nonce_for_string a DOM element to put the value of a nonce
+ *  @param Object random_string a DOM element to get the value of random_string
+ *  @param Object time a DOM element to get the value of the time
+ *  @param Object level a DOM element to get the value of a level
+ */
+function findNonce(nonce_for_string, random_string, time, level) 
+{
+    if(elt(random_string).value)
+    {
+        var random_string = elt(random_string).value;
+        var level = elt(level).value;
+        var time = elt(time).value;
+        var nonce = hashStamp(random_string, time, level);
+        var input = elt(nonce_for_string);
+        input.setAttribute('value', nonce);
+        input.setAttribute('type', 'hidden');
+    }
+}
+
+/*
+ *  This function calculates the sha1 on the strings until
+ *  numeber of a leading zeroes in the sha1 value matches with a level 
+ *  parameter.
+ *
+ *  @param String random_string a string sent by the server
+ *  @param String time the time sent by the server
+ *  @param String level define number of leading zeroes
+ *  @return int nonce for which the sha1 of a string
+        produces the level number of a zeroes
+ */
+function hashStamp(random_string, time, level)
+{
+    var nonce = -1;
+    var input_string = '';
+    var pattern = new RegExp("^0{"+level+"}");
+    while (!generateSha1(input_string).match(pattern)) {
+        nonce = nonce + 1;
+        input_string = random_string +':'+time + ':' + nonce;
+    }
+    return nonce;
+}
+
+/*
+ *  To calculate the sha1 on the given String.
+ *
+ *  @param String input string for the function
+ *  @return String sha1 of the input string
+ */
+function generateSha1(str) 
+{
+    var n = ((str.toString().length + 8) >> 6) + 1;
+    var x = new Array();
+
+    for (var i = 0; i < n * 16; i++) {
+        x[i] = 0;
+    }
+
+    for (i = 0; i < str.toString().length; i++) {
+        x[i >> 2] += 
+        (str.toString().substr(i, 1)).charCodeAt(0) << (24 - (i % 4) * 8);
+    }
+
+    x[i >> 2] |= 0x80 << (24 - (i % 4) * 8);
+    x[n * 16 - 1] = str.toString().length * 8;
+
+    var a = 1732584193;
+    var b = -271733879;
+    var c = -1732584194;
+    var d = 271733878;
+    var e = -1009589776;
+
+    var w = new Array();
+
+    for (i = 0; i < x.length; i += 16) {
+        var olda = a;
+        var oldb = b;
+        var oldc = c;
+        var oldd = d;
+        var olde = e;
+
+        for (var j = 0; j < 80; j++) {
+            if (j < 16) {
+                w[j] = x[i + j];
+            } else {
+                w[j] = rotate(w[j - 3]^w[j - 8]^w[j - 14]^w[j - 16], 1);
+            }
+            var t = safeAdd(safeAdd(rotate(a, 5),
+                    hashOperationByIteration(j, b, c, d)),
+                    safeAdd(safeAdd(e, w[j]), constantForIteration(j)));
+            e = d;
+            d = c;
+            c = rotate(b, 30);
+            b = a;
+            a = t;
+        }
+
+        a = safeAdd(a, olda);
+        b = safeAdd(b, oldb);
+        c = safeAdd(c, oldc);
+        d = safeAdd(d, oldd);
+        e = safeAdd(e, olde);
+    }
+    return bin2hex(Array(a, b, c, d, e));
+}
+
+/*
+ *  To conver a binary array into a hexadecimal value
+ *
+ *  @param array binarray array of a 5 binary values
+ *  @return String hexadecimal
+ */
+function bin2hex(binarray)
+{
+    var hex_case = 0;
+    var hex_tab = hex_case ? "0123456789ABCDEF" : "0123456789abcdef";
+    var str = "";
+    for (var i = 0; i < binarray.length * 4; i++) {
+        str += hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8 + 4))
+        & 0xF)+hex_tab.charAt((binarray[i >> 2] >> ((3 - i % 4) * 8))
+        & 0xF);
+    }
+    return str;
+}
+
+/*
+ *  Calculate the one of the operand of a safe_add function
+ *  based on the iteration value
+ *
+ *  @param int t value of iteration
+ *  @param int b constant value
+ *  @param int c constant value
+ *  @param int d constant value
+ *  @return int result of a xor value on constants
+ */
+function hashOperationByIteration(t, b, c, d) 
+{
+    if (t < 20) {
+        return (b & c) | ((~b) & d);
+    }
+    if (t < 40) {
+        return b^c^d;
+    }
+    if (t < 60) {
+        return (b & c) | (b & d) | (c & d);
+    }
+    return b^c^d;
+}
+
+/*
+ *  To find a constant based on the iteration
+ *
+ *  @param int t value of iteration
+ *  @return int constant value
+ */
+function constantForIteration(t) 
+{
+    if (t < 20) {
+        return 1518500249;
+    } else if (t < 40) {
+        return 1859775393;
+    } else if (t < 60) {
+        return -1894007588;
+    } else {
+        return -899497514;
+    }
+}
+
+/*
+ *  To add integers, wrapping at 2^32
+ *
+ *  @param int x first operand of the add operation
+ *  @param int y second operand of the add operation
+ *  @return int result of the add operation
+ */
+function safeAdd(x, y)
+{
+    var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+    var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+    return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ *  Bitwise rotate a 32-bit number
+ *
+ *  @param int num number on which rotation operation is performed
+ *  @param int count define a number of times the shift opeation should perform
+ *  @return int result of the rotate operation
+ */
+function rotate(num, cnt)
+{
+    return (num << cnt) | zeroFill(num, 32 - cnt);
+}
+
+/*
+ *  Use for a zero padding to the input number if a number is not 32 bit
+ *
+ *  @param int a input number
+ *  @param int b to define how many leading zero should be added
+ *  @return int 32 bit number
+ */
+function zeroFill(a, b)
+{
+    var bin = dec2bin(a);
+
+    if (bin.toString().length < b) {
+        bin = 0;
+    } else {
+        var length = bin.toString().length;
+        bin = bin.toString().substr(0, length - b);
+    }
+
+    for (var i = 0; i < b; i++) {
+        bin = "0" + bin;
+    }
+    return bin2dec(bin);
+}
+
+/*
+ *  Convert decimal number to binary string
+ *
+ *  @param int number input number
+ *  @return string binary number
+ */
+function dec2bin(number) 
+{
+    if (number < 0) {
+        number = 0xFFFFFFFF + number + 1;
+    }
+    return parseInt(number, 10).toString(2);
+}
+
+/*
+ *  Convert binary string to decimal number
+ *
+ *  @param string binary string input string
+ *  @return int decimal number
+ */
+function bin2dec(binary_string)
+{
+    binary_string = (binary_string + '').replace(/[^01]/gi, '');
+    return parseInt(binary_string, 2);
+}
diff --git a/tests/hash_table_test.php b/tests/hash_table_test.php
index a013281..39b2e5d 100755
--- a/tests/hash_table_test.php
+++ b/tests/hash_table_test.php
@@ -27,7 +27,7 @@
  * @subpackage test
  * @license http://www.gnu.org/licenses/ GPL3
  * @link http://www.seekquarry.com/
- * @copyright 2009 - 2014
+ * @copyright 2009 - 2014 
  * @filesource
  */
 
@@ -233,4 +233,4 @@ class HashTableTest extends UnitTest
     }
 
 }
-?>
\ No newline at end of file
+?>
diff --git a/tests/index.php b/tests/index.php
index 953aff1..f71a6a6 100644
--- a/tests/index.php
+++ b/tests/index.php
@@ -153,9 +153,8 @@ function runAllTests()
 function runTestBasedOnRequest()
 {
     echo "<p><a href='?activity=listTests'>See test case list</a>.</p>";
-
     if(isset($_REQUEST['test'])) {
-        $name = preg_replace("/[^A-Za-z_]/", '', $_REQUEST['test'])."_test.php";
+        $name = preg_replace("/[^A-Za-z_0-9]/", '', $_REQUEST['test'])."_test.php";
         if(file_exists($name)) {
             runTest($name);
         }
@@ -170,10 +169,9 @@ function runTestBasedOnRequest()
  */
 function runTest($name)
 {
-    require_once $name;
-
     $class_name = getClassNameFromFileName($name);
     echo "<h2>$class_name</h2>";
+    require_once $name;
 
     $test = new $class_name();
     $results = $test->run();
@@ -193,7 +191,6 @@ function runTest($name)
                 }
                 $count++;
             }
-
             if ($count == $passed) {
                 $color = "green";
             } else {
@@ -222,7 +219,6 @@ function runTest($name)
 function getTestNames()
 {
     return glob('*_test.php');
-
 }
 
 /**
@@ -242,7 +238,6 @@ function getClassNameFromFileName($name)
     foreach($name_parts as $part) {
         $class_name .= ucfirst($part);
     }
-
     //strip .php
     $class_name = substr($class_name, 0, strlen($class_name) - strlen(".php"));
 
diff --git a/tests/sha1_test.php b/tests/sha1_test.php
new file mode 100644
index 0000000..9c8359e
--- /dev/null
+++ b/tests/sha1_test.php
@@ -0,0 +1,313 @@
+<?php
+/**
+ *  SeekQuarry/Yioop --
+ *  Open Source Pure PHP Search Engine, Crawler, and Indexer
+ *
+ *  Copyright (C) 2009 - 2014  Chris Pollett chris@pollett.org
+ *
+ *  LICENSE:
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ *  END LICENSE
+ *
+ * @author Chris Pollett chris@pollett.org
+ * @package seek_quarry
+ * @subpackage test
+ * @license http://www.gnu.org/licenses/ GPL3
+ * @link http://www.seekquarry.com/
+ * @copyright 2009 - 2014
+ * @filesource
+ */
+
+ $time = time();
+ $randomString1 = md5($time.rand(1,1000));
+ $randomString2 = md5($time.rand(1,1000));
+ $randomString3 = md5($time.rand(1,1000));
+ $randomString4 = md5($time.rand(1,1000));
+ $randomString5 = md5($time.rand(1,1000));
+
+ $sha11 = sha1($randomString1);
+ $sha12 = sha1($randomString2);
+ $sha13 = sha1($randomString3);
+ $sha14 = sha1($randomString4);
+ $sha15 = sha1($randomString5);
+
+ ?>
+ <html>
+    <body>
+        <table border="1" summary="Displays info about this test case">
+        <div id="sha1Test">
+        </div>
+        <head>
+            <script type="text/javascript">
+                var rs1 = '<?php echo $randomString1; ?>' ;
+                var rs2 = '<?php echo $randomString2; ?>' ;
+                var rs3 = '<?php echo $randomString3; ?>' ;
+                var rs4 = '<?php echo $randomString4; ?>' ;
+                var rs5 = '<?php echo $randomString5; ?>' ;
+                var sha11 = '<?php echo $sha11; ?>' ;
+                var sha12 = '<?php echo $sha12; ?>' ;
+                var sha13 = '<?php echo $sha13; ?>' ;
+                var sha14 = '<?php echo $sha14; ?>' ;
+                var sha15 = '<?php echo $sha15; ?>' ;
+                find_nonce(rs1,rs2,rs3,rs4,rs5,sha11,sha12,sha13,sha14,sha15);
+                
+                /*
+                 *  To find the nonce for the inpute paramaters given by the server.
+                 * 
+                 *  @param Object nonce_for_string a DOM element to put the value of a nonce
+                 *  @param Object random_string a DOM element to get the value of random_string
+                 *  @param Object time a DOM element to get the value of the time
+                 *  @param Object level a DOM element to get the value of a level
+                 */
+                function find_nonce(randomString1,randomString2,randomString3,
+                randomString4,randomString5,sha11,sha12,sha13,sha14,sha15 )
+                {
+                    var cell, row, table;
+                    var counter=0,total=5;
+                    var result,color;
+                    if(sha11 == generateSha1(randomString1)) counter++;
+                    if(sha12 == generateSha1(randomString2)) counter++;
+                    if(sha13 == generateSha1(randomString3)) counter++;
+                    if(sha14 == generateSha1(randomString4)) counter++;
+                    if(sha15 == generateSha1(randomString5)) counter++;
+                    result = counter+"/"+total+" Test Passed";
+                    if(total==counter){
+                        color='lightgreen';
+                    } else {
+                        color='red';
+                    }
+                    table = document.createElement('table');
+                    table.setAttribute('border','1');
+                    row = table.insertRow(0);
+                    cell = row.insertCell(0);
+                    cell.style.fontWeight = 'bold';
+                    cell.innerHTML = "generateSha1TestCase";
+                    cell = row.insertCell(1);
+                    cell.setAttribute("style","background-color: "+color+";");
+                    cell.innerHTML = result;
+                    document.getElementById("sha1Test").appendChild(table);
+                }
+                /*
+                 *  To calculate the sha1 on the given String.
+                 *
+                 *  @param String input string for the function
+                 *  @return String sha1 of the input string
+                 */
+                function generateSha1(str) 
+                {
+                    var n = ((str.toString().length + 8) >> 6) + 1;
+                    var x = new Array();
+
+                    for (var i = 0; i < n * 16; i++) {
+                        x[i] = 0;
+                    }
+
+                    for (i = 0; i < str.toString().length; i++) {
+                        x[i >> 2] += 
+                        (str.toString().substr(i, 1)).charCodeAt(0) 
+                        << (24 - (i % 4) * 8);
+                    }
+
+                    x[i >> 2] |= 0x80 << (24 - (i % 4) * 8);
+                    x[n * 16 - 1] = str.toString().length * 8;
+
+                    var a = 1732584193;
+                    var b = -271733879;
+                    var c = -1732584194;
+                    var d = 271733878;
+                    var e = -1009589776;
+                    var w = new Array();
+
+                    for (i = 0; i < x.length; i += 16) {
+                        var olda = a;
+                        var oldb = b;
+                        var oldc = c;
+                        var oldd = d;
+                        var olde = e;
+
+                        for (var j = 0; j < 80; j++) {
+                            if (j < 16) {
+                                w[j] = x[i + j];
+                            } else {
+                                w[j] = rotate(w[j - 3]^w[j - 8]^
+                                        w[j - 14]^w[j - 16], 1);
+                            }
+                            var t = safeAdd(safeAdd(rotate(a, 5),
+                                    hashOperationByIteration(j, b, c, d)),
+                                    safeAdd(safeAdd(e, w[j]), 
+                                    constantForIteration(j)));
+                            e = d;
+                            d = c;
+                            c = rotate(b, 30);
+                            b = a;
+                            a = t;
+                        }
+
+                        a = safeAdd(a, olda);
+                        b = safeAdd(b, oldb);
+                        c = safeAdd(c, oldc);
+                        d = safeAdd(d, oldd);
+                        e = safeAdd(e, olde);
+                    }
+                    return bin2hex(Array(a, b, c, d, e));
+                }
+
+                /*
+                 *  To conver a binary array into a hexadecimal value
+                 *
+                 *  @param array binarray array of a 5 binary values
+                 *  @return String hexadecimal
+                 */
+                function bin2hex(binarray)
+                {
+                    var hex_case = 0;
+                    var hex_tab = hex_case ? "0123456789ABCDEF" :
+                        "0123456789abcdef";
+                    var str = "";
+                    for (var i = 0; i < binarray.length * 4; i++) {
+                        str += hex_tab.charAt((binarray[i >> 2] >> 
+                            ((3 - i % 4) * 8 + 4)) & 0xF)+
+                            hex_tab.charAt((binarray[i >> 2] >> 
+                            ((3 - i % 4) * 8)) & 0xF);
+                    }
+                    return str;
+                }
+
+                /*
+                 *  Calculate one of the operand of asafe_add function
+                 *  based on the iteration value
+                 *
+                 *  @param int t value of iteration
+                 *  @param int b constant value
+                 *  @param int c constant value
+                 *  @param int d constant value
+                 *  @return int result of xor value on the constants
+                 */
+                function hashOperationByIteration(t, b, c, d) 
+                {
+                    if (t < 20) {
+                        return (b & c) | ((~b) & d);
+                    }
+                    if (t < 40) {
+                        return b^c^d;
+                    }
+                    if (t < 60) {
+                        return (b & c) | (b & d) | (c & d);
+                    }
+                    return b^c^d;
+                }
+
+                /*
+                 *  To find the constant based on the iteration
+                 *
+                 *  @param int t value of the iteration
+                 *  @return int constant value
+                 */
+                function constantForIteration(t) 
+                {
+                    if (t < 20) {
+                        return 1518500249;
+                    } else if (t < 40) {
+                        return 1859775393;
+                    } else if (t < 60) {
+                        return -1894007588;
+                    } else {
+                        return -899497514;
+                    }
+                }
+
+                /*
+                 *  To add integers, wrapping at 2^32
+                 *
+                 *  @param int x first operand of the add operation
+                 *  @param int y second operand of the add operation
+                 *  @return int result of the add operation
+                 */
+                function safeAdd(x, y)
+                {
+                    var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+                    var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+                    return (msw << 16) | (lsw & 0xFFFF);
+                }
+
+                /*
+                 *  Bitwise rotate a 32-bit number
+                 *
+                 *  @param int num number on which rotation operation
+                 *        is performed
+                 *  @param int count define a number of times the shift 
+                 *    opeation should perform
+                 *  @return int result of the rotate operation
+                 */
+                function rotate(num, cnt)
+                {
+                    return (num << cnt) | zeroFill(num, 32 - cnt);
+                }
+
+                /*
+                 *  Use for a zero padding to the input number if number 
+                 * is not 32 bit
+                 *
+                 *  @param int a input number
+                 *  @param int b to define how many leading zero should be added
+                 *  @return int 32 bit number
+                 */
+                function zeroFill(a, b)
+                {
+                    var bin = dec2bin(a);
+
+                    if (bin.toString().length < b) {
+                        bin = 0;
+                    } else {
+                        var length = bin.toString().length;
+                        bin = bin.toString().substr(0, length - b);
+                    }
+
+                    for (var i = 0; i < b; i++) {
+                        bin = "0" + bin;
+                    }
+                    return bin2dec(bin);
+                }
+
+                /*
+                 *  Convert a decimal number to binary string
+                 *
+                 *  @param int number input number
+                 *  @return string binary number
+                 */
+                function dec2bin(number) 
+                {
+                    if (number < 0) {
+                        number = 0xFFFFFFFF + number + 1;
+                    }
+                    return parseInt(number, 10).toString(2);
+                }
+
+                /*
+                 *  Convert a binary string to decimal number
+                 *
+                 *  @param string binary string input string
+                 *  @return int decimal number
+                 */
+                function bin2dec(binary_string)
+                {
+                    binary_string = (binary_string + '').replace(/[^01]/gi,'');
+                    return parseInt(binary_string, 2);
+                }
+                </script>
+        </head>
+    </body>
+</html>
diff --git a/views/elements/serversettings_element.php b/views/elements/serversettings_element.php
index 9d86665..626e5e0 100644
--- a/views/elements/serversettings_element.php
+++ b/views/elements/serversettings_element.php
@@ -237,6 +237,20 @@ class ServersettingsElement extends Element
                 </div>
             </fieldset>
             </div>
+            <div class="top-margin">
+            <fieldset>
+                <legend><label
+                for="captcha_setting"><?php
+                e(tl('serversettings_element_captcha_setting'));
+                ?>
+                </label></legend>
+                    <?php $this->view->helper("options")->render(
+                        "captcha_setting", "CAPTCHA_MODE",
+                       $data['CAPTCHA_MODES'],
+                     $data['CAPTCHA_MODE']);
+                ?>
+            </fieldset>
+            </div>
             <div class="top-margin center">
             <button class="button-box" type="submit"><?php
                 e(tl('serversettings_element_submit')); ?></button>
diff --git a/views/suggest_view.php b/views/suggest_view.php
index 7498bdf..d7ccbbb 100644
--- a/views/suggest_view.php
+++ b/views/suggest_view.php
@@ -75,6 +75,16 @@ class SuggestView extends View
                 ?></span></h1>
             <p class="center"><?php e(tl('suggest_view_instructions'));?></p>
             <form method="post" action="#">
+            <input type='hidden' name='nonce_for_string'
+            id='nonce_for_string' />
+            <input type='hidden' name='random_string' id='random_string'
+            value='<?php e($_SESSION["random_string"]);?>' />
+            <input type='hidden' name='time1' id='time1'
+            value='<?php e($_SESSION["request_time"]);?>' />
+            <input type='hidden' name='level' id='level'
+            value='<?php e($_SESSION["level"]);?>' />
+            <input type='hidden' name='time' id='time'
+            value='<?php e(time());?>'/>
             <input type="hidden" name="c" value="register" />
             <input type="hidden" name="a" value="suggestUrl" />
             <input type="hidden" name="arg" value="save" />
@@ -97,6 +107,7 @@ class SuggestView extends View
                         </tr>
                         <tr>
                         <?php
+                        if(!isset($_SESSION["random_string"])){
                         $question_sets = array(
                             tl('register_view_human_check')=>$data['CAPTCHAS']);
                         $i = 0;
@@ -108,7 +119,7 @@ class SuggestView extends View
                                     <tr><th class="table-label"
                                         rowspan='<?php e($num); ?>'><?php
                                         e($name);
-                                    ?></th><td class="table-input border-top">
+                                      ?></th><td class="table-input border-top">
                                 <?php
                                 } else { ?>
                                     <tr><td class="table-input">
@@ -123,17 +134,22 @@ class SuggestView extends View
                                 e("</td></tr>");
                                 $i++;
                             }
+                          } 
                         }
                         ?>
                         <tr>
                             <td></td>
-                            <td class="table-input border-top">
-                            <input type="hidden"
+                            <?php if(isset($_SESSION["randomString"])) { ?>
+                            <td class="table-input"> 
+                            <?php } else { ?>
+                               <td class="table-input border-top"> 
+                            <?php }?>
+                               <input type="hidden"
                                 name="<?php e(CSRF_TOKEN);?>"
                                 value="<?php e($data[CSRF_TOKEN]); ?>"/>
-                                <button class="sides-margin" type="submit"><?php
-                                    e(tl('suggest_view_submit_url'));
-                                ?></button>
+                                <button class="sides-margin" type="submit">
+                                <?php e(tl('suggest_view_submit_url')); ?>
+                                </button>
                             </td>
                         </tr>
                     </table>
@@ -148,7 +164,13 @@ class SuggestView extends View
         </div>
         </div>
         <div class='tall-landing-spacer'></div>
-    <?php
+        <script>
+            document.addEventListener('DOMContentLoaded', function() {
+            var body = tag(body);
+            body.onload = findNonce('nonce_for_string','random_string','time1','level');
+            }, false);
+        </script>
+        <?php
         }
     }
-    ?>
\ No newline at end of file
+?>
-- 
1.8.0.msysgit.0

