# HG changeset patch # User dan@fuhry # Date 1181788380 14400 # Node ID 2f9b67edc9b198d45e3675e69de3d777bb795efd Initial repository population diff -r 000000000000 -r 2f9b67edc9b1 ajim/ajim.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ajim/ajim.php Wed Jun 13 22:33:00 2007 -0400 @@ -0,0 +1,826 @@ +'.$text); + } + /** + * Make a SQL query. This function contains some error correction that performs automatic database upgrades if needed. + * @param string $q - The query text to send to MySQL. + * @return resource - or, kills the connection and bails out if the query failed + */ + function sql($q) { + $r = mysql_query($q, $this->conn); + if(!$r) + { + if(strstr(mysql_error(), 'Unknown column \'time_id\'')) + { + $this->sql('ALTER TABLE '.$this->table_prefix.'ajim ADD COLUMN time_id int(11) NOT NULL DEFAULT 0;'); + $r = mysql_query($q, $this->conn); + } + elseif(strstr(mysql_error(), 'Unknown column \'sid\'')) + { + $this->sql('ALTER TABLE '.$this->table_prefix.'ajim ADD COLUMN sid varchar(40) NOT NULL DEFAULT \'\';'); + $r = mysql_query($q, $this->conn); + } + elseif(strstr(mysql_error(), 'Unknown column \'ip_addr\'')) + { + $this->sql('ALTER TABLE '.$this->table_prefix.'ajim ADD COLUMN ip_addr varchar(15) NOT NULL DEFAULT \'\';'); + $r = mysql_query($q, $this->conn); + } + $this->kill('Error during query:
'.htmlspecialchars($q).'

MySQL said: '.mysql_error().'

