initial checkin

darcs-hash:20070109215204-7ad00-911102ca792d59b403fea07473621236672a23ee.gz
This commit is contained in:
Andreas Gohr
2007-01-09 22:52:04 +01:00
commit 42a2703562
39 changed files with 433 additions and 0 deletions

BIN
VeraSe.ttf Normal file

Binary file not shown.

246
action.php Normal file
View File

@ -0,0 +1,246 @@
<?php
/**
* CAPTCHA antispam plugin
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Andreas Gohr <gohr@cosmocode.de>
*/
// must be run within Dokuwiki
if(!defined('DOKU_INC')) die();
if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
require_once(DOKU_PLUGIN.'action.php');
require_once(DOKU_INC.'inc/blowfish.php');
class action_plugin_captcha extends DokuWiki_Action_Plugin {
/**
* return some info
*/
function getInfo(){
return array(
'author' => 'Andreas Gohr',
'email' => 'andi@splitbrain.org',
'date' => '2006-12-02',
'name' => 'CAPTCHA Plugin',
'desc' => 'Use a CAPTCHA challenge to protect the Wiki against automated spam',
'url' => 'http://wiki:splitbrain.org/plugin:captcha',
);
}
/**
* register the eventhandlers
*/
function register(&$controller){
$controller->register_hook('ACTION_ACT_PREPROCESS',
'BEFORE',
$this,
'handle_act_preprocess',
array());
$controller->register_hook('HTML_EDITFORM_INJECTION',
'BEFORE',
$this,
'handle_editform_injection',
array('editform' => true));
if($this->getConf('regprotect')){
$controller->register_hook('ACTION_REGISTER',
'BEFORE',
$this,
'handle_act_register',
array());
$controller->register_hook('HTML_REGISTERFORM_INJECTION',
'BEFORE',
$this,
'handle_editform_injection',
array('editform' => false));
}
}
/**
* Will intercept the 'save' action and check for CAPTCHA first.
*/
function handle_act_preprocess(&$event, $param){
if('save' != $this->_act_clean($event->data)) return; // nothing to do for us
// do nothing if logged in user and no CAPTCHA required
if(!$this->getConf('forusers') && $_SERVER['REMOTE_USER']){
return;
}
// compare provided string with decrypted captcha
$rand = PMA_blowfish_decrypt($_REQUEST['plugin__captcha_secret'],auth_cookiesalt());
$code = $this->_generateCAPTCHA($this->_fixedIdent(),$rand);
if(!$_REQUEST['plugin__captcha_secret'] ||
!$_REQUEST['plugin__captcha'] ||
strtoupper($_REQUEST['plugin__captcha']) != $code){
// CAPTCHA test failed! Continue to edit instead of saving
msg($this->getLang('testfailed'),-1);
$event->data = 'preview';
}
// if we arrive here it was a valid save
}
/**
* Will intercept the register process and check for CAPTCHA first.
*/
function handle_act_register(&$event, $param){
// compare provided string with decrypted captcha
$rand = PMA_blowfish_decrypt($_REQUEST['plugin__captcha_secret'],auth_cookiesalt());
$code = $this->_generateCAPTCHA($this->_fixedIdent(),$rand);
if(!$_REQUEST['plugin__captcha_secret'] ||
!$_REQUEST['plugin__captcha'] ||
strtoupper($_REQUEST['plugin__captcha']) != $code){
// CAPTCHA test failed! Continue to edit instead of saving
msg($this->getLang('testfailed'),-1);
$event->preventDefault();
$event->stopPropagation();
return false;
}
// if we arrive here it was a valid save
}
/**
* Create the additional fields for the edit form
*/
function handle_editform_injection(&$event, $param){
if($param['editform'] && !$event->data['writable']) return;
// do nothing if logged in user and no CAPTCHA required
if(!$this->getConf('forusers') && $_SERVER['REMOTE_USER']){
return;
}
global $ID;
$rand = (float) (rand(0,10000))/10000;
$code = $this->_generateCAPTCHA($this->_fixedIdent(),$rand);
$secret = PMA_blowfish_encrypt($rand,auth_cookiesalt());
echo '<div id="plugin__captcha_wrapper">';
echo '<input type="hidden" name="plugin__captcha_secret" value="'.hsc($secret).'" />';
echo '<label for="plugin__captcha">'.$this->getLang('fillcaptcha').'</label> ';
echo '<input type="text" size="5" maxlength="5" name="plugin__captcha" id="plugin__captcha" class="edit" /> ';
switch($this->getConf('mode')){
case 'text':
echo $code;
break;
case 'js':
echo '<span id="plugin__captcha_code">'.$code.'</span>';
break;
case 'image':
echo '<img src="'.DOKU_BASE.'lib/plugins/captcha/img.php?secret='.rawurlencode($secret).'&amp;id='.$ID.'" '.
' width="'.$this->getConf('width').'" height="'.$this->getConf('height').'" alt="" /> ';
break;
case 'audio':
echo '<img src="'.DOKU_BASE.'lib/plugins/captcha/img.php?secret='.rawurlencode($secret).'&amp;id='.$ID.'" '.
' width="'.$this->getConf('width').'" height="'.$this->getConf('height').'" alt="" /> ';
echo '<a href="'.DOKU_BASE.'lib/plugins/captcha/wav.php?secret='.rawurlencode($secret).'&amp;id='.$ID.'"'.
' class="JSnocheck" title="'.$this->getLang('soundlink').'">';
echo '<img src="'.DOKU_BASE.'lib/plugins/captcha/sound.png" width="16" height="16"'.
' alt="'.$this->getLang('soundlink').'" /></a>';
break;
}
echo '</div>';
}
/**
* Build a semi-secret fixed string identifying the current page and user
*
* This string is always the same for the current user when editing the same
* page revision.
*/
function _fixedIdent(){
global $ID;
$lm = @filemtime(wikiFN($ID));
return auth_browseruid().
auth_cookiesalt().
$ID.$lm;
}
/**
* Pre-Sanitize the action command
*
* Similar to act_clean in action.php but simplified and without
* error messages
*/
function _act_clean($act){
// check if the action was given as array key
if(is_array($act)){
list($act) = array_keys($act);
}
//remove all bad chars
$act = strtolower($act);
$act = preg_replace('/[^a-z_]+/','',$act);
return $act;
}
/**
* Generates a random 5 char string
*
* @param $fixed string - the fixed part, any string
* @param $rand float - some random number between 0 and 1
*/
function _generateCAPTCHA($fixed,$rand){
$fixed = hexdec(substr(md5($fixed),5,5)); // use part of the md5 to generate an int
$rand = $rand * $fixed; // combine both values
// seed the random generator
srand($rand);
// now create the letters
$code = '';
for($i=0;$i<5;$i++){
$code .= chr(rand(65, 90));
}
// restore a really random seed
srand();
return $code;
}
/**
* Create a CAPTCHA image
*/
function _imageCAPTCHA($text){
$w = $this->getConf('width');
$h = $this->getConf('height');
// create a white image
$img = imagecreate($w, $h);
imagecolorallocate($img, 255, 255, 255);
// add some lines as background noise
for ($i = 0; $i < 30; $i++) {
$color = imagecolorallocate($img,rand(100, 250),rand(100, 250),rand(100, 250));
imageline($img,rand(0,$w),rand(0,$h),rand(0,$w),rand(0,$h),$color);
}
// draw the letters
for ($i = 0; $i < strlen($text); $i++){
$font = dirname(__FILE__).'/VeraSe.ttf';
$color = imagecolorallocate($img, rand(0, 100), rand(0, 100), rand(0, 100));
$size = rand(floor($h/1.8),floor($h*0.7));
$angle = rand(-35, 35);
$x = ($w*0.05) + $i * floor($w*0.9/5);
$cheight = $size + ($size*0.5);
$y = floor($h / 2 + $cheight / 3.8);
imagettftext($img, $size, $angle, $x, $y, $color, $font, $text[$i]);
}
header("Content-type: image/png");
imagepng($img);
imagedestroy($img);
}
}

