From 461084d400620c32ef262aab1f837573f47b0951 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Tue, 30 Jul 2013 16:41:10 -0400 Subject: [PATCH 01/11] MySQL's utf8 charset only supports up to 3-byte symbols. Insterting four byte symbols (U+010000 to U+10FFFF) can be done maliciously to break HTML mark-up. The ideal solution was to convert to MySQL's utf8mb4 charset, but then we would lose support for MySQL < 5.5.3. In this fix, incompatible characters are encoded as HTML numeric character references (eg. #65536) and just stripped from body_nommarkup. --- inc/functions.php | 2 +- post.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 2915e1b6..4af7891a 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -1523,7 +1523,7 @@ function markup(&$body, $track_cites = false) { } function utf8tohtml($utf8) { - return htmlspecialchars($utf8, ENT_NOQUOTES, 'UTF-8'); + return mb_encode_numericentity(htmlspecialchars($utf8, ENT_NOQUOTES, 'UTF-8'), array(0x010000, 0xffffff, 0, 0xffffff), 'UTF-8'); } function ordutf8($string, &$offset) { diff --git a/post.php b/post.php index ad045b66..e9041ce8 100644 --- a/post.php +++ b/post.php @@ -378,7 +378,7 @@ if (isset($_POST['delete'])) { wordfilters($post['body']); - $post['body_nomarkup'] = $post['body']; + $post['body_nomarkup'] = preg_replace('/[\x{010000}-\x{ffffff}]/u', '', $post['body']); if (!($mod && isset($post['raw']) && $post['raw'])) $post['tracked_cites'] = markup($post['body'], true); From 91bb3182da63e64d45ab8ac26735e090db928f15 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Tue, 30 Jul 2013 19:07:28 -0400 Subject: [PATCH 02/11] =?UTF-8?q?=E2=8C=98+click=20should=20open=20image?= =?UTF-8?q?=20in=20new=20tab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- js/inline-expanding.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/inline-expanding.js b/js/inline-expanding.js index 06d3f76f..20383f04 100644 --- a/js/inline-expanding.js +++ b/js/inline-expanding.js @@ -21,7 +21,7 @@ onready(function(){ link[i].onclick = function(e) { if (this.childNodes[0].className == 'hidden') return false; - if (e.which == 2) + if (e.which == 2 || e.metaKey) return true; if (!this.dataset.src) { this.dataset.expanded = 'true'; From 8efab06616bfcdab49fb9e996cdc7ddfe79426bb Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Tue, 30 Jul 2013 22:08:56 -0400 Subject: [PATCH 03/11] Upgrade to utf8mb4 charset for MySQL server versions above 5.5.3. Keep support for older versions. Fix charsets for multiple columns and tables. --- inc/database.php | 21 ++++++++-- inc/functions.php | 5 ++- install.php | 100 +++++++++++++++++++++++++++++++++++++++++++- install.sql | 95 ++++++++++++++++++++--------------------- post.php | 5 ++- templates/posts.sql | 63 ++++++++++++++-------------- 6 files changed, 202 insertions(+), 87 deletions(-) diff --git a/inc/database.php b/inc/database.php index a0edd1fb..3dc0f0ce 100644 --- a/inc/database.php +++ b/inc/database.php @@ -51,12 +51,16 @@ function sql_open() { try { $options = array( PDO::ATTR_TIMEOUT => $config['db']['timeout'], - PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true ); if ($config['db']['persistent']) $options[PDO::ATTR_PERSISTENT] = true; - return $pdo = new PDO($dsn, $config['db']['user'], $config['db']['password'], $options); + $pdo = new PDO($dsn, $config['db']['user'], $config['db']['password'], $options); + if (mysql_version() >= 50503) + query('SET NAMES utf8mb4') or error(db_error()); + else + query('SET NAMES utf8') or error(db_error()); + return $pdo; } catch(PDOException $e) { $message = $e->getMessage(); @@ -65,10 +69,21 @@ function sql_open() { $message = str_replace($config['db']['password'], 'hidden', $message); // Print error - error('Database error: ' . $message); + error(_('Database error: ') . $message); } } +// 5.6.10 becomes 50610 +function mysql_version() { + global $pdo; + + $version = $pdo->getAttribute(PDO::ATTR_SERVER_VERSION); + $v = explode('.', $version); + if (count($v) != 3) + return false; + return (int) sprintf("%02d%02d%02d", $v[0], $v[1], $v[2]); +} + function prepare($query) { global $pdo, $debug, $config; diff --git a/inc/functions.php b/inc/functions.php index 4af7891a..d5692be5 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -1399,6 +1399,9 @@ function markup(&$body, $track_cites = false) { $body = str_replace("\r", '', $body); $body = utf8tohtml($body); + if (mysql_version() < 50503) + $body = mb_encode_numericentity($body, array(0x010000, 0xffffff, 0, 0xffffff), 'UTF-8'); + foreach ($config['markup'] as $markup) { if (is_string($markup[1])) { $body = preg_replace($markup[0], $markup[1], $body); @@ -1523,7 +1526,7 @@ function markup(&$body, $track_cites = false) { } function utf8tohtml($utf8) { - return mb_encode_numericentity(htmlspecialchars($utf8, ENT_NOQUOTES, 'UTF-8'), array(0x010000, 0xffffff, 0, 0xffffff), 'UTF-8'); + return htmlspecialchars($utf8, ENT_NOQUOTES, 'UTF-8'); } function ordutf8($string, &$offset) { diff --git a/install.php b/install.php index 08e8046a..c2fe1a55 100644 --- a/install.php +++ b/install.php @@ -1,7 +1,7 @@ execute() or error(db_error($_query)); } } + case 'v0.9.6-dev-9': + function __query($sql) { + if (mysql_version() >= 50503) + return query($sql); + else + return query(str_replace('utf8mb4', 'utf8', $sql)); + } + + foreach ($boards as &$board) { + __query(sprintf("ALTER TABLE `posts_%s` + CHANGE `subject` `subject` VARCHAR(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + CHANGE `email` `email` VARCHAR(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + CHANGE `name` `name` VARCHAR(35) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + CHANGE `trip` `trip` VARCHAR(15) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + CHANGE `capcode` `capcode` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + CHANGE `body` `body` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, + CHANGE `body_nomarkup` `body_nomarkup` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + CHANGE `thumb` `thumb` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + CHANGE `thumbwidth` `thumbwidth` INT(11) NULL DEFAULT NULL, + CHANGE `thumbheight` `thumbheight` INT(11) NULL DEFAULT NULL, + CHANGE `file` `file` VARCHAR(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + CHANGE `filename` `filename` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + CHANGE `filehash` `filehash` TEXT CHARACTER SET ascii COLLATE ascii_general_ci NULL DEFAULT NULL, + CHANGE `password` `password` VARCHAR(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + CHANGE `ip` `ip` VARCHAR(39) CHARACTER SET ascii COLLATE ascii_general_ci NOT NULL, + CHANGE `embed` `embed` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;", $board['uri'])) or error(db_error()); + } + + __query("ALTER TABLE `antispam` + CHANGE `board` `board` VARCHAR( 120 ) CHARACTER SET ASCII COLLATE ascii_general_ci NOT NULL , + CHANGE `hash` `hash` CHAR( 40 ) CHARACTER SET ASCII COLLATE ascii_bin NOT NULL , + DEFAULT CHARACTER SET ASCII COLLATE ascii_bin;") or error(db_error()); + __query("ALTER TABLE `bans` + CHANGE `ip` `ip` VARCHAR( 39 ) CHARACTER SET ASCII COLLATE ascii_general_ci NOT NULL , + CHANGE `reason` `reason` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL , + CHANGE `board` `board` VARCHAR( 120 ) CHARACTER SET ASCII COLLATE ascii_general_ci NULL DEFAULT NULL, + DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;") or error(db_error()); + __query("ALTER TABLE `boards` + CHANGE `uri` `uri` VARCHAR( 120 ) CHARACTER SET ASCII COLLATE ascii_general_ci NOT NULL , + CHANGE `title` `title` TINYTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL , + CHANGE `subtitle` `subtitle` TINYTEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;") or error(db_error()); + __query("ALTER TABLE `cites` + CHANGE `board` `board` VARCHAR( 120 ) CHARACTER SET ASCII COLLATE ascii_general_ci NOT NULL , + CHANGE `target_board` `target_board` VARCHAR( 120 ) CHARACTER SET ASCII COLLATE ascii_general_ci NOT NULL , + DEFAULT CHARACTER SET ASCII COLLATE ascii_general_ci;") or error(db_error()); + __query("ALTER TABLE `ip_notes` + CHANGE `ip` `ip` VARCHAR( 39 ) CHARACTER SET ASCII COLLATE ascii_general_ci NOT NULL , + CHANGE `body` `body` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL , + DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;") or error(db_error()); + __query("ALTER TABLE `ip_notes` + CHANGE `ip` `ip` VARCHAR( 39 ) CHARACTER SET ASCII COLLATE ascii_general_ci NOT NULL , + CHANGE `body` `body` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL , + DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;") or error(db_error()); + __query("ALTER TABLE `modlogs` + CHANGE `ip` `ip` VARCHAR( 39 ) CHARACTER SET ASCII COLLATE ascii_general_ci NOT NULL , + CHANGE `board` `board` VARCHAR( 120 ) CHARACTER SET ASCII COLLATE ascii_general_ci NULL DEFAULT NULL , + CHANGE `text` `text` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL , + DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;") or error(db_error()); + __query("ALTER TABLE `mods` + CHANGE `username` `username` VARCHAR( 30 ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL , + CHANGE `password` `password` CHAR( 64 ) CHARACTER SET ASCII COLLATE ascii_general_ci NOT NULL COMMENT 'SHA256', + CHANGE `salt` `salt` CHAR( 32 ) CHARACTER SET ASCII COLLATE ascii_general_ci NOT NULL , + CHANGE `boards` `boards` TEXT CHARACTER SET ASCII COLLATE ascii_general_ci NOT NULL , + DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;") or error(db_error()); + __query("ALTER TABLE `mutes` + CHANGE `ip` `ip` VARCHAR( 39 ) CHARACTER SET ASCII COLLATE ascii_general_ci NOT NULL , + DEFAULT CHARACTER SET ASCII COLLATE ascii_general_ci;") or error(db_error()); + __query("ALTER TABLE `news` + CHANGE `name` `name` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL , + CHANGE `subject` `subject` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL , + CHANGE `body` `body` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL , + DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;") or error(db_error()); + __query("ALTER TABLE `noticeboard` + CHANGE `subject` `subject` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL , + CHANGE `body` `body` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL , + DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;") or error(db_error()); + __query("ALTER TABLE `pms` + CHANGE `message` `message` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL , + DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;") or error(db_error()); + __query("ALTER TABLE `reports` + CHANGE `ip` `ip` VARCHAR( 39 ) CHARACTER SET ASCII COLLATE ascii_general_ci NOT NULL , + CHANGE `board` `board` VARCHAR( 120 ) CHARACTER SET ASCII COLLATE ascii_general_ci NULL DEFAULT NULL , + CHANGE `reason` `reason` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL , + DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;") or error(db_error()); + __query("ALTER TABLE `robot` + CHANGE `hash` `hash` VARCHAR( 40 ) CHARACTER SET ASCII COLLATE ascii_bin NOT NULL COMMENT 'SHA1', + DEFAULT CHARACTER SET ASCII COLLATE ascii_bin;") or error(db_error()); + __query("ALTER TABLE `theme_settings` + CHANGE `theme` `theme` VARCHAR( 40 ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL , + CHANGE `name` `name` VARCHAR( 40 ) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL , + CHANGE `value` `value` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL , + DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;") or eror(db_error()); case false: // Update version number file_write($config['has_installed'], VERSION); @@ -547,6 +641,10 @@ if ($step == 0) { $sql = @file_get_contents('install.sql') or error("Couldn't load install.sql."); + sql_open(); + if (mysql_version() < 50503) + $sql = str_replace('utf8', 'utf8mb4', $sql); + // This code is probably horrible, but what I'm trying // to do is find all of the SQL queires and put them // in an array. diff --git a/install.sql b/install.sql index ee2ca8a9..c4e82820 100644 --- a/install.sql +++ b/install.sql @@ -1,25 +1,20 @@ -- phpMyAdmin SQL Dump --- version 3.4.2 +-- version 4.0.4.1 -- http://www.phpmyadmin.net -- -- Host: localhost --- Generation Time: Apr 12, 2012 at 11:22 PM --- Server version: 5.1.61 --- PHP Version: 5.3.3-7+squeeze8 +-- Generation Time: Jul 30, 2013 at 09:45 PM +-- Server version: 5.6.10 +-- PHP Version: 5.3.15 -SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; SET time_zone = "+00:00"; - /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; --- --- Database: `tinyboard` --- - -- -------------------------------------------------------- -- @@ -27,15 +22,15 @@ SET time_zone = "+00:00"; -- CREATE TABLE IF NOT EXISTS `antispam` ( - `board` varchar(255) NOT NULL, + `board` varchar(120) CHARACTER SET ascii NOT NULL, `thread` int(11) DEFAULT NULL, - `hash` char(40) NOT NULL, + `hash` char(40) COLLATE ascii_bin NOT NULL, `created` int(11) NOT NULL, `expires` int(11) DEFAULT NULL, `passed` smallint(6) NOT NULL, PRIMARY KEY (`hash`), KEY `board` (`board`,`thread`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) ENGINE=MyISAM DEFAULT CHARSET=ascii COLLATE=ascii_bin; -- -------------------------------------------------------- @@ -44,17 +39,17 @@ CREATE TABLE IF NOT EXISTS `antispam` ( -- CREATE TABLE IF NOT EXISTS `bans` ( - `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, - `ip` varchar(45) NOT NULL, + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `ip` varchar(39) CHARACTER SET ascii NOT NULL, `mod` int(11) NOT NULL COMMENT 'which mod made the ban', `set` int(11) NOT NULL, `expires` int(11) DEFAULT NULL, `reason` text, - `board` varchar(120) DEFAULT NULL, + `board` varchar(120) CHARACTER SET ascii DEFAULT NULL, `seen` tinyint(1) NOT NULL, PRIMARY KEY (`id`), FULLTEXT KEY `ip` (`ip`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ; -- -------------------------------------------------------- @@ -63,18 +58,18 @@ CREATE TABLE IF NOT EXISTS `bans` ( -- CREATE TABLE IF NOT EXISTS `boards` ( - `uri` varchar(120) NOT NULL, + `uri` varchar(120) CHARACTER SET ascii NOT NULL, `title` tinytext NOT NULL, `subtitle` tinytext, PRIMARY KEY (`uri`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; -- -- Dumping data for table `boards` -- -INSERT INTO `boards` (`uri`, `title`, `subtitle`) VALUES -('b', 'Beta', 'In development.'); +INSERT INTO `boards` VALUES +('b', 'Random', NULL); -- -------------------------------------------------------- @@ -83,13 +78,13 @@ INSERT INTO `boards` (`uri`, `title`, `subtitle`) VALUES -- CREATE TABLE IF NOT EXISTS `cites` ( - `board` varchar(8) NOT NULL, + `board` varchar(120) NOT NULL, `post` int(11) NOT NULL, - `target_board` varchar(8) NOT NULL, + `target_board` varchar(120) NOT NULL, `target` int(11) NOT NULL, KEY `target` (`target_board`,`target`), KEY `post` (`board`,`post`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) ENGINE=MyISAM DEFAULT CHARSET=ascii; -- -------------------------------------------------------- @@ -98,14 +93,14 @@ CREATE TABLE IF NOT EXISTS `cites` ( -- CREATE TABLE IF NOT EXISTS `ip_notes` ( - `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, - `ip` varchar(45) NOT NULL, + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `ip` varchar(39) CHARACTER SET ascii NOT NULL, `mod` int(11) DEFAULT NULL, `time` int(11) NOT NULL, `body` text NOT NULL, UNIQUE KEY `id` (`id`), KEY `ip` (`ip`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ; -- -------------------------------------------------------- @@ -115,12 +110,12 @@ CREATE TABLE IF NOT EXISTS `ip_notes` ( CREATE TABLE IF NOT EXISTS `modlogs` ( `mod` int(11) NOT NULL, - `ip` varchar(45) NOT NULL, - `board` varchar(120) DEFAULT NULL, + `ip` varchar(39) CHARACTER SET ascii NOT NULL, + `board` varchar(120) CHARACTER SET ascii DEFAULT NULL, `time` int(11) NOT NULL, `text` text NOT NULL, KEY `time` (`time`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; -- -------------------------------------------------------- @@ -131,19 +126,19 @@ CREATE TABLE IF NOT EXISTS `modlogs` ( CREATE TABLE IF NOT EXISTS `mods` ( `id` smallint(6) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(30) NOT NULL, - `password` char(64) NOT NULL COMMENT 'SHA256', - `salt` char(32) NOT NULL, + `password` char(64) CHARACTER SET ascii NOT NULL COMMENT 'SHA256', + `salt` char(32) CHARACTER SET ascii NOT NULL, `type` smallint(1) NOT NULL COMMENT '0: janitor, 1: mod, 2: admin', - `boards` text NOT NULL, + `boards` text CHARACTER SET ascii NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `id` (`id`,`username`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ; +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=4 ; -- -- Dumping data for table `mods` -- -INSERT INTO `mods` (`id`, `username`, `password`, `salt`, `type`, `boards`) VALUES +INSERT INTO `mods` VALUES (1, 'admin', 'cedad442efeef7112fed0f50b011b2b9bf83f6898082f995f69dd7865ca19fb7', '4a44c6c55df862ae901b413feecb0d49', 2, '*'); -- -------------------------------------------------------- @@ -153,10 +148,10 @@ INSERT INTO `mods` (`id`, `username`, `password`, `salt`, `type`, `boards`) VALU -- CREATE TABLE IF NOT EXISTS `mutes` ( - `ip` varchar(45) NOT NULL, + `ip` varchar(39) NOT NULL, `time` int(11) NOT NULL, KEY `ip` (`ip`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) ENGINE=MyISAM DEFAULT CHARSET=ascii; -- -------------------------------------------------------- @@ -165,14 +160,14 @@ CREATE TABLE IF NOT EXISTS `mutes` ( -- CREATE TABLE IF NOT EXISTS `news` ( - `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` text NOT NULL, `time` int(11) NOT NULL, `subject` text NOT NULL, `body` text NOT NULL, UNIQUE KEY `id` (`id`), KEY `time` (`time`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ; -- -------------------------------------------------------- @@ -181,13 +176,13 @@ CREATE TABLE IF NOT EXISTS `news` ( -- CREATE TABLE IF NOT EXISTS `noticeboard` ( - `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `mod` int(11) NOT NULL, `time` int(11) NOT NULL, `subject` text NOT NULL, `body` text NOT NULL, UNIQUE KEY `id` (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ; -- -------------------------------------------------------- @@ -196,14 +191,14 @@ CREATE TABLE IF NOT EXISTS `noticeboard` ( -- CREATE TABLE IF NOT EXISTS `pms` ( - `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `sender` int(11) NOT NULL, `to` int(11) NOT NULL, `message` text NOT NULL, `time` int(11) NOT NULL, `unread` tinyint(1) NOT NULL, PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ; -- -------------------------------------------------------- @@ -212,14 +207,14 @@ CREATE TABLE IF NOT EXISTS `pms` ( -- CREATE TABLE IF NOT EXISTS `reports` ( - `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `time` int(11) NOT NULL, - `ip` varchar(45) NOT NULL, - `board` varchar(120) DEFAULT NULL, + `ip` varchar(39) CHARACTER SET ascii NOT NULL, + `board` varchar(120) CHARACTER SET ascii DEFAULT NULL, `post` int(11) NOT NULL, `reason` text NOT NULL, PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=3 ; -- -------------------------------------------------------- @@ -228,9 +223,9 @@ CREATE TABLE IF NOT EXISTS `reports` ( -- CREATE TABLE IF NOT EXISTS `robot` ( - `hash` varchar(40) NOT NULL COMMENT 'SHA1', + `hash` varchar(40) COLLATE ascii_bin NOT NULL COMMENT 'SHA1', PRIMARY KEY (`hash`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) ENGINE=MyISAM DEFAULT CHARSET=ascii COLLATE=ascii_bin; -- -------------------------------------------------------- @@ -243,7 +238,7 @@ CREATE TABLE IF NOT EXISTS `theme_settings` ( `name` varchar(40) DEFAULT NULL, `value` text, KEY `theme` (`theme`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8; +) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; diff --git a/post.php b/post.php index e9041ce8..d1749fd1 100644 --- a/post.php +++ b/post.php @@ -378,7 +378,10 @@ if (isset($_POST['delete'])) { wordfilters($post['body']); - $post['body_nomarkup'] = preg_replace('/[\x{010000}-\x{ffffff}]/u', '', $post['body']); + if (mysql_version() >= 50503) + $post['body_nomarkup'] = $post['body']; // Assume we're using the utf8mb4 charset + else + $post['body_nomarkup'] = preg_replace('/[\x{010000}-\x{ffffff}]/u', '', $post['body']); // MySQL's `utf8` charset only supports up to 3-byte symbols if (!($mod && isset($post['raw']) && $post['raw'])) $post['tracked_cites'] = markup($post['body'], true); diff --git a/templates/posts.sql b/templates/posts.sql index c766d38e..0a1dc92a 100644 --- a/templates/posts.sql +++ b/templates/posts.sql @@ -1,32 +1,33 @@ CREATE TABLE IF NOT EXISTS `posts_{{ board }}` ( - `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, - `thread` int(11) DEFAULT NULL, - `subject` varchar(100) DEFAULT NULL, - `email` varchar(30) DEFAULT NULL, - `name` varchar(35) DEFAULT NULL, - `trip` varchar(15) DEFAULT NULL, - `capcode` varchar(50) DEFAULT NULL, - `body` text NOT NULL, - `body_nomarkup` text DEFAULT NULL, - `time` int(11) NOT NULL, - `bump` int(11) DEFAULT NULL, - `thumb` varchar(50) DEFAULT NULL, - `thumbwidth` int(11) DEFAULT NULL, - `thumbheight` int(11) DEFAULT NULL, - `file` varchar(50) DEFAULT NULL, - `filewidth` int(11) DEFAULT NULL, - `fileheight` int(11) DEFAULT NULL, - `filesize` int(11) DEFAULT NULL, - `filename` text DEFAULT NULL, - `filehash` text DEFAULT NULL, - `password` varchar(20) DEFAULT NULL, - `ip` varchar(45) NOT NULL, - `sticky` int(1) NOT NULL, - `locked` int(1) NOT NULL, - `sage` int(1) NOT NULL, - `embed` text, - UNIQUE KEY `id` (`id`), - KEY `thread_id` (`thread`, `id`), - KEY `time` (`time`), - FULLTEXT KEY `body` (`body`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `thread` int(11) DEFAULT NULL, + `subject` varchar(100) DEFAULT NULL, + `email` varchar(30) DEFAULT NULL, + `name` varchar(35) DEFAULT NULL, + `trip` varchar(15) DEFAULT NULL, + `capcode` varchar(50) DEFAULT NULL, + `body` text NOT NULL, + `body_nomarkup` text, + `time` int(11) NOT NULL, + `bump` int(11) DEFAULT NULL, + `thumb` varchar(50) DEFAULT NULL, + `thumbwidth` int(11) DEFAULT NULL, + `thumbheight` int(11) DEFAULT NULL, + `file` varchar(50) DEFAULT NULL, + `filewidth` int(11) DEFAULT NULL, + `fileheight` int(11) DEFAULT NULL, + `filesize` int(11) DEFAULT NULL, + `filename` text, + `filehash` text CHARACTER SET ascii, + `password` varchar(20) DEFAULT NULL, + `ip` varchar(39) CHARACTER SET ascii NOT NULL, + `sticky` int(1) NOT NULL, + `locked` int(1) NOT NULL, + `sage` int(1) NOT NULL, + `embed` text, + UNIQUE KEY `id` (`id`), + KEY `thread_id` (`thread`,`id`), + KEY `time` (`time`), + FULLTEXT KEY `body` (`body`) + ) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1 ; + \ No newline at end of file From fbc78f76323528cab0d23fa259de984b8b9db457 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Tue, 30 Jul 2013 22:13:05 -0400 Subject: [PATCH 04/11] Fix last commit (mysql_version() doesn't work if connection is not yet established) --- install.php | 1 + 1 file changed, 1 insertion(+) diff --git a/install.php b/install.php index c2fe1a55..1f148e92 100644 --- a/install.php +++ b/install.php @@ -249,6 +249,7 @@ if (file_exists($config['has_installed'])) { } } case 'v0.9.6-dev-9': + sql_open(); function __query($sql) { if (mysql_version() >= 50503) return query($sql); From 343397d561183a72ed3a6351175b0d34ebe9ffc3 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Tue, 30 Jul 2013 23:30:49 -0400 Subject: [PATCH 05/11] ?/IP/: Don't show posts for board user doesn't have access to --- inc/mod/pages.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index d447fa51..fdf9d8e9 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -744,6 +744,8 @@ function mod_page_ip($ip) { $boards = listBoards(); foreach ($boards as $board) { openBoard($board['uri']); + if (!hasPermission($config['mod']['show_ip'], $board['uri'])) + continue; $query = prepare(sprintf('SELECT * FROM `posts_%s` WHERE `ip` = :ip ORDER BY `sticky` DESC, `id` DESC LIMIT :limit', $board['uri'])); $query->bindValue(':ip', $ip); From 889945f50cd0c2cd1b8f39d7b380133196a93a74 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Tue, 30 Jul 2013 23:36:46 -0400 Subject: [PATCH 06/11] Hide "flags" section in posting form if user doesn't have permission to do anything special --- templates/post_form.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/post_form.html b/templates/post_form.html index aa051f5f..a112e2ab 100644 --- a/templates/post_form.html +++ b/templates/post_form.html @@ -85,7 +85,7 @@ {% endif %} - {% if mod %} + {% if mod and ((not id and post.mod|hasPermission(config.mod.sticky, board.uri)) or (not id and post.mod|hasPermission(config.mod.lock, board.uri)) or post.mod|hasPermission(config.mod.rawhtml, board.uri)) %} {% trans %}Flags{% endtrans %} From bb2868896c2f383a913e8e5929627660cc754b14 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 31 Jul 2013 00:04:57 -0400 Subject: [PATCH 07/11] Ability to hide IP addresses from mod log --- inc/config.php | 2 ++ templates/mod/log.html | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/inc/config.php b/inc/config.php index 582c3145..28a7a4db 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1024,6 +1024,8 @@ $config['mod']['createusers'] = ADMIN; // View the moderation log $config['mod']['modlog'] = ADMIN; + // View IP addresses of other mods in ?/log + $config['mod']['show_ip_modlog'] = ADMIN; // View relevant moderation log entries on IP address pages (ie. ban history, etc.) // Warning: Can be pretty resource exhaustive if your mod logs are huge. $config['mod']['modlog_ip'] = MOD; diff --git a/templates/mod/log.html b/templates/mod/log.html index e75330b5..0aa3f110 100644 --- a/templates/mod/log.html +++ b/templates/mod/log.html @@ -18,7 +18,11 @@ {% endif %} - {{ log.ip }} + {% if mod|hasPermission(config.mod.show_ip_modlog) %} + {{ log.ip }} + {% else %} + hidden + {% endif %} {{ log.time|ago }} From 42d29657a4fd9cba4ce3db6912dd3696b4a5f83e Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 31 Jul 2013 00:29:49 -0400 Subject: [PATCH 08/11] README update --- README.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 50dcc621..7bd6f461 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,21 @@ license. It is written in PHP and has few dependencies. Requirements ------------ 1. PHP >= 5.2.5 -2. [mbstring](http://www.php.net/manual/en/mbstring.installation.php) - (--enable-mbstring) -3. [PHP-GD](http://php.net/manual/en/book.image.php) -4. [PHP-PDO](http://php.net/manual/en/book.pdo.php) - (only MySQL is supported at the moment) +2. MySQL server +3. [mbstring](http://www.php.net/manual/en/mbstring.installation.php) +4. [PHP GD](http://www.php.net/manual/en/intro.image.php) +5. [PHP PDO](http://www.php.net/manual/en/intro.pdo.php) We try to make sure Tinyboard is compatible with all major web servers and operating systems. Tinyboard does not include an Apache ```.htaccess``` file nor does it need one. +### Recommended +1. PHP >= 5.3 +2. MySQL server >= 5.5.3 +3. ImageMagick or command-line version (```convert``` and ```identify```) +4. [APC (Alternative PHP Cache)](http://php.net/manual/en/book.apc.php), [XCache](http://xcache.lighttpd.net/) or [Memcached](http://www.php.net/manual/en/intro.memcached.php) + Contributing ------------ You can contribute to Tinyboard by: @@ -58,7 +63,7 @@ find support from a variety of sources: * Documentation can be found [here](http://tinyboard.org/docs/). * You can join Tinyboard's IRC channel for support and general queries: [irc.datnode.net #tinyboard](irc://irc.datnode.net/tinyboard). -* You can find enterprise-grade support at [tinyboard.org](http://tinyboard.org/#support). +* You may find help at [tinyboard.org](http://tinyboard.org/#help). License -------- From 3bee1ba4fd16263ac4eefdf0bc8c7a3d11d8cccb Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 31 Jul 2013 00:39:00 -0400 Subject: [PATCH 09/11] Issue #61 --- js/expand.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/js/expand.js b/js/expand.js index 16a0efb2..6e5fa659 100644 --- a/js/expand.js +++ b/js/expand.js @@ -1,9 +1,9 @@ /* - *expand.js + * expand.js * https://github.com/savetheinternet/Tinyboard/blob/master/js/expand.js * * Released under the MIT license - * Copyright (c) 2012 Michael Save + * Copyright (c) 2012-2013 Michael Save * * Usage: * $config['additional_javascript'][] = 'js/jquery.min.js'; @@ -35,9 +35,8 @@ $(document).ready(function(){ $(this).addClass('expanded').insertAfter(thread.find('div.post:first')).after('
'); } last_expanded = $(this); - - } - else { + $(document).trigger('new_post', this); + } else { last_expanded = post_in_doc; } }); From 42cba538878cb695ba8b3093f72bc66ccd2fa074 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 31 Jul 2013 02:08:55 -0400 Subject: [PATCH 10/11] Allow Unicode in board URIs. --- inc/config.php | 3 +++ inc/display.php | 6 +++--- inc/functions.php | 8 ++++---- inc/mod/pages.php | 9 ++++----- install.php | 23 ++++++++++++++++++----- install.sql | 22 +++++++++++----------- mod.php | 33 +++++++++++++++++---------------- post.php | 6 +++--- 8 files changed, 63 insertions(+), 47 deletions(-) diff --git a/inc/config.php b/inc/config.php index 28a7a4db..36518028 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1130,6 +1130,9 @@ // 'type' => 'scp' //); + // Regex for board URIs + $config['board_regex'] = '[0-9a-zA-Z$_\x{0080}-\x{FFFF}]{1,58}'; + // Complex regular expression to catch URLs $config['url_regex'] = '/' . '(https?|ftp):\/\/' . '(([\w\-]+\.)+[a-zA-Z]{2,6}|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' . '(:\d+)?' . '(\/([\w\-~.#\/?=&;:+%!*\[\]@$\'()+,|\^]+)?)?' . '/'; diff --git a/inc/display.php b/inc/display.php index a7e4826e..0992166d 100644 --- a/inc/display.php +++ b/inc/display.php @@ -235,7 +235,7 @@ function bidi_cleanup($str){ function secure_link_confirm($text, $title, $confirm_message, $href) { global $config; - return '' . $text . ''; + return '' . $text . ''; } function secure_link($href) { return $href . '/' . make_secure_link_token($href); @@ -299,7 +299,7 @@ class Post { // Fix internal links // Very complicated regex $this->body = preg_replace( - '/ ':?/', // redirect to dashboard @@ -45,7 +45,7 @@ $pages = array( '/news/(\d+)' => 'news', // view news '/news/delete/(\d+)' => 'news_delete', // delete from news - '/edit/(\w+)' => 'edit_board', // edit board details + '/edit/(\%b)' => 'edit_board', // edit board details '/new-board' => 'new_board', // create a new board '/rebuild' => 'rebuild', // rebuild static files @@ -63,15 +63,15 @@ $pages = array( // CSRF-protected moderator actions '/ban' => 'secure_POST ban', // new ban - '/(\w+)/ban(&delete)?/(\d+)' => 'secure_POST ban_post', // ban poster - '/(\w+)/move/(\d+)' => 'secure_POST move', // move thread - '/(\w+)/edit(_raw)?/(\d+)' => 'secure_POST edit_post', // edit post - '/(\w+)/delete/(\d+)' => 'secure delete', // delete post - '/(\w+)/deletefile/(\d+)' => 'secure deletefile', // delete file from post - '/(\w+)/deletebyip/(\d+)(/global)?' => 'secure deletebyip', // delete all posts by IP address - '/(\w+)/(un)?lock/(\d+)' => 'secure lock', // lock thread - '/(\w+)/(un)?sticky/(\d+)' => 'secure sticky', // sticky thread - '/(\w+)/bump(un)?lock/(\d+)' => 'secure bumplock', // "bumplock" thread + '/(\%b)/ban(&delete)?/(\d+)' => 'secure_POST ban_post', // ban poster + '/(\%b)/move/(\d+)' => 'secure_POST move', // move thread + '/(\%b)/edit(_raw)?/(\d+)' => 'secure_POST edit_post', // edit post + '/(\%b)/delete/(\d+)' => 'secure delete', // delete post + '/(\%b)/deletefile/(\d+)' => 'secure deletefile', // delete file from post + '/(\%b)/deletebyip/(\d+)(/global)?' => 'secure deletebyip', // delete all posts by IP address + '/(\%b)/(un)?lock/(\d+)' => 'secure lock', // lock thread + '/(\%b)/(un)?sticky/(\d+)' => 'secure sticky', // sticky thread + '/(\%b)/bump(un)?lock/(\d+)' => 'secure bumplock', // "bumplock" thread '/themes' => 'themes_list', // manage themes '/themes/(\w+)' => 'theme_configure', // configure/reconfigure theme @@ -86,10 +86,10 @@ $pages = array( '/debug/sql' => 'secure_POST debug_sql', // This should always be at the end: - '/(\w+)/' => 'view_board', - '/(\w+)/' . preg_quote($config['file_index'], '!') => 'view_board', - '/(\w+)/' . str_replace('%d', '(\d+)', preg_quote($config['file_page'], '!')) => 'view_board', - '/(\w+)/' . preg_quote($config['dir']['res'], '!') . + '/(\%b)/' => 'view_board', + '/(\%b)/' . preg_quote($config['file_index'], '!') => 'view_board', + '/(\%b)/' . str_replace('%d', '(\d+)', preg_quote($config['file_page'], '!')) => 'view_board', + '/(\%b)/' . preg_quote($config['dir']['res'], '!') . str_replace('%d', '(\d+)', preg_quote($config['file_page'], '!')) => 'view_thread', ); @@ -109,7 +109,8 @@ $new_pages = array(); foreach ($pages as $key => $callback) { if (is_string($callback) && preg_match('/^secure /', $callback)) $key .= '(/(?P[a-f0-9]{8}))?'; - $new_pages[@$key[0] == '!' ? $key : '!^' . $key . '(?:&[^&=]+=[^&]*)*$!'] = $callback; + $key = str_replace('\%b', $config['board_regex'], $key); + $new_pages[@$key[0] == '!' ? $key : '!^' . $key . '(?:&[^&=]+=[^&]*)*$!u'] = $callback; } $pages = $new_pages; diff --git a/post.php b/post.php index d1749fd1..85dc2d05 100644 --- a/post.php +++ b/post.php @@ -170,7 +170,7 @@ if (isset($_POST['delete'])) { error($config['error']['bot']); // Check the referrer - if (!isset($_SERVER['HTTP_REFERER']) || !preg_match($config['referer_match'], $_SERVER['HTTP_REFERER'])) + if (!isset($_SERVER['HTTP_REFERER']) || !preg_match($config['referer_match'], urldecode($_SERVER['HTTP_REFERER']))) error($config['error']['referer']); checkDNSBL(); @@ -547,9 +547,9 @@ if (isset($_POST['delete'])) { // Remove board directories before inserting them into the database. if ($post['has_file']) { $post['file_path'] = $post['file']; - $post['file'] = substr_replace($post['file'], '', 0, mb_strlen($board['dir'] . $config['dir']['img'])); + $post['file'] = mb_substr($post['file'], mb_strlen($board['dir'] . $config['dir']['img'])); if ($is_an_image && $post['thumb'] != 'spoiler') - $post['thumb'] = substr_replace($post['thumb'], '', 0, mb_strlen($board['dir'] . $config['dir']['thumb'])); + $post['thumb'] = mb_substr($post['thumb'], mb_strlen($board['dir'] . $config['dir']['thumb'])); } $post = (object)$post; From b22bcdecf9cce8b08c48283290173996b6c8abf2 Mon Sep 17 00:00:00 2001 From: Michael Foster Date: Wed, 31 Jul 2013 02:19:58 -0400 Subject: [PATCH 11/11] Fix last commit: purge() issue --- inc/functions.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/inc/functions.php b/inc/functions.php index 6db21c54..773f8eb6 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -366,6 +366,11 @@ function boardTitle($uri) { function purge($uri) { global $config, $debug; + // Fix for Unicode + $uri = urlencode($uri); + $uri = str_replace("%2F", "/", $uri); + $uri = str_replace("%3A", ":", $uri); + if (preg_match($config['referer_match'], $config['root']) && isset($_SERVER['REQUEST_URI'])) { $uri = (str_replace('\\', '/', dirname($_SERVER['REQUEST_URI'])) == '/' ? '/' : str_replace('\\', '/', dirname($_SERVER['REQUEST_URI'])) . '/') . $uri; } else {