Depending on the error, AjIM may be able to automatically repair it. Just hang tight for about ten seconds. Whatever you do, don\'t close this browser window!'); + } + return $r; + } + /** + * Get the user's SID (unique ID used for editing authorization) or generate a new one. + * @return string + */ + function get_sid() + { + // Tag the user with a unique ID that can be used to edit posts + // This is used to essentially track users, but only for the purpose of letting them edit posts + if(!isset($_COOKIE['ajim_sid'])) + { + $hash = sha1(microtime()); + setcookie('ajim_sid', $hash, time()+60*60*24*365); // Cookies last for one year + } + else + $hash = $_COOKIE['ajim_sid']; + + return $hash; + } + /** + * Set the default value for a configuration field. + * @param string $key - name of the configuration key + * @param string $value - the default value + * @param array $confarray - needs to be the array passed as the first param on the constructor + */ + function config_default($key, $value, &$confarray) + { + if(!isset($confarray[$key])) + $confarray[$key] = $value; + } + /** + * Set up some basic vars and a database connection + * @param array $config - a configuration array, with either the key db_connection_handle (a valid MySQL connection resource) or the keys dbhost, dbname, dbuser, and dbpass + * @param string $table_prefix - the text prepended to the "ajim" table, should match ^([A-z0-9_]+)$ + * @param string $handler - URL to the backend script, for example in Enano this would be the plugin file plugins/ajim.php + * @param string $admin - string containing the MD5 hash of the user's password, IF AND ONLY IF the user should be allowed to use the moderation function. In all other cases this should be false. + * @param string $id - used to carry over the randomly generated instance ID between requests. Should be false if the class is being initialized for displaying the inital HTML, in all other cases should be the value of the class variable AjIM::$id + * @param bool $can_post - true if the user is allowed to post, false otherwise. Defaults to true. + * @param mixed $formatfunc - a string containing the name of a function that can be called to format text before posts are sent to the user. If you need to call a class method, this should be an array with key 0 being the class name and key 1 being the method name. + */ + function __construct($config, $table_prefix, $handler, $admin = false, $id = false, $can_post = true, $formatfunc = false) { + // CONFIGURATION + // $this->prune: a limit on the number of posts in the chat box. Usually this should be set to 40 or 50. Default is 40. + // Set to -1 to disable pruning. + $this->prune = -1; + + $this->get_sid(); + + if(!is_array($config)) + $this->kill('$config passed to the AjIM constructor should be an associative array with either the keys dbhost, dbname, dbuser, and dbpass, or the key db_connection_handle.'); + if(isset($config['db_connection_handle'])) + { + if(!is_resource($config['db_connection_handle'])) $this->kill('$config[\'db_connection_handle\'] is not a valid resource'); + $this->conn = $config['db_connection_handle']; + if(!$this->conn) $this->kill('Error verifying database connection: '.mysql_error()); + } elseif(isset($config['dbhost']) && isset($config['dbname']) && isset($config['dbuser']) && isset($config['dbpass'])) { + $this->conn = mysql_connect($config['dbhost'], $config['dbuser'], $config['dbpass']); + if(!$this->conn) $this->kill('Error connecting to the database: '.mysql_error()); + $this->sql('USE '.$config['dbname']); + } + + $this->bad_words = Array('viagra', 'phentermine', 'pharma', 'rolex', 'genital', 'penis', 'ranitidine', 'prozac', 'acetaminophen', 'acyclovir', 'ionamin', 'denavir', 'nizoral', 'zoloft', 'estradiol', 'didrex', 'aciphex', 'seasonale', 'allegra', 'lexapro', 'famvir', 'propecia', 'nasacort'); + if(isset($config['bad_words']) && is_array($config['bad_words'])) + { + $this->bad_words = array_values(array_merge($this->bad_words, $config['bad_words'])); + } + + // Don't change these values here - change them by passing values to the config array in this constructor's params! + $this->config_default('sb_color_background', '#FFFFFF', $config); + $this->config_default('sb_color_foreground', '#000000', $config); + $this->config_default('sb_color_editlink', '#00C000', $config); + $this->config_default('sb_color_deletelink', '#FF0000', $config); + $this->config_default('sb_color_userlink', '#0000FF', $config); + + $this->config = $config; + + if($id) $this->id = $id; + else $this->id = 'ajim_'.time(); + $this->admin = $admin; + $this->formatfunc = $formatfunc; + $this->can_post = $can_post; + $this->table_prefix = $table_prefix; + $this->sql('CREATE TABLE IF NOT EXISTS '.$this->table_prefix.'ajim( + post_id mediumint(8) NOT NULL auto_increment, + name text, + website text, + post text, + time_id int(11) NOT NULL DEFAULT 0, + PRIMARY KEY ( post_id ) + );'); + $this->iface = $handler; + if(isset($_GET['ajimmode'])) $this->handler(); + } + /** + * A dummy function used for PHP4 compatibility. + * @see AjIM::__construct() + */ + function ajim($config, $table_prefix, $handler, $admin = false, $id = false, $can_post = true, $formatfunc = false) { + $this->__construct($config, $table_prefix, $handler, $admin, $id, $can_post, $formatfunc); + } + /** + * Generates the initial HTML UI to be sent to the user, used internally. + * @access private + * @param string $ajimPath - path to the AjIM connector (not this file), relative to document root, with initial slash. + */ + function html($ajimPath) { + + $enstr = $this->can_post ? '' : ' disabled="disabled"'; + $html = ''; + $html .= ''; + if($this->admin) { + $html.= ''; + } + $html .= '
+
'; + // This is the post form div + if($this->can_post) + { + $html .= '
+
+ + + + + + + '; + $html .= '
Name:
Website:
Message:

+ AjIM powered
+
'; + if($this->admin) { + $html .= '
Administration
'; + } + $html.='
'; + } else { + $html .= '
'; + if(isset($this->config['cant_post_notice'])) { + $html .= '
'.$this->config['cant_post_notice'].'
'; + } + $html .= '
'; + } + $html.=''; + + return $html; + } + /** + * Kills the database connection + */ + function destroy() { + mysql_close($this->conn); + } + /** + * Strips all traces of HTML, XML, and PHP from text, and prepares it for being inserted into a MySQL database. + * @access private + * @param string $text - the text to sanitize + * @return string + */ + function sanitize($text) { + $text = rawurldecode($text); + $text = preg_replace('#<(.*?)>#is', '<\\1>', $text); + $text = str_replace("\n", '
', $text); + $text = mysql_real_escape_string($text); + return $text; + } + /** + * Scrutinizes a string $text for any traces of the word $word, returns true if the text is clean. + * For example, if $word is "viagra" and the text contains "\/|@6r/\" this returns false, else you would get true. + * @access private + * @param string $text - the text to check + * @param string $word - word to look for. + * @return bool + */ + function spamcheck($text, $word) + { + // build an array, with each key containing one letter (equiv. to str_split() in PHP 5) + $chars = Array(); + for($i=0;$i'a|\/\\\\|@', + 'b'=>'b|\|o', + 'c'=>'c|\(|', + 'd'=>'d|o\|', + 'e'=>'e|3', + 'f'=>'f', + 'g'=>'g|6|9', + 'h'=>'h|\|n', + 'i'=>'i|\!|1|\|', + 'j'=>'j|\!|1|\|', + 'k'=>'k|\|<|\|<', + 'l'=>'l|\!|1|\|', + 'm'=>'m|nn|rn', + 'n'=>'n|h|u\\|\\\\\|', + 'o'=>'o|\(\)|0|@', + 'p'=>'p', + 'q'=>'q', + 'r'=>'r|\|\^', + 's'=>'s', + 't'=>'t|\+', + 'u'=>'u|n', + 'v'=>'v|\\\\\/', // "\/" + 'w'=>'w|vv|\\\\\/\\\\\/', // allows for "\/\/" + 'x'=>'x|><|><|><|><', + 'y'=>'y', + 'z'=>'z|\|\\\\\|' // |\| + ); + $regex = '#([\s]){0,1}'; + foreach($chars as $c) + { + $lc = strtolower($c); + if(isset($subs[$lc])) + { + $regex .= '('.$subs[$lc].')'; + } else { + die('0 $subs['.$lc.'] is not set'); + $regex .= preg_quote($c); + } + $regex .= '(.|)'; + } + $regex .= '([\s]){0,1}#is'; + //echo($word.': '.$regex.'
'); + if(preg_match($regex, $text)) return false; + return true; + } + /** + * Processes AJAX requests. Usually called if $_GET['ajimmode'] is set. + * @access private + */ + function handler() { + if(isset($_GET['ajimmode'])) { + switch($_GET['ajimmode']) { + default: + die(''); + break; + case 'getsource': + case 'getpost': + if(!preg_match('#^([0-9]+)$#', $_GET['p'])) die('SQL injection attempt'); + $q = $this->sql('SELECT post,sid,ip_addr FROM '.$this->table_prefix.'ajim WHERE post_id='.$_GET['p']); + $r = mysql_fetch_assoc($q); + if( ( ( isset($_GET['ajim_auth']) && (!$this->admin || ($this->admin != $_GET['ajim_auth']) ) ) || !isset($_GET['ajim_auth']) ) && ( $this->get_sid() != $r['sid'] || $_SERVER['REMOTE_ADDR'] != $r['ip_addr'] ) ) die('Hacking attempt'); + if($_GET['ajimmode']=='getpost') + if($this->formatfunc) + { + $p = @call_user_func($this->formatfunc, $r['post']); + if($p) $r['post'] = $p; + unset($p); // Free some memory + } + echo $r['post']; + break; + case "savepost": + if(!preg_match('#^([0-9]+)$#', $_POST['p'])) die('SQL injection attempt'); + $q = $this->sql('SELECT sid,ip_addr FROM '.$this->table_prefix.'ajim WHERE post_id='.$_POST['p']); + $r = mysql_fetch_assoc($q); + if( ( ( isset($_POST['ajim_auth']) && (!$this->admin || ($this->admin != $_POST['ajim_auth']) ) ) || !isset($_POST['ajim_auth']) ) && ( $this->get_sid() != $r['sid'] || $_SERVER['REMOTE_ADDR'] != $r['ip_addr'] ) ) die('Hacking attempt'); + $post = $this->sanitize($_POST['post']); + $post = $this->make_clickable($post); + $post = preg_replace('#_(.*?)_#is', '\\1', $post); + $post = preg_replace('#\*(.*?)\*#is', '\\1', $post); + $bad_words = Array('viagra', 'phentermine', 'pharma'); + foreach($bad_words as $w) + { + die('0000 checking'); + if(!$this->spamcheck($post, $w)) die('34567 The word "'.$w.'" has been detected in your message and as a result your post has been blocked. Don\'t argue, that will only get you banned.'); + } + if(!$this->can_post) die('Access to posting messages has been denied because the administrator has set that you must be logged into this website in order to post.'); + + $this->sql('UPDATE '.$this->table_prefix.'ajim SET post=\''.$post.'\' WHERE post_id='.$_POST['p'].';'); + + if($this->formatfunc) + { + $p = @call_user_func($this->formatfunc, $post); + if($p) $post = $p; + unset($p); // Free some memory + } + die($post); + break; + case 'delete': + if(!preg_match('#^([0-9]+)$#', $_POST['p'])) die('SQL injection attempt'); + $q = $this->sql('SELECT sid,ip_addr FROM '.$this->table_prefix.'ajim WHERE post_id='.$_POST['p']); + $r = mysql_fetch_assoc($q); + if( ( ( isset($_POST['ajim_auth']) && (!$this->admin || ($this->admin != $_POST['ajim_auth']) ) ) || !isset($_POST['ajim_auth']) ) && ( $this->get_sid() != $r['sid'] || $_SERVER['REMOTE_ADDR'] != $r['ip_addr'] ) ) die('Hacking attempt'); + $this->sql('DELETE FROM '.$this->table_prefix.'ajim WHERE post_id='.$_POST['p']); + die('good'); + break; + case 'post': + if(!preg_match('#(^|[\n ])([\w]+?://[\w\#$%&~/.\-;:=,?@\[\]+]*)$#is', $_POST['website'])) $_POST['website']=''; + // Now for a clever anti-spam trick: blacklist the words "viagra" and "phentermine" using one wicked regex: + // #([\s]){1}(v|\\\\\/)(.*){1}(i|\||l|1)(.*){1}(a|@|\/\\\\)(.*){1}(g|6)(.*){1}r(.*){1}(a|@|\/\\\\)(\s){1}#is + $name = $this->sanitize($_POST['name']); + $website = $this->sanitize($_POST['website']); + $post = $this->sanitize($_POST['post']); + foreach($this->bad_words as $w) + { + if(!$this->spamcheck($post, $w)) die('The word "'.$w.'" has been detected in your message and as a result your post has been blocked. Don\'t argue, that will only get you banned.'); + } + $post = $this->make_clickable($post); + $post = preg_replace('#_(.*?)_#is', '\\1', $post); + $post = preg_replace('#\*(.*?)\*#is', '\\1', $post); + if(!$this->can_post) die('Access to posting messages has been denied because the administrator has set that you must be logged into this website in order to post.'); + $this->sql('INSERT INTO '.$this->table_prefix.'ajim ( name, website, post, time_id, sid, ip_addr ) VALUES(\''.$name.'\', \''.$website.'\', \''.$post.'\', '.time().', \''.mysql_real_escape_string($this->get_sid()).'\', \''.mysql_real_escape_string($_SERVER['REMOTE_ADDR']).'\');'); + case 'view': + // if(isset($_GET['ajim_auth'])) + // die('Auth: '.$_GET['ajim_auth']); // .'
Pw: '.$this->admin); + if(isset($_GET['latest']) && ( isset($this->config['allow_looping']) && $this->config['allow_looping'] == true )) + { + // Determine max execution time + $max_exec = intval(@ini_get('max_execution_time')); + if(!$max_exec) $max_exec = 30; + $time_left = $max_exec - 1; + } + $q = $this->sql('SELECT name, website, post, post_id, time_id, sid, ip_addr FROM '.$this->table_prefix.'ajim ORDER BY post_id;'); + if(mysql_num_rows($q) < 1) echo '0 No posts.'; + else { + // Prune the table + if($this->prune > 0) { + $nr = mysql_num_rows($q); + $nr = $nr - $this->prune; + if($nr > 0) $this->sql('DELETE FROM '.$this->table_prefix.'ajim LIMIT '.$nr.';'); + } + // Alright, what we want to do here is grab the entire table, load it into an array, and then display the posts in reverse order. + for($i = 1; $i<=mysql_num_rows($q); $i++) { + $t[$i] = mysql_fetch_object($q); + } + + $s = sizeof($t); + + if(isset($_GET['latest']) && ( isset($this->config['allow_looping']) && $this->config['allow_looping'] == true )) + { + // When I was coding this, I immediately thought "use labels and goto!" Here's hoping, PHP6 :-) + $latest_from_user = intval($_GET['latest']); + $latest_from_db = intval($t[$s]->time_id); + while(true) + { + if($latest_from_user == $latest_from_db && $time_left > 5) + { + $time_left = $time_left - 5; + sleep(5); + mysql_free_result($q); + $q = $this->sql('SELECT name, website, post, post_id, time_id, sid, ip_addr FROM '.$this->table_prefix.'ajim ORDER BY post_id;'); + $t = Array(); + for($i = 1; $i<=mysql_num_rows($q); $i++) { + $t[$i] = mysql_fetch_object($q); + } + $s = sizeof($t); + $latest_from_user = intval($_GET['latest']); + $latest_from_db = intval($t[$s]->time_id); + //echo (string)$latest_from_db.'
'; + //flush(); + //exit; + if($latest_from_user != $latest_from_db) + break; + continue; + } + elseif($latest_from_user == $latest_from_db && $time_left < 5) + { + die('[E] No new posts'); + } + break; + } + } + + echo $t[$s]->time_id . ' '; + + // This is my favorite array trick - it baffles everyone who looks at it :-D + // What it does is the same as for($i=0;$i 0; $i--) { + if($this->formatfunc) + { + $p = @call_user_func($this->formatfunc, $t[$i]->post); + if($p) $t[$i]->post = $p; + unset($p); // Free some memory + $good_tags = Array('b', 'i', 'u', 'br'); + $gt = implode('|', $good_tags); + + // Override any modifications that may have been made to the HTML + $t[$i]->post = preg_replace('#<('.$gt.')>([^.]+)</\\1>#is', '<\\1>\\2', $t[$i]->post); + $t[$i]->post = preg_replace('#<('.$gt.')([ ]*?)/>#is', '<\\1 />', $t[$i]->post); + $t[$i]->post = preg_replace('#<('.$gt.')>#is', '<\\1 />', $t[$i]->post); + } + echo '
'; + if($t[$i]->website != '') echo ''.$t[$i]->name.''; + else echo ''.$t[$i]->name.''; + echo ' '; + if( $this->can_post && ($t[$i]->sid == $this->get_sid() && $t[$i]->ip_addr == $_SERVER['REMOTE_ADDR'] ) || ( isset($_GET['ajim_auth']) && $_GET['ajim_auth']==$this->admin ) ) + echo 'Delete Edit'; + echo '
Posted on '.date('n/j, g:ia', $t[$i]->time_id).'
'; + echo '
'.$t[$i]->post.'
'; + echo '
'; + } + } + break; + case 'auth': + if($_POST['ajim_auth']==$this->admin) echo 'good'; + else echo 'The password you entered is invalid.'; + break; + } + } + } + + /** + * Replace URLs within a block of text with anchors + * Written by Nathan Codding, copyright (C) phpBB Group + * @param string $text - the text to process + * @return string + */ + function make_clickable($text) + { + $text = preg_replace('#(script|about|applet|activex|chrome):#is', "\\1:", $text); + $ret = ' ' . $text; + $ret = preg_replace('#(^|[\n ])([\w]+?://[\w\#$%&~/.\-;:=,?@\[\]+]*)#is', '\\1\\2', $ret); + $ret = preg_replace("#(^|[\ n ])((www|ftp)\.[\w\#$%&~/.\-;:=,?@\[\]+]*)#is", '\\1\\2', $ret); + $ret = preg_replace("#(^|[\n ])([a-z0-9&\-_.]+?)@([\w\-]+\.([\w\-\.]+\.)*[\w]+)#i", '\\1\\2@\\3', $ret); + $ret = substr($ret, 1); + return($ret); + } +} + +// The client-side javascript and CSS code + +if(isset($_GET['js']) && isset($_GET['id']) && isset($_GET['path']) && isset($_GET['pfx'])) { + header('Content-type: text/javascript'); + ?> + //