12
conf/default.php Normal file
View File

@ -0,0 +1,12 @@
<?php
/**
* Options for the CAPTCHA plugin
*
* @author Andreas Gohr <andi@splitbrain.org>
*/
$conf['mode'] = 'js';
$conf['forusers'] = false;
$conf['regprotect'] = true;
$conf['width'] = 115;
$conf['height'] = 22;

13
conf/metadata.php Normal file
View File

@ -0,0 +1,13 @@
<?php
/**
* Options for the CAPTCHA plugin
*
* @author Andreas Gohr <andi@splitbrain.org>
*/
$meta['mode'] = array('multichoice','_choices' => array('js','text','image','audio'));
$meta['regprotect'] = array('onoff');
$meta['forusers'] = array('onoff');
$meta['width'] = array('numeric','_pattern' => '/[0-9]+/');
$meta['height'] = array('numeric','_pattern' => '/[0-9]+/');

22
img.php Normal file
View File

@ -0,0 +1,22 @@
<?php
/**
* CAPTCHA antispam plugin - Image generator
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Andreas Gohr <gohr@cosmocode.de>
*/
if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../../').'/');
define('NOSESSION',true);
define('DOKU_DISABLE_GZIP_OUTPUT', 1);
require_once(DOKU_INC.'inc/init.php');
require_once(DOKU_INC.'inc/auth.php');
require_once(dirname(__FILE__).'/action.php');
$ID = $_REQUEST['id'];
$plugin = new action_plugin_captcha();
$rand = PMA_blowfish_decrypt($_REQUEST['secret'],auth_cookiesalt());
$code = $plugin->_generateCAPTCHA($plugin->_fixedIdent(),$rand);
$plugin->_imageCAPTCHA($code);
//Setup VIM: ex: et ts=4 enc=utf-8 :

4
lang/en/audio/LICENSE Normal file
View File

@ -0,0 +1,4 @@
This work is licensed under the Creative Commons Sampling Plus 1.0 License. To
view a copy of this license, visit
http://creativecommons.org/licenses/sampling+/1.0/ or send a letter to Creative
Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA.

13
lang/en/audio/README Normal file
View File

@ -0,0 +1,13 @@
Author: Michael Klier <chi@chimeric.de>
Link: http://www.chimeric.de/projects/npa
Voice: Christian Spellenberg
These samples represent the NATO phonetical alphabet. They are protected
by the Creative Commons Sampling Plus 1.0 License. You are free to use
and redistribute these samples under the conditions defined by the
license. For further information read the LICENSE file and visit
http://www.creativecommons.org.
Note: The original high quality wave files were downsampled and converted
to 8-Bit mono files for distribution with the CAPTCHA plugin. Visit
the link above for the original files.

BIN
lang/en/audio/a.wav Normal file

Binary file not shown.

BIN
lang/en/audio/b.wav Normal file

Binary file not shown.

BIN
lang/en/audio/c.wav Normal file

Binary file not shown.

BIN
lang/en/audio/d.wav Normal file

Binary file not shown.

BIN
lang/en/audio/e.wav Normal file

Binary file not shown.

BIN
lang/en/audio/f.wav Normal file

Binary file not shown.

BIN
lang/en/audio/g.wav Normal file

Binary file not shown.

BIN
lang/en/audio/h.wav Normal file

Binary file not shown.

BIN
lang/en/audio/i.wav Normal file

Binary file not shown.

BIN
lang/en/audio/j.wav Normal file

Binary file not shown.

BIN
lang/en/audio/k.wav Normal file

Binary file not shown.

BIN
lang/en/audio/l.wav Normal file

Binary file not shown.

BIN
lang/en/audio/m.wav Normal file

Binary file not shown.

BIN
lang/en/audio/n.wav Normal file

Binary file not shown.

BIN
lang/en/audio/o.wav Normal file

Binary file not shown.

BIN
lang/en/audio/p.wav Normal file

Binary file not shown.

BIN
lang/en/audio/q.wav Normal file

Binary file not shown.

BIN
lang/en/audio/r.wav Normal file

Binary file not shown.

BIN
lang/en/audio/s.wav Normal file

Binary file not shown.

BIN
lang/en/audio/t.wav Normal file

Binary file not shown.

BIN
lang/en/audio/u.wav Normal file

Binary file not shown.

BIN
lang/en/audio/v.wav Normal file

Binary file not shown.

BIN
lang/en/audio/w.wav Normal file

Binary file not shown.

BIN
lang/en/audio/x.wav Normal file

Binary file not shown.

BIN
lang/en/audio/y.wav Normal file

Binary file not shown.

BIN
lang/en/audio/z.wav Normal file

Binary file not shown.

11
lang/en/lang.php Normal file
View File

@ -0,0 +1,11 @@
<?php
/**
* English language file
*
* @author Andreas Gohr <andi@splitbrain.org>
*/
$lang['testfailed'] = "Sorry, but the CAPTCHA wasn't answered correctly. Maybe you're not human at all?";
$lang['fillcaptcha'] = "Please fill all the letters into the box to prove you're human.";
$lang['soundlink'] = "If you can't read the letters on the image, download this .wav file to get them read to you.";

18
lang/en/settings.php Normal file
View File

@ -0,0 +1,18 @@
<?php
/**
* English language file
*
* @author Andreas Gohr <andi@splitbrain.org>
*/
$lang['mode'] = "Which type of CAPTCHA to use?";
$lang['mode_o_js'] = "Text (prefilled with JavaScript)";
$lang['mode_o_text'] = "Text (manual only)";
$lang['mode_o_image'] = "Image (bad accessibility)";
$lang['mode_o_audio'] = "Image (better accessibility)";
$lang['regprotect'] = "Protect the registration form as well?";
$lang['forusers'] = "Use CAPTCHA for logged in users, too?";
$lang['width'] = "Width of the CAPTCHA image (pixel)";
$lang['height'] = "Height of the CAPTCHA image (pixel)";

12
script.js Normal file
View File

@ -0,0 +1,12 @@
/**
* Autofill and hide the whole captcha stuff in the simple JS mode
*/
addInitEvent(function(){
var code = $('plugin__captcha_code');
if(!code) return;
var box = $('plugin__captcha');
box.value=code.innerHTML;
$('plugin__captcha_wrapper').style.display = 'none';
});

BIN
sound.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

5
style.css Normal file
View File

@ -0,0 +1,5 @@
.dokuwiki #plugin__captcha_wrapper img {
margin: 1px;
vertical-align: bottom;
border: 1px solid __border__;
}

77
wav.php Normal file
View File

@ -0,0 +1,77 @@
<?php
/**
* CAPTCHA antispam plugin - sound generator
*
* @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
* @author Andreas Gohr <gohr@cosmocode.de>
*/
if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../../').'/');
define('NOSESSION',true);
define('DOKU_DISABLE_GZIP_OUTPUT', 1);
require_once(DOKU_INC.'inc/init.php');
require_once(DOKU_INC.'inc/auth.php');
require_once(dirname(__FILE__).'/action.php');
$ID = $_REQUEST['id'];
$plugin = new action_plugin_captcha();
$rand = PMA_blowfish_decrypt($_REQUEST['secret'],auth_cookiesalt());
$code = strtolower($plugin->_generateCAPTCHA($plugin->_fixedIdent(),$rand));
// prepare an array of wavfiles
$lc = dirname(__FILE__).'/lang/'.$conf['lang'].'/audio/';
$en = dirname(__FILE__).'/lang/en/audio/';
$wavs = array();
for($i=0;$i<5;$i++){
$file = $lc.$code{$i}.'.wav';
if(!@file_exists($file)) $file = $en.$code{$i}.'.wav';
$wavs[] = $file;
}
header('Content-type: audio/x-wav');
header('Content-Disposition: attachment;filename=captcha.wav');
echo joinwavs($wavs);
/**
* Join multiple wav files
*
* All wave files need to have the same format and need to be uncompressed.
* The headers of the last file will be used (with recalculated datasize
* of course)
*
* @link http://ccrma.stanford.edu/CCRMA/Courses/422/projects/WaveFormat/
* @link http://www.thescripts.com/forum/thread3770.html
*/
function joinwavs($wavs){
$fields = join('/',array( 'H8ChunkID', 'VChunkSize', 'H8Format',
'H8Subchunk1ID', 'VSubchunk1Size',
'vAudioFormat', 'vNumChannels', 'VSampleRate',
'VByteRate', 'vBlockAlign', 'vBitsPerSample' ));
$data = '';
foreach($wavs as $wav){
$fp = fopen($wav,'rb');
$header = fread($fp,36);
$info = unpack($fields,$header);
// read optional extra stuff
if($info['Subchunk1Size'] > 16){
$header .= fread($fp,($info['Subchunk1Size']-16));
}
// read SubChunk2ID
$header .= fread($fp,4);
// read Subchunk2Size
$size = unpack('vsize',fread($fp, 4));
$size = $size['size'];
// read data
$data .= fread($fp,$size);
}
return $header.pack('V',strlen($data)).$data;
}