Revert r88399 (delete fuzz-tester). Apparently people use it ;-)
authorChad Horohoe <demon@users.mediawiki.org>
Thu, 19 May 2011 19:56:26 +0000 (19:56 +0000)
committerChad Horohoe <demon@users.mediawiki.org>
Thu, 19 May 2011 19:56:26 +0000 (19:56 +0000)
maintenance/fuzz-tester.php [new file with mode: 0644]

diff --git a/maintenance/fuzz-tester.php b/maintenance/fuzz-tester.php
new file mode 100644 (file)
index 0000000..aadb935
--- /dev/null
@@ -0,0 +1,2770 @@
+<?php
+/**
+ * Performs fuzz-style testing of MediaWiki's parser and forms.
+ *
+ * Copyright © 2006 Nick Jenkins
+ *
+ * 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 2 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, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Maintenance
+ * @author Nick Jenkins ( http://nickj.org/ ).
+
+
+Started: 18 May 2006.
+
+Description:
+  Performs fuzz-style testing of MediaWiki's parser and forms.
+
+How:
+  - Generate lots of nasty wiki text.
+  - Ask the Parser to render that wiki text to HTML, or ask MediaWiki's forms
+       to deal with that wiki text.
+  - Check MediaWiki's output for problems.
+  - Repeat.
+
+Why:
+  - To help find bugs.
+  - To help find security issues, or potential security issues.
+
+What type of problems are being checked for:
+  - Unclosed tags.
+  - Errors or interesting warnings from Tidy.
+  - PHP errors / warnings / notices.
+  - MediaWiki internal errors.
+  - Very slow responses.
+  - No response from apache.
+  - Optionally checking for malformed HTML using the W3C validator.
+
+Background:
+  Many of the wikiFuzz class methods are a modified PHP port,
+  of a "shameless" Python port, of LCAMTUF'S MANGELME:
+  - http://www.securiteam.com/tools/6Z00N1PBFK.html
+  - http://www.securityfocus.com/archive/1/378632/2004-10-15/2004-10-21/0
+
+Video:
+  There's an XviD video discussing this fuzz tester. You can get it from:
+  http://files.nickj.org/MediaWiki/Fuzz-Testing-MediaWiki-xvid.avi
+
+Requirements:
+  To run this, you will need:
+  - Command-line PHP5, with PHP-curl enabled (not all installations have this
+       enabled - try "apt-get install php5-curl" if you're on Debian to install).
+  - the Tidy standalone executable. ("apt-get install tidy").
+
+Optional:
+  - If you want to run the curl scripts, you'll need standalone curl installed
+       ("apt-get install curl")
+  - For viewing the W3C validator output on a command line, the "html2text"
+       program may be useful ("apt-get install html2text")
+
+Saving tests and test results:
+  Any of the fuzz tests which find problems are saved for later review.
+  In order to help track down problems, tests are saved in a number of
+  different formats. The default filename extensions and their meanings are:
+  - ".test.php" : PHP script that reproduces just that one problem using PHP-Curl.
+  - ".curl.sh"  : Shell script that reproduces that problem using standalone curl.
+  - ".data.bin" : The serialized PHP data so that this script can re-run the test.
+  - ".info.txt" : A human-readable text file with details of the field contents.
+
+Wiki configuration for testing:
+  You should make some additions to LocalSettings.php in order to catch the most
+  errors. Note this configuration is for **TESTING PURPOSES ONLY**, and is IN NO
+  WAY, SHAPE, OR FORM suitable for deployment on a hostile network. That said,
+  personally I find these additions to be the most helpful for testing purposes:
+
+  // --------- Start ---------
+  // Everyone can do everything. Very useful for testing, yet useless for deployment.
+  $wgGroupPermissions['*']['autoconfirmed']   = true;
+  $wgGroupPermissions['*']['block']           = true;
+  $wgGroupPermissions['*']['bot']             = true;
+  $wgGroupPermissions['*']['delete']          = true;
+  $wgGroupPermissions['*']['deletedhistory']  = true;
+  $wgGroupPermissions['*']['deleterevision']  = true;
+  $wgGroupPermissions['*']['editinterface']   = true;
+  $wgGroupPermissions['*']['hiderevision']    = true;
+  $wgGroupPermissions['*']['import']          = true;
+  $wgGroupPermissions['*']['importupload']    = true;
+  $wgGroupPermissions['*']['minoredit']       = true;
+  $wgGroupPermissions['*']['move']            = true;
+  $wgGroupPermissions['*']['patrol']          = true;
+  $wgGroupPermissions['*']['protect']         = true;
+  $wgGroupPermissions['*']['proxyunbannable'] = true;
+  $wgGroupPermissions['*']['renameuser']      = true;
+  $wgGroupPermissions['*']['reupload']        = true;
+  $wgGroupPermissions['*']['reupload-shared'] = true;
+  $wgGroupPermissions['*']['rollback']        = true;
+  $wgGroupPermissions['*']['siteadmin']       = true;
+  $wgGroupPermissions['*']['trackback']       = true;
+  $wgGroupPermissions['*']['unwatchedpages']  = true;
+  $wgGroupPermissions['*']['upload']          = true;
+  $wgGroupPermissions['*']['userrights']      = true;
+  $wgGroupPermissions['*']['renameuser']      = true;
+  $wgGroupPermissions['*']['makebot']         = true;
+  $wgGroupPermissions['*']['makesysop']       = true;
+
+  // Enable weird and wonderful options:
+                                                         // Increase default error reporting level.
+  error_reporting (E_ALL);    // At a later date could be increased to E_ALL | E_STRICT
+  $wgBlockOpenProxies = true; // Some block pages require this to be true in order to test.
+  $wgEnableUploads = true;    // enable uploads.
+  //$wgUseTrackbacks = true;  // enable trackbacks; However this breaks the viewPageTest, so currently disabled.
+  $wgDBerrorLog = "/root/mediawiki-db-error-log.txt";  // log DB errors, replace with suitable path.
+  $wgShowSQLErrors = true;    // Show SQL errors (instead of saying the query was hidden).
+  $wgShowExceptionDetails = true;  // want backtraces.
+  $wgEnableAPI = true;        // enable API.
+  $wgEnableWriteAPI = true;   // enable API.
+
+  // Install & enable Parser Hook extensions to increase code coverage. E.g.:
+  require_once("extensions/ParserFunctions/ParserFunctions.php");
+  require_once("extensions/Cite/Cite.php");
+  require_once("extensions/inputbox/inputbox.php");
+  require_once("extensions/Sort/Sort.php");
+  require_once("extensions/wikihiero/wikihiero.php");
+  require_once("extensions/CharInsert/CharInsert.php");
+  require_once("extensions/FixedImage/FixedImage.php");
+
+  // Install & enable Special Page extensions to increase code coverage. E.g.:
+  require_once("extensions/Cite/SpecialCite.php");
+  require_once("extensions/Filepath/SpecialFilepath.php");
+  require_once("extensions/Makebot/Makebot.php");
+  require_once("extensions/Makesysop/SpecialMakesysop.php");
+  require_once("extensions/Renameuser/SpecialRenameuser.php");
+  require_once("extensions/LinkSearch/LinkSearch.php");
+  // --------- End ---------
+
+  If you want to try E_STRICT error logging, add this to the above:
+  // --------- Start ---------
+  error_reporting (E_ALL | E_STRICT);
+  set_error_handler( 'error_handler' );
+  function error_handler ($type, $message, $file=__FILE__, $line=__LINE__) {
+        if ($message == "var: Deprecated. Please use the public/private/protected modifiers") return;
+        print "<br />\n<b>Strict Standards:</b> Type: <b>$type</b>:  $message in <b>$file</b> on line <b>$line</b><br />\n";
+  }
+  // --------- End ---------
+
+  Also add/change this in LocalSettings.php:
+  // --------- Start ---------
+  $wgEnableProfileInfo = true;
+  $wgDBserver = "localhost"; // replace with DB server hostname
+  // --------- End ---------
+
+Usage:
+  Run with "php fuzz-tester.php".
+  To see the various command-line options, run "php fuzz-tester.php --help".
+  To stop the script, press Ctrl-C.
+
+Console output:
+  - If requested, first any previously failed tests will be rerun.
+  - Then new tests will be generated and run. Any tests that fail will be saved,
+       and a brief message about why they failed will be printed on the console.
+  - The console will show the number of tests run, time run, number of tests
+       failed, number of tests being done per minute, and the name of the current test.
+
+TODO:
+  Some known things that could improve this script:
+  - Logging in with cookie jar storage needed for some tests (as there are some
+       pages that cannot be tested without being logged in, and which are currently
+       untested - e.g. Special:Emailuser, Special:Preferences, adding to Watchist).
+  - Testing of Timeline extension (I cannot test as ploticus has/had issues on
+       my architecture).
+
+*/
+
+// ///////////////////////// COMMAND LINE HELP ////////////////////////////////////
+
+// This is a command line script, load MediaWiki env (gives command line options);
+require_once( dirname( __FILE__ ) . '/commandLine.inc' );
+
+// if the user asked for an explanation of command line options.
+if ( isset( $options["help"] ) ) {
+       print <<<ENDS
+MediaWiki $wgVersion fuzz tester
+Usage: php {$_SERVER["SCRIPT_NAME"]} [--quiet] [--base-url=<url-to-test-wiki>]
+                                                  [--directory=<failed-test-path>] [--include-binary]
+                                                  [--w3c-validate] [--delete-passed-retests] [--help]
+                                                  [--user=<username>] [--password=<password>]
+                                                  [--rerun-failed-tests] [--max-errors=<int>]
+                                                  [--max-runtime=<num-minutes>]
+                                                  [--specific-test=<test-name>]
+
+Options:
+  --quiet                 : Hides passed tests, shows only failed tests.
+  --base-url              : URL to a wiki on which to run the tests.
+                                                       The "http://" is optional and can be omitted.
+  --directory             : Full path to directory for storing failed tests.
+                                                       Will be created if it does not exist.
+  --include-binary        : Includes non-alphanumeric characters in the tests.
+  --w3c-validate          : Validates pages using the W3C's web validator.
+                                                       Slow. Currently many pages fail validation.
+  --user                  : Login name of a valid user on your test wiki.
+  --password              : Password for the valid user on your test wiki.
+  --delete-passed-retests : Will delete retests that now pass.
+                                                       Requires --rerun-failed-tests to be meaningful.
+  --rerun-failed-tests    : Whether to rerun any previously failed tests.
+  --max-errors            : Maximum number of errors to report before exiting.
+                                                       Does not include errors from --rerun-failed-tests
+  --max-runtime           : Maximum runtime, in minutes, to run before exiting.
+                                                       Only applies to new tests, not --rerun-failed-tests
+  --specific-test         : Runs only the specified fuzz test.
+                                                       Only applies to new tests, not --rerun-failed-tests
+  --keep-passed-tests     : Saves all test files, even those that pass.
+  --help                  : Show this help message.
+
+Example:
+  If you wanted to fuzz test a nightly MediaWiki checkout using cron for 1 hour,
+  and only wanted to be informed of errors, and did not want to redo previously
+  failed tests, and wanted a maximum of 100 errors, then you could do:
+  php {$_SERVER["SCRIPT_NAME"]} --quiet --max-errors=100 --max-runtime=60
+
+
+ENDS;
+
+       exit( 0 );
+}
+
+
+// if we got command line options, check they look valid.
+$validOptions = array ( "quiet", "base-url", "directory", "include-binary",
+               "w3c-validate", "user", "password", "delete-passed-retests",
+               "rerun-failed-tests", "max-errors",
+               "max-runtime", "specific-test", "keep-passed-tests", "help" );
+if ( !empty( $options ) ) {
+       $unknownArgs = array_diff ( array_keys( $options ), $validOptions );
+       foreach ( $unknownArgs as $invalidArg ) {
+               print "Ignoring invalid command-line option: --$invalidArg\n";
+       }
+}
+
+
+// /////////////////////////// CONFIGURATION ////////////////////////////////////
+
+// URL to some wiki on which we can run our tests.
+if ( !empty( $options["base-url"] ) ) {
+       define( "WIKI_BASE_URL", $options["base-url"] );
+} else {
+       define( "WIKI_BASE_URL", $wgServer . $wgScriptPath . '/' );
+}
+
+// The directory name where we store the output.
+// Example for Windows: "c:\\temp\\wiki-fuzz"
+if ( !empty( $options["directory"] ) ) {
+       define( "DIRECTORY", $options["directory"] );
+} else {
+       define( "DIRECTORY", "{$wgUploadDirectory}/fuzz-tests" );
+}
+
+// Should our test fuzz data include binary strings?
+define( "INCLUDE_BINARY",  isset( $options["include-binary"] ) );
+
+// Whether we want to validate HTML output on the web.
+// At the moment very few generated pages will validate, so not recommended.
+define( "VALIDATE_ON_WEB", isset( $options["w3c-validate"] ) );
+// URL to use to validate our output:
+define( "VALIDATOR_URL",  "http://validator.w3.org/check" );
+
+// Location of Tidy standalone executable.
+define( "PATH_TO_TIDY",  "/usr/bin/tidy" );
+
+// The name of a user who has edited on your wiki. Used
+// when testing the Special:Contributions and Special:Userlogin page.
+if ( !empty( $options["user"] ) ) {
+       define( "USER_ON_WIKI", $options["user"] );
+} else {
+       define( "USER_ON_WIKI", "nickj" );
+}
+
+// The password of the above user. Used when testing the login page,
+// and to do this we sometimes need to login successfully.
+if ( !empty( $options["password"] ) ) {
+       define( "USER_PASSWORD", $options["password"] );
+} else {
+       // And no, this is not a valid password on any public wiki.
+       define( "USER_PASSWORD", "nickj" );
+}
+
+// If we have a test that failed, and then we run it again, and it passes,
+// do you want to delete it or keep it?
+define( "DELETE_PASSED_RETESTS", isset( $options["delete-passed-retests"] ) );
+
+// Do we want to rerun old saved tests at script startup?
+// Set to true to help catch regressions, or false if you only want new stuff.
+define( "RERUN_OLD_TESTS", isset( $options["rerun-failed-tests"] ) );
+
+// File where the database errors are logged. Should be defined in LocalSettings.php.
+define( "DB_ERROR_LOG_FILE", $wgDBerrorLog );
+
+// Run in chatty mode (all output, default), or run in quiet mode (only prints out details of failed tests)?
+define( "QUIET", isset( $options["quiet"] ) );
+
+// Keep all test files, even those that pass. Potentially useful to tracking input that causes something
+// unusual to happen, if you don't know what "unusual" is until later.
+define( "KEEP_PASSED_TESTS", isset( $options["keep-passed-tests"] ) );
+
+// The maximum runtime, if specified.
+if ( !empty( $options["max-runtime"] ) && intval( $options["max-runtime"] ) > 0 ) {
+       define( "MAX_RUNTIME", intval( $options["max-runtime"] ) );
+}
+
+// The maximum number of problems to find, if specified. Excludes retest errors.
+if ( !empty( $options["max-errors"] ) && intval( $options["max-errors"] ) > 0 ) {
+       define( "MAX_ERRORS", intval( $options["max-errors"] ) );
+}
+
+// if the user has requested a specific test (instead of all tests), and the test they asked for looks valid.
+if ( !empty( $options["specific-test"] ) ) {
+       if ( class_exists( $options["specific-test"] ) && get_parent_class( $options["specific-test"] ) == "pageTest" ) {
+               define( "SPECIFIC_TEST", $options["specific-test"] );
+       }
+       else {
+               print "Ignoring invalid --specific-test\n";
+       }
+}
+
+// Define the file extensions we'll use:
+define( "PHP_TEST" , ".test.php" );
+define( "CURL_TEST", ".curl.sh" );
+define( "DATA_FILE", ".data.bin" );
+define( "INFO_FILE", ".info.txt" );
+define( "HTML_FILE", ".wiki_preview.html" );
+
+// If it goes wrong, we want to know about it.
+error_reporting( E_ALL | E_STRICT );
+
+// //////////////  A CLASS THAT GENERATES RANDOM NASTY WIKI & HTML STRINGS  //////////////////////
+
+class wikiFuzz {
+
+       // Only some HTML tags are understood with params by MediaWiki, the rest are ignored.
+       // List the tags that accept params below, as well as what those params are.
+       public static $data = array(
+                       "B"          => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
+                       "CAPTION"    => array( "CLASS", "ID", "STYLE", "align", "lang", "dir", "title" ),
+                       "CENTER"     => array( "CLASS", "STYLE", "ID", "lang", "dir", "title" ),
+                       "DIV"        => array( "CLASS", "STYLE", "ID", "align", "lang", "dir", "title" ),
+                       "FONT"       => array( "CLASS", "STYLE", "ID", "lang", "dir", "title", "face", "size", "color" ),
+                       "H1"         => array( "STYLE", "CLASS", "ID", "align", "lang", "dir", "title" ),
+                       "H2"         => array( "STYLE", "CLASS", "ID", "align", "lang", "dir", "title" ),
+                       "HR"         => array( "STYLE", "CLASS", "ID", "WIDTH", "lang", "dir", "title", "size", "noshade" ),
+                       "LI"         => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "type", "value" ),
+                       "TABLE"      => array( "STYLE", "CLASS", "ID", "BGCOLOR", "WIDTH", "ALIGN", "BORDER", "CELLPADDING",
+                                                                  "CELLSPACING", "lang", "dir", "title", "summary", "frame", "rules" ),
+                       "TD"         => array( "STYLE", "CLASS", "ID", "BGCOLOR", "WIDTH", "ALIGN", "COLSPAN", "ROWSPAN",
+                                                                 "VALIGN", "abbr", "axis", "headers", "scope", "nowrap", "height", "lang",
+                                                                 "dir", "title", "char", "charoff" ),
+                       "TH"         => array( "STYLE", "CLASS", "ID", "BGCOLOR", "WIDTH", "ALIGN", "COLSPAN", "ROWSPAN",
+                                                                 "VALIGN", "abbr", "axis", "headers", "scope", "nowrap", "height", "lang",
+                                                                 "dir", "title", "char", "charoff" ),
+                       "TR"         => array( "CLASS", "STYLE", "ID", "BGCOLOR", "ALIGN", "VALIGN", "lang", "dir", "title", "char", "charoff" ),
+                       "UL"         => array( "CLASS", "STYLE", "ID", "lang", "dir", "title", "type" ),
+                       "P"          => array( "style", "class", "id", "align", "lang", "dir", "title" ),
+                       "blockquote" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "cite" ),
+                       "span"       => array( "CLASS", "ID", "STYLE", "align", "lang", "dir", "title" ),
+                       "code"       => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
+                       "tt"         => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
+                       "small"      => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
+                       "big"        => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
+                       "s"          => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
+                       "u"          => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
+                       "del"        => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "datetime", "cite" ),
+                       "ins"        => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "datetime", "cite" ),
+                       "sub"        => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
+                       "sup"        => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
+                       "ol"         => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "type", "start" ),
+                       "br"         => array( "CLASS", "ID", "STYLE", "title", "clear" ),
+                       "cite"       => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
+                       "var"        => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
+                       "dl"         => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
+                       "ruby"       => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
+                       "rt"         => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
+                       "rp"         => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
+                       "dt"         => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
+                       "dl"         => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
+                       "em"         => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
+                       "strong"     => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
+                       "i"          => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
+                       "thead"      => array( "CLASS", "ID", "STYLE", "lang", "dir", "title",  'align', 'char', 'charoff', 'valign' ),
+                       "tfoot"      => array( "CLASS", "ID", "STYLE", "lang", "dir", "title",  'align', 'char', 'charoff', 'valign' ),
+                       "tbody"      => array( "CLASS", "ID", "STYLE", "lang", "dir", "title",  'align', 'char', 'charoff', 'valign' ),
+                       "colgroup"   => array( "CLASS", "ID", "STYLE", "lang", "dir", "title",  'align', 'char', 'charoff', 'valign', 'span', 'width' ),
+                       "col"        => array( "CLASS", "ID", "STYLE", "lang", "dir", "title",  'align', 'char', 'charoff', 'valign', 'span', 'width' ),
+                       "pre"        => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "width" ),
+
+                       // extension tags that accept parameters:
+                       "sort"         => array( "order", "class" ),
+                       "ref"          => array( "name" ),
+                       "categorytree" => array( "hideroot", "mode", "style" ),
+                       "chemform"     => array( "link", "wikilink", "query" ),
+                       "section"      => array( "begin", "new" ),
+
+                       // older MW transclusion.
+                       "transclude"   => array( "page" ),
+                               );
+
+       // The types of the HTML that we will be testing were defined above
+       // Note: this needs to be initialized later to be equal to: array_keys(wikiFuzz::$data);
+       // as such, it also needs to also be publicly modifiable.
+       public static $types;
+
+
+       // Some attribute values.
+       static private $other = array( "&", "=", ":", "?", "\"", "\n", "%n%n%n%n%n%n%n%n%n%n%n%n", "\\" );
+       static private $ints  = array(
+                       // various numbers
+                       "0", "-1", "127", "-7897", "89000", "808080", "90928345",
+                       "0xfffffff", "ffff",
+
+                       // Different ways of saying: '
+                       "&#0000039;", // Long UTF-8 Unicode encoding
+                       "&#39;",  // dec version.
+                       "&#x27;", // hex version.
+                       "&#xA7;", // malformed hex variant, MSB not zero.
+
+                       // Different ways of saying: "
+                       "&#0000034;", // Long UTF-8 Unicode encoding
+                       "&#34;",
+                       "&#x22;", // hex version.
+                       "&#xA2;", // malformed hex variant, MSB not zero.
+
+                       // Different ways of saying: <
+                       "<",
+                       "&#0000060",  // Long UTF-8 Unicode encoding without semicolon (Mediawiki wants the colon)
+                       "&#0000060;", // Long UTF-8 Unicode encoding with semicolon
+                       "&#60;",
+                       "&#x3C;",     // hex version.
+                       "&#xBC;",     // malformed hex variant, MSB not zero.
+                       "&#x0003C;",  // mid-length hex version
+                       "&#X00003C;", // slightly longer hex version, with capital "X"
+
+                       // Different ways of saying: >
+                       ">",
+                       "&#0000062;", // Long UTF-8 Unicode encoding
+                       "&#62;",
+                       "&#x3E;",     // hex version.
+                       "&#xBE;",     // malformed variant, MSB not zero.
+
+                       // Different ways of saying: [
+                       "&#0000091;", // Long UTF-8 Unicode encoding
+                       "&#91;",
+                       "&#x5B;",     // hex version.
+
+                       // Different ways of saying: {{
+                       "&#0000123;&#0000123;", // Long UTF-8 Unicode encoding
+                       "&#123;&#123;",
+                       "&#x7B;&#x7B;",         // hex version.
+
+                       // Different ways of saying: |
+                       "&#0000124;", // Long UTF-8 Unicode encoding
+                       "&#124;",
+                       "&#x7C;",     // hex version.
+                       "&#xFC;",     // malformed hex variant, MSB not zero.
+
+                       // a "lignature" - http://www.robinlionheart.com/stds/html4/spchars#ligature
+                       // &#8204; == &zwnj;
+                       "&#8204;"
+                               );
+
+       // Defines various wiki-related bits of syntax, that can potentially cause
+       // MediaWiki to do something other than just print that literal text.
+       static private $ext = array(
+                       // links, templates, parameters.
+                       "[[", "]]", "{{", "}}", "|", "[", "]", "{{{", "}}}", "|]]",
+
+                       // wiki tables.
+                       "\n{|", "\n|}",
+                       "!",
+                       "\n!",
+                       "!!",
+                       "||",
+                       "\n|-", "| ", "\n|",
+
+                       // section headings.
+                       "=", "==", "===", "====", "=====", "======",
+
+                       // lists (ordered and unordered) and indentation.
+                       "\n*", "*", "\n:", ":",
+                       "\n#", "#",
+
+                       // definition lists (dl, dt, dd), newline, and newline with pre, and a tab.
+                       "\n;", ";", "\n ",
+
+                       // Whitespace: newline, tab, space.
+                       "\n", "\t", " ",
+
+                       // Some XSS attack vectors from http://ha.ckers.org/xss.html
+                       "&#x09;", // tab
+                       "&#x0A;", // newline
+                       "&#x0D;", // carriage return
+                       "\0",     // null character
+                       " &#14; ", // spaces and meta characters
+                       "'';!--\"<XSS>=&{()}", // compact injection of XSS & SQL tester
+
+                       // various NULL fields
+                       "%00",
+                       "&#00;",
+                       "\0",
+
+                       // horizontal rule.
+                       "-----", "\n-----",
+
+                       // signature, redirect, bold, italics.
+                       "~~~~", "#REDIRECT [[", "'''", "''",
+
+                       // comments.
+                       "<!--", "-->",
+
+                       // quotes.
+                       "\"", "'",
+
+                       // tag start and tag end.
+                       "<", ">",
+
+                       // implicit link creation on URIs.
+                       "http://",
+                       "https://",
+                       "ftp://",
+                       "irc://",
+                       "news:",
+                       'gopher://',
+                       'telnet://',
+                       'nntp://',
+                       'worldwind://',
+                       'mailto:',
+
+                       // images.
+                       "[[image:",
+                       ".gif",
+                       ".png",
+                       ".jpg",
+                       ".jpeg",
+                       'thumbnail=',
+                       'thumbnail',
+                       'thumb=',
+                       'thumb',
+                       'right',
+                       'none',
+                       'left',
+                       'framed',
+                       'frame',
+                       'enframed',
+                       'centre',
+                       'center',
+                       "Image:",
+                       "[[:Image",
+                       'px',
+                       'upright=',
+                       'border',
+
+                       // misc stuff to throw at the Parser.
+                       '%08X',
+                       '/',
+                       ":x{|",
+                       "\n|+",
+                       "<noinclude>",
+                       "</noinclude>",
+                       " \302\273",
+                       " :",
+                       " !",
+                       " ;",
+                       "\302\253",
+                       "[[category:",
+                       "?=",
+                       "(",
+                       ")",
+                       "]]]",
+                       "../",
+                       "{{{{",
+                       "}}}}",
+                       "[[Special:",
+                       "<includeonly>",
+                       "</includeonly>",
+                       "<!--MWTEMPLATESECTION=",
+                       '<!--MWTOC-->',
+
+                       // implicit link creation on booknum, RFC, and PubMed ID usage (both with and without IDs)
+                       "ISBN 2",
+                       "RFC 000",
+                       "PMID 000",
+                       "ISBN ",
+                       "RFC ",
+                       "PMID ",
+
+                       // magic words:
+                       '__NOTOC__',
+                       '__FORCETOC__',
+                       '__NOEDITSECTION__',
+                       '__START__',
+                       '__NOTITLECONVERT__',
+                       '__NOCONTENTCONVERT__',
+                       '__END__',
+                       '__TOC__',
+                       '__NOTC__',
+                       '__NOCC__',
+                       "__FORCETOC__",
+                       "__NEWSECTIONLINK__",
+                       "__NOGALLERY__",
+
+                       // more magic words / internal templates.
+                       '{{PAGENAME}}',
+                       '{{PAGENAMEE}}',
+                       '{{NAMESPACE}}',
+                       "{{MSG:",
+                       "}}",
+                       "{{MSGNW:",
+                       "}}",
+                       "{{INT:",
+                       "}}",
+                       '{{SITENAME}}',
+                       "{{NS:",
+                       "}}",
+                       "{{LOCALURL:",
+                       "}}",
+                       "{{LOCALURLE:",
+                       "}}",
+                       "{{SCRIPTPATH}}",
+                       "{{GRAMMAR:gentiv|",
+                       "}}",
+                       "{{REVISIONID}}",
+                       "{{SUBPAGENAME}}",
+                       "{{SUBPAGENAMEE}}",
+                       "{{ns:0}}",
+                       "{{fullurle:",
+                       "}}",
+                       "{{subst::",
+                       "}}",
+                       "{{UCFIRST:",
+                       "}}",
+                       "{{UC:",
+                       '{{SERVERNAME}}',
+                       '{{SERVER}}',
+                       "{{RAW:",
+                       "}}",
+                       "{{PLURAL:",
+                       "}}",
+                       "{{LCFIRST:",
+                       "}}",
+                       "{{LC:",
+                       "}}",
+                       '{{CURRENTWEEK}}',
+                       '{{CURRENTDOW}}',
+                       "{{INT:{{LC:contribs-showhideminor}}|",
+                       "}}",
+                       "{{INT:googlesearch|",
+                       "}}",
+                       "{{BASEPAGENAME}}",
+                       "{{CONTENTLANGUAGE}}",
+                       "{{PAGESINNAMESPACE:}}",
+                       "{{#language:",
+                       "}}",
+                       "{{#special:",
+                       "}}",
+                       "{{#special:emailuser",
+                       "}}",
+
+                       // Some raw link for magic words.
+                       "{{NUMBEROFPAGES:R",
+                       "}}",
+                       "{{NUMBEROFUSERS:R",
+                       "}}",
+                       "{{NUMBEROFARTICLES:R",
+                       "}}",
+                       "{{NUMBEROFFILES:R",
+                       "}}",
+                       "{{NUMBEROFADMINS:R",
+                       "}}",
+                       "{{padleft:",
+                       "}}",
+                       "{{padright:",
+                       "}}",
+                       "{{DEFAULTSORT:",
+                       "}}",
+
+                       // internal Math "extension":
+                       "<math>",
+                       "</math>",
+
+                       // Parser extension functions:
+                       "{{#expr:",
+                       "{{#if:",
+                       "{{#ifeq:",
+                       "{{#ifexist:",
+                       "{{#ifexpr:",
+                       "{{#switch:",
+                       "{{#time:",
+                       "}}",
+
+                       // references table for the Cite extension.
+                       "<references/>",
+
+                       // Internal Parser tokens - try inserting some of these.
+                       "UNIQ25f46b0524f13e67NOPARSE",
+                       "UNIQ17197916557e7cd6-HTMLCommentStrip46238afc3bb0cf5f00000002",
+                       "\x07UNIQ17197916557e7cd6-HTMLCommentStrip46238afc3bb0cf5f00000002-QINU",
+
+                       // Inputbox extension:
+                       "<inputbox>\ntype=search\nsearchbuttonlabel=\n",
+                       "</inputbox>",
+
+                       // charInsert extension:
+                       "<charInsert>",
+                       "</charInsert>",
+
+                       // wikiHiero extension:
+                       "<hiero>",
+                       "</hiero>",
+
+                       // Image gallery:
+                       "<gallery>",
+                       "</gallery>",
+
+                       // FixedImage extension.
+                       "<fundraising/>",
+
+                       // Timeline extension: currently untested.
+
+                       // Nowiki:
+                       "<nOwIkI>",
+                       "</nowiki>",
+
+                       // an external image to test the external image displaying code
+                       "http://debian.org/Pics/debian.png",
+
+                       // LabeledSectionTransclusion extension.
+                       "{{#lstx:",
+                       "}}",
+                       "{{#lst:",
+                       "}}",
+                       "{{#lst:Main Page|",
+                       "}}"
+                       );
+
+       /**
+        ** Randomly returns one element of the input array.
+        */
+       static public function chooseInput( array $input ) {
+               $randindex = wikiFuzz::randnum( count( $input ) - 1 );
+               return $input[$randindex];
+       }
+
+       // Max number of parameters for HTML attributes.
+       static private $maxparams = 10;
+
+       /**
+        ** Returns random number between finish and start.
+        */
+       static public function randnum( $finish, $start = 0 ) {
+               return mt_rand( $start, $finish );
+       }
+
+       /**
+        ** Returns a mix of random text and random wiki syntax.
+        */
+       static private function randstring() {
+               $thestring = "";
+
+               for ( $i = 0; $i < 40; $i++ ) {
+                       $what = wikiFuzz::randnum( 1 );
+
+                       if ( $what == 0 ) { // include some random wiki syntax
+                               $which = wikiFuzz::randnum( count( wikiFuzz::$ext ) - 1 );
+                               $thestring .= wikiFuzz::$ext[$which];
+                       }
+                       else { // include some random text
+                               $char = INCLUDE_BINARY
+                                       // Decimal version:
+                                       // "&#" . wikiFuzz::randnum(255) . ";"
+                                       // Hex version:
+                                       ? "&#x" . str_pad( dechex( wikiFuzz::randnum( 255 ) ), wikiFuzz::randnum( 2, 7 ), "0", STR_PAD_LEFT ) . ";"
+                                       // A truly binary version:
+                                       // ? chr(wikiFuzz::randnum(0,255))
+                                       : chr( wikiFuzz::randnum( 126, 32 ) );
+
+                               $length = wikiFuzz::randnum( 8 );
+                               $thestring .= str_repeat ( $char, $length );
+                       }
+               }
+               return $thestring;
+       }
+
+       /**
+        ** Returns either random text, or random wiki syntax, or random data from "ints",
+        **        or random data from "other".
+        */
+       static private function makestring() {
+               $what = wikiFuzz::randnum( 2 );
+               if ( $what == 0 ) {
+                       return wikiFuzz::randstring();
+               }
+               elseif ( $what == 1 ) {
+                       return wikiFuzz::$ints[wikiFuzz::randnum( count( wikiFuzz::$ints ) - 1 )];
+               }
+               else {
+                       return wikiFuzz::$other[wikiFuzz::randnum( count( wikiFuzz::$other ) - 1 )];
+               }
+       }
+
+       /**
+        * Returns the matched character slash-escaped as in a C string
+        * Helper for makeTitleSafe callback
+        */
+       static private function stringEscape( $matches ) {
+               return sprintf( "\\x%02x", ord( $matches[1] ) );
+       }
+       
+       /**
+        ** Strips out the stuff that Mediawiki balks at in a page's title.
+        **        Implementation copied/pasted from cleanupTable.inc & cleanupImages.php
+        */
+       static public function makeTitleSafe( $str ) {
+               $legalTitleChars = " %!\"$&'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF";
+               return preg_replace_callback(
+                               "/([^$legalTitleChars])/", 'wikiFuzz::stringEscape',
+                               $str );
+       }
+
+       /**
+        ** Returns a string of fuzz text.
+        */
+       static private function loop() {
+               switch ( wikiFuzz::randnum( 3 ) ) {
+                       case 1: // an opening tag, with parameters.
+                               $string = "";
+                               $i = wikiFuzz::randnum( count( wikiFuzz::$types ) - 1 );
+                               $t = wikiFuzz::$types[$i];
+                               $arr = wikiFuzz::$data[$t];
+                               $string .= "<" . $t . " ";
+                               $num_params = min( wikiFuzz::$maxparams, count( $arr ) );
+                               for ( $z = 0; $z < $num_params; $z++ ) {
+                                       $badparam = $arr[wikiFuzz::randnum( count( $arr ) - 1 )];
+                                       $badstring = wikiFuzz::makestring();
+                                       $string .= $badparam . "=" . wikiFuzz::getRandQuote() . $badstring . wikiFuzz::getRandQuote() . " ";
+                               }
+                               $string .= ">\n";
+                               return $string;
+                       case 2: // a closing tag.
+                               $i = wikiFuzz::randnum( count( wikiFuzz::$types ) - 1 );
+                               return "</" . wikiFuzz::$types[$i] . ">";
+                       case 3: // a random string, between tags.
+                               return wikiFuzz::makeString();
+               }
+               return "";    // catch-all, should never be called.
+       }
+
+       /**
+        ** Returns one of the three styles of random quote: ', ", and nothing.
+        */
+       static private function getRandQuote() {
+               switch ( wikiFuzz::randnum( 3 ) ) {
+                       case 1 : return "'";
+                       case 2 : return "\"";
+                       default: return "";
+               }
+       }
+
+       /**
+        ** Returns fuzz text, with the parameter indicating approximately how many lines of text you want.
+        */
+       static public function makeFuzz( $maxtypes = 2 ) {
+               $page = "";
+               for ( $k = 0; $k < $maxtypes; $k++ ) {
+                       $page .= wikiFuzz::loop();
+               }
+               return $page;
+       }
+}
+
+
+// //////   MEDIAWIKI PAGES TO TEST, AND HOW TO TEST THEM  ///////
+
+/**
+ ** A page test has just these things:
+ **        1) Form parameters.
+ **        2) the URL we are going to test those parameters on.
+ **        3) Any cookies required for the test.
+ **        4) Whether Tidy should validate the page. Defaults to true, but can be turned off.
+ **        Declared abstract because it should be extended by a class
+ **        that supplies these parameters.
+ */
+abstract class pageTest {
+       protected $params;
+       protected $pagePath;
+       protected $cookie = "";
+       protected $tidyValidate = true;
+
+       public function getParams() {
+               return $this->params;
+       }
+
+       public function getPagePath() {
+               return $this->pagePath;
+       }
+
+       public function getCookie() {
+               return $this->cookie;
+       }
+
+       public function tidyValidate() {
+               return $this->tidyValidate;
+       }
+}
+
+
+/**
+ ** a page test for the "Edit" page. Tests Parser.php and Sanitizer.php.
+ */
+class editPageTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=WIKIFUZZ";
+
+               $this->params = array (
+                               "action"        => "submit",
+                               "wpMinoredit"   => wikiFuzz::makeFuzz( 2 ),
+                               "wpPreview"     => wikiFuzz::makeFuzz( 2 ),
+                               "wpSection"     => wikiFuzz::makeFuzz( 2 ),
+                               "wpEdittime"    => wikiFuzz::makeFuzz( 2 ),
+                               "wpSummary"     => wikiFuzz::makeFuzz( 2 ),
+                               "wpScrolltop"   => wikiFuzz::makeFuzz( 2 ),
+                               "wpStarttime"   => wikiFuzz::makeFuzz( 2 ),
+                               "wpAutoSummary" => wikiFuzz::makeFuzz( 2 ),
+                               "wpTextbox1"    => wikiFuzz::makeFuzz( 40 )  // the main wiki text, need lots of this.
+                               );
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpSection"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpEdittime"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpSummary"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpScrolltop"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpStarttime"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpAutoSummary"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpTextbox1"] );
+       }
+}
+
+
+/**
+ ** a page test for "Special:Listusers".
+ */
+class listusersTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Listusers";
+
+               $this->params = array (
+                               "title"        => wikiFuzz::makeFuzz( 2 ),
+                               "group"        => wikiFuzz::makeFuzz( 2 ),
+                               "username"     => wikiFuzz::makeFuzz( 2 ),
+                               "Go"           => wikiFuzz::makeFuzz( 2 ),
+                               "limit"        => wikiFuzz::chooseInput( array( "0", "-1", "---'----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "offset"       => wikiFuzz::chooseInput( array( "0", "-1", "--------'-----0", "+1", "81343242346234234", wikiFuzz::makeFuzz( 2 ) ) )
+                               );
+       }
+}
+
+
+/**
+ ** a page test for "Special:Search".
+ */
+class searchTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Search";
+
+               $this->params = array (
+                               "action"        => "index.php?title=Special:Search",
+                               "ns0"           => wikiFuzz::makeFuzz( 2 ),
+                               "ns1"           => wikiFuzz::makeFuzz( 2 ),
+                               "ns2"           => wikiFuzz::makeFuzz( 2 ),
+                               "ns3"           => wikiFuzz::makeFuzz( 2 ),
+                               "ns4"           => wikiFuzz::makeFuzz( 2 ),
+                               "ns5"           => wikiFuzz::makeFuzz( 2 ),
+                               "ns6"           => wikiFuzz::makeFuzz( 2 ),
+                               "ns7"           => wikiFuzz::makeFuzz( 2 ),
+                               "ns8"           => wikiFuzz::makeFuzz( 2 ),
+                               "ns9"           => wikiFuzz::makeFuzz( 2 ),
+                               "ns10"          => wikiFuzz::makeFuzz( 2 ),
+                               "ns11"          => wikiFuzz::makeFuzz( 2 ),
+                               "ns12"          => wikiFuzz::makeFuzz( 2 ),
+                               "ns13"          => wikiFuzz::makeFuzz( 2 ),
+                               "ns14"          => wikiFuzz::makeFuzz( 2 ),
+                               "ns15"          => wikiFuzz::makeFuzz( 2 ),
+                               "redirs"        => wikiFuzz::makeFuzz( 2 ),
+                               "search"        => wikiFuzz::makeFuzz( 2 ),
+                               "offset"        => wikiFuzz::chooseInput( array( "", "0", "-1", "--------'-----0", "+1", "81343242346234234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "fulltext"      => wikiFuzz::chooseInput( array( "", "0", "1", "--------'-----0", "+1", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "searchx"       => wikiFuzz::chooseInput( array( "", "0", "1", "--------'-----0", "+1", wikiFuzz::makeFuzz( 2 ) ) )
+                                       );
+       }
+}
+
+
+/**
+ ** a page test for "Special:Recentchanges".
+ */
+class recentchangesTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Recentchanges";
+
+               $this->params = array (
+                               "action"        => wikiFuzz::makeFuzz( 2 ),
+                               "title"         => wikiFuzz::makeFuzz( 2 ),
+                               "namespace"     => wikiFuzz::chooseInput( range( -1, 15 ) ),
+                               "Go"            => wikiFuzz::makeFuzz( 2 ),
+                               "invert"        => wikiFuzz::chooseInput( array( "-1", "---'----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "hideanons"     => wikiFuzz::chooseInput( array( "-1", "------'-------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'limit'         => wikiFuzz::chooseInput( array( "0", "-1", "---------'----0", "+1", "81340909772349234",  wikiFuzz::makeFuzz( 2 ) ) ),
+                               "days"          => wikiFuzz::chooseInput( array( "-1", "----------'---0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "hideminor"     => wikiFuzz::chooseInput( array( "-1", "-----------'--0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "hidebots"      => wikiFuzz::chooseInput( array( "-1", "---------'----0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "hideliu"       => wikiFuzz::chooseInput( array( "-1", "-------'------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "hidepatrolled" => wikiFuzz::chooseInput( array( "-1", "-----'--------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "hidemyself"    => wikiFuzz::chooseInput( array( "-1", "--'-----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'categories_any' => wikiFuzz::chooseInput( array( "-1", "--'-----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'categories'    => wikiFuzz::chooseInput( array( "-1", "--'-----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'feed'          => wikiFuzz::chooseInput( array( "-1", "--'-----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) )
+                               );
+       }
+}
+
+
+/**
+ ** a page test for "Special:Prefixindex".
+ */
+class prefixindexTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Prefixindex";
+
+               $this->params = array (
+                               "title"         => "Special:Prefixindex",
+                               "namespace"     => wikiFuzz::randnum( 101, -10 ),
+                               "Go"            => wikiFuzz::makeFuzz( 2 )
+                               );
+
+               // sometimes we want 'prefix', sometimes we want 'from', and sometimes we want nothing.
+               if ( wikiFuzz::randnum( 3 ) == 0 ) {
+                       $this->params["prefix"] = wikiFuzz::chooseInput( array( "-1", "-----'--------0", "+++--+1",
+                                                                                                wikiFuzz::randnum( 8134, -10 ), wikiFuzz::makeFuzz( 2 ) ) );
+               }
+               if ( wikiFuzz::randnum( 3 ) == 0 ) {
+                       $this->params["from"]   = wikiFuzz::chooseInput( array( "-1", "-----'--------0", "+++--+1",
+                                                                                               wikiFuzz::randnum( 8134, -10 ), wikiFuzz::makeFuzz( 2 ) ) );
+               }
+       }
+}
+
+
+/**
+ ** a page test for "Special:MIMEsearch".
+ */
+class mimeSearchTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:MIMEsearch";
+
+               $this->params = array (
+                               "action"        => "index.php?title=Special:MIMEsearch",
+                               "mime"          => wikiFuzz::makeFuzz( 3 ),
+                               'limit'         => wikiFuzz::chooseInput( array( "0", "-1", "-------'------0", "+1", "81342321351235325",  wikiFuzz::makeFuzz( 2 ) ) ),
+                               'offset'        => wikiFuzz::chooseInput( array( "0", "-1", "-----'--------0", "+1", "81341231235365252234324",  wikiFuzz::makeFuzz( 2 ) ) )
+                               );
+       }
+}
+
+
+/**
+ ** a page test for "Special:Log".
+ */
+class specialLogTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Log";
+
+               $this->params = array (
+                               "type"        => wikiFuzz::chooseInput( array( "", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "par"         => wikiFuzz::makeFuzz( 2 ),
+                               "user"        => wikiFuzz::makeFuzz( 2 ),
+                               "page"        => wikiFuzz::makeFuzz( 2 ),
+                               "from"        => wikiFuzz::makeFuzz( 2 ),
+                               "until"       => wikiFuzz::makeFuzz( 2 ),
+                               "title"       => wikiFuzz::makeFuzz( 2 )
+                               );
+       }
+}
+
+
+/**
+ ** a page test for "Special:Userlogin", with a successful login.
+ */
+class successfulUserLoginTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Userlogin&action=submitlogin&type=login&returnto=" . wikiFuzz::makeFuzz( 2 );
+
+               $this->params = array (
+                               "wpName"          => USER_ON_WIKI,
+                               // sometimes real password, sometimes not:
+                               'wpPassword'      => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ), USER_PASSWORD ) ),
+                               'wpRemember'      => wikiFuzz::makeFuzz( 2 )
+                               );
+
+               $this->cookie = "wikidb_session=" .  wikiFuzz::chooseInput( array( "1" , wikiFuzz::makeFuzz( 2 ) ) );
+       }
+}
+
+
+/**
+ ** a page test for "Special:Userlogin".
+ */
+class userLoginTest extends pageTest {
+       function __construct() {
+
+               $this->pagePath = "index.php?title=Special:Userlogin";
+
+               $this->params = array (
+                               'wpRetype'        => wikiFuzz::makeFuzz( 2 ),
+                               'wpRemember'      => wikiFuzz::makeFuzz( 2 ),
+                               'wpRealName'      => wikiFuzz::makeFuzz( 2 ),
+                               'wpPassword'      => wikiFuzz::makeFuzz( 2 ),
+                               'wpName'          => wikiFuzz::makeFuzz( 2 ),
+                               'wpMailmypassword' => wikiFuzz::makeFuzz( 2 ),
+                               'wpLoginattempt'  => wikiFuzz::makeFuzz( 2 ),
+                               'wpEmail'         => wikiFuzz::makeFuzz( 2 ),
+                               'wpDomain'        => wikiFuzz::chooseInput( array( "", "local", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'wpCreateaccountMail' => wikiFuzz::chooseInput( array( "", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'wpCreateaccount' => wikiFuzz::chooseInput( array( "", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'wpCookieCheck'   => wikiFuzz::chooseInput( array( "", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'type'            => wikiFuzz::chooseInput( array( "signup", "login", "", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'returnto'        => wikiFuzz::makeFuzz( 2 ),
+                               'action'          => wikiFuzz::chooseInput( array( "", "submitlogin", wikiFuzz::makeFuzz( 2 ) ) )
+                               );
+
+               $this->cookie = "wikidb_session=" . wikiFuzz::chooseInput( array( "1" , wikiFuzz::makeFuzz( 2 ) ) );
+       }
+}
+
+
+/**
+ ** a page test for "Special:Ipblocklist" (also includes unblocking)
+ */
+class ipblocklistTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Ipblocklist";
+
+               $this->params = array (
+                               'wpUnblockAddress' => wikiFuzz::makeFuzz( 2 ),
+                               'ip'              => wikiFuzz::chooseInput( array( "20398702394", "", "Nickj2", wikiFuzz::makeFuzz( 2 ),
+                                                                        // something like an IP address, sometimes invalid:
+                                                                        ( wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) . "."
+                                                                          . wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) ) ) ),
+                               'id'              => wikiFuzz::makeFuzz( 2 ),
+                               'wpUnblockReason' => wikiFuzz::makeFuzz( 2 ),
+                               'action'          => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ), "success", "submit", "unblock" ) ),
+                               'wpEditToken'     => wikiFuzz::makeFuzz( 2 ),
+                               'wpBlock'         => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ), "" ) ),
+                               'limit'           => wikiFuzz::chooseInput( array( "0", "-1", "--------'-----0", "+1",
+                                                                                                "09700982312351132098234",  wikiFuzz::makeFuzz( 2 ) ) ),
+                               'offset'          => wikiFuzz::chooseInput( array( "0", "-1", "------'-------0", "+1",
+                                                                                                "09700980982341535324234234", wikiFuzz::makeFuzz( 2 ) ) )
+                               );
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["action"] );
+               if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["ip"] );
+               if ( wikiFuzz::randnum( 2 ) == 0 ) unset( $this->params["id"] );
+               if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["wpUnblockAddress"] );
+       }
+}
+
+
+/**
+ ** a page test for "Special:Newimages".
+ */
+class newImagesTest extends  pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Newimages";
+
+               $this->params = array (
+                               'hidebots'  => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ), "1", "", "-1" ) ),
+                               'wpIlMatch' => wikiFuzz::makeFuzz( 2 ),
+                               'until'     => wikiFuzz::makeFuzz( 2 ),
+                               'from'      => wikiFuzz::makeFuzz( 2 )
+                               );
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["until"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["from"] );
+       }
+}
+
+
+/**
+ ** a page test for the "Special:Imagelist" page.
+ */
+class imagelistTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Imagelist";
+
+               $this->params = array (
+                               'sort'      => wikiFuzz::chooseInput( array( "bysize", "byname" , "bydate", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'limit'     => wikiFuzz::chooseInput( array( "0", "-1", "--------'-----0", "+1", "09700982312351132098234",  wikiFuzz::makeFuzz( 2 ) ) ),
+                               'offset'    => wikiFuzz::chooseInput( array( "0", "-1", "------'-------0", "+1", "09700980982341535324234234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'wpIlMatch' => wikiFuzz::makeFuzz( 2 )
+                               );
+       }
+}
+
+
+/**
+ ** a page test for "Special:Export".
+ */
+class specialExportTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Export";
+
+               $this->params = array (
+                               'action'      => wikiFuzz::chooseInput( array( "submit", "", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'pages'       => wikiFuzz::makeFuzz( 2 ),
+                               'curonly'     => wikiFuzz::chooseInput( array( "", "0", "-1", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'listauthors' => wikiFuzz::chooseInput( array( "", "0", "-1", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'history'     => wikiFuzz::chooseInput( array( "0", "-1", "------'-------0", "+1", "09700980982341535324234234", wikiFuzz::makeFuzz( 2 ) ) ),
+
+                               );
+
+               // For the time being, need to disable "submit" action as Tidy barfs on MediaWiki's XML export.
+               if ( $this->params['action'] == 'submit' ) $this->params['action'] = '';
+
+               // Sometimes remove the history field.
+               if ( wikiFuzz::randnum( 2 ) == 0 ) unset( $this->params["history"] );
+
+               // page does not produce HTML.
+               $this->tidyValidate = false;
+       }
+}
+
+
+/**
+ ** a page test for "Special:Booksources".
+ */
+class specialBooksourcesTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Booksources";
+
+               $this->params = array (
+                               'go'    => wikiFuzz::makeFuzz( 2 ),
+                               // ISBN codes have to contain some semi-numeric stuff or will be ignored:
+                               'isbn'  => "0X0" . wikiFuzz::makeFuzz( 2 )
+                               );
+       }
+}
+
+
+/**
+ ** a page test for "Special:Allpages".
+ */
+class specialAllpagesTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special%3AAllpages";
+
+               $this->params = array (
+                               'from'      => wikiFuzz::makeFuzz( 2 ),
+                               'namespace' => wikiFuzz::chooseInput( range( -1, 15 ) ),
+                               'go'        => wikiFuzz::makeFuzz( 2 )
+                               );
+       }
+}
+
+
+/**
+ ** a page test for the page History.
+ */
+class pageHistoryTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Main_Page&action=history";
+
+               $this->params = array (
+                               'limit'     => wikiFuzz::chooseInput( array( "-1", "0", "-------'------0", "+1", "8134",  wikiFuzz::makeFuzz( 2 ) ) ),
+                               'offset'    => wikiFuzz::chooseInput( array( "-1", "0", "------'-------0", "+1", "9823412312312412435", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "go"        => wikiFuzz::chooseInput( array( "first", "last", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "dir"       => wikiFuzz::chooseInput( array( "prev", "next", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "diff"      => wikiFuzz::chooseInput( array( "-1", "--------'-----0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "oldid"     => wikiFuzz::chooseInput( array( "prev", "-1", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "feed"      => wikiFuzz::makeFuzz( 2 )
+                               );
+       }
+}
+
+
+/**
+ ** a page test for the Special:Contributions".
+ */
+class contributionsTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Contributions/" . USER_ON_WIKI;
+
+               $this->params = array (
+                               'target'    => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ), "newbies", USER_ON_WIKI ) ),
+                               'namespace' => wikiFuzz::chooseInput( array( -1, 15, 1, wikiFuzz::makeFuzz( 2 ) ) ),
+                               'offset'    => wikiFuzz::chooseInput( array( "0", "-1", "------'-------0", "+1", "982342131232131231241", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'bot'       => wikiFuzz::chooseInput( array( "", "-1", "0", "1", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'go'        => wikiFuzz::chooseInput( array( "-1", 'prev', 'next', wikiFuzz::makeFuzz( 2 ) ) )
+                               );
+       }
+}
+
+
+/**
+ ** a page test for viewing a normal page, whilst posting various params.
+ */
+class viewPageTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Main_Page";
+
+               $this->params = array (
+                               "useskin"        => wikiFuzz::chooseInput( array( "chick", "cologneblue", "myskin",
+                                                                               "nostalgia", "simple", "standard", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "uselang"        => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ),
+                                               "ab", "af", "an", "ar", "arc", "as", "ast", "av", "ay", "az", "ba",
+                                               "bat-smg", "be", "bg", "bm", "bn", "bo", "bpy", "br", "bs", "ca",
+                                               "ce", "cs", "csb", "cv", "cy", "da", "de", "dv", "dz", "el", "en",
+                                               "eo", "es", "et", "eu", "fa", "fi", "fo", "fr", "fur", "fy", "ga",
+                                               "gn", "gsw", "gu", "he", "hi", "hr", "hu", "ia", "id", "ii", "is",
+                                               "it", "ja", "jv", "ka", "km", "kn", "ko", "ks", "ku", "kv", "la",
+                                               "li", "lo", "lt", "lv", "mk", "ml", "ms", "nah", "nap", "nds",
+                                               "nds-nl", "nl", "nn", "no", "non", "nv", "oc", "or", "os", "pa",
+                                               "pl", "pms", "ps", "pt", "pt-br", "qu", "rmy", "ro", "ru", "sc",
+                                               "sd", "sk", "sl", "sq", "sr", "sr-ec", "sr-el",
+                                               "su", "sv", "ta", "te", "th", "tr", "tt", "ty", "tyv", "udm",
+                                               "ug", "uk", "ur", "utf8", "vec", "vi", "wa", "xal", "yi", "za",
+                                               "zh", "zh-cn", "zh-hk", "zh-sg", "zh-tw", "zh-tw" ) ),
+                               "returnto"       => wikiFuzz::makeFuzz( 2 ),
+                               "feed"           => wikiFuzz::chooseInput( array( "atom", "rss", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "rcid"           => wikiFuzz::makeFuzz( 2 ),
+                               "action"         => wikiFuzz::chooseInput( array( "view", "raw", "render", wikiFuzz::makeFuzz( 2 ), "markpatrolled" ) ),
+                               "printable"      => wikiFuzz::makeFuzz( 2 ),
+                               "oldid"          => wikiFuzz::makeFuzz( 2 ),
+                               "redirect"       => wikiFuzz::makeFuzz( 2 ),
+                               "diff"           => wikiFuzz::makeFuzz( 2 ),
+                               "search"         => wikiFuzz::makeFuzz( 2 ),
+                               "rdfrom"         => wikiFuzz::makeFuzz( 2 ),  // things from Article.php from here on:
+                               "token"          => wikiFuzz::makeFuzz( 2 ),
+                               "tbid"           => wikiFuzz::makeFuzz( 2 ),
+                               "action"         => wikiFuzz::chooseInput( array( "purge", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "wpReason"       => wikiFuzz::makeFuzz( 2 ),
+                               "wpEditToken"    => wikiFuzz::makeFuzz( 2 ),
+                               "from"           => wikiFuzz::makeFuzz( 2 ),
+                               "bot"            => wikiFuzz::makeFuzz( 2 ),
+                               "summary"        => wikiFuzz::makeFuzz( 2 ),
+                               "direction"      => wikiFuzz::chooseInput( array( "next", "prev", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "section"        => wikiFuzz::makeFuzz( 2 ),
+                               "preload"        => wikiFuzz::makeFuzz( 2 ),
+
+                               );
+
+               // Tidy does not know how to valid atom or rss, so exclude from testing for the time being.
+               if ( $this->params["feed"] == "atom" )     { unset( $this->params["feed"] ); }
+               else if ( $this->params["feed"] == "rss" ) { unset( $this->params["feed"] ); }
+
+               // Raw pages cannot really be validated
+               if ( $this->params["action"] == "raw" ) unset( $this->params["action"] );
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["rcid"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["diff"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["rdfrom"] );
+               if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["oldid"] );
+
+               // usually don't want action == purge.
+               if ( wikiFuzz::randnum( 6 ) > 1 ) unset( $this->params["action"] );
+       }
+}
+
+
+/**
+ ** a page test for "Special:Allmessages".
+ */
+class specialAllmessagesTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Allmessages";
+
+               // only really has one parameter
+               $this->params = array (
+                               "ot"     => wikiFuzz::chooseInput( array( "php", "html", wikiFuzz::makeFuzz( 2 ) ) )
+                               );
+       }
+}
+
+/**
+ ** a page test for "Special:Newpages".
+ */
+class specialNewpagesPageTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Newpages";
+
+               $this->params = array (
+                               "namespace" => wikiFuzz::chooseInput( range( -1, 15 ) ),
+                               "feed"      => wikiFuzz::chooseInput( array( "atom", "rss", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'limit'     => wikiFuzz::chooseInput( array( "-1", "0", "-------'------0", "+1", "8134",  wikiFuzz::makeFuzz( 2 ) ) ),
+                               'offset'    => wikiFuzz::chooseInput( array( "-1", "0", "------'-------0", "+1", "9823412312312412435", wikiFuzz::makeFuzz( 2 ) ) )
+                               );
+
+               // Tidy does not know how to valid atom or rss, so exclude from testing for the time being.
+               if ( $this->params["feed"] == "atom" )     { unset( $this->params["feed"] ); }
+               else if ( $this->params["feed"] == "rss" ) { unset( $this->params["feed"] ); }
+       }
+}
+
+/**
+ ** a page test for "redirect.php"
+ */
+class redirectTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "redirect.php";
+
+               $this->params = array (
+                               "wpDropdown" => wikiFuzz::makeFuzz( 2 )
+                               );
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpDropdown"] );
+       }
+}
+
+
+/**
+ ** a page test for "Special:Confirmemail"
+ */
+class confirmEmail extends pageTest {
+       function __construct() {
+               // sometimes we send a bogus confirmation code, and sometimes we don't.
+               $this->pagePath = "index.php?title=Special:Confirmemail" . wikiFuzz::chooseInput( array( "", "/" . wikiFuzz::makeTitleSafe( wikiFuzz::makeFuzz( 1 ) ) ) );
+
+               $this->params = array (
+                               "token" => wikiFuzz::makeFuzz( 2 )
+                               );
+       }
+}
+
+
+/**
+ ** a page test for "Special:Watchlist"
+ **        Note: this test would be better if we were logged in.
+ */
+class watchlistTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Watchlist";
+
+               $this->params = array (
+                               "remove"   => wikiFuzz::chooseInput( array( "Remove checked items from watchlist", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'days'     => wikiFuzz::chooseInput( array( 0, -1, -230, "--", 3, 9, wikiFuzz::makeFuzz( 2 ) ) ),
+                               'hideOwn'  => wikiFuzz::chooseInput( array( "", "0", "1", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'hideBots' => wikiFuzz::chooseInput( array( "", "0", "1", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'namespace' => wikiFuzz::chooseInput( array( "", "0", "1", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'action'   => wikiFuzz::chooseInput( array( "submit", "clear", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'id[]'     => wikiFuzz::makeFuzz( 2 ),
+                               'edit'     => wikiFuzz::makeFuzz( 2 ),
+                               'token'    => wikiFuzz::chooseInput( array( "", "1243213", wikiFuzz::makeFuzz( 2 ) ) )
+                               );
+
+               // sometimes we specifiy "reset", and sometimes we don't.
+               if ( wikiFuzz::randnum( 3 ) == 0 ) $this->params["reset"] = wikiFuzz::chooseInput( array( "", "0", "1", wikiFuzz::makeFuzz( 2 ) ) );
+       }
+}
+
+
+/**
+ ** a page test for "Special:Blockme"
+ */
+class specialBlockmeTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Blockme";
+
+               $this->params = array ( );
+
+               // sometimes we specify "ip", and sometimes we don't.
+               if ( wikiFuzz::randnum( 1 ) == 0 ) {
+                       $this->params["ip"] = wikiFuzz::chooseInput( array( "10.12.41.213", wikiFuzz::randnum( 8134, -10 ), wikiFuzz::makeFuzz( 2 ) ) );
+               }
+       }
+}
+
+
+/**
+ ** a page test for "Special:Movepage"
+ */
+class specialMovePage extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Movepage";
+
+               $this->params = array (
+                               "action"      => wikiFuzz::chooseInput( array( "success", "submit", "", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'wpEditToken' => wikiFuzz::chooseInput( array( '', 0, 34987987, wikiFuzz::makeFuzz( 2 ) ) ),
+                               'target'      => wikiFuzz::chooseInput( array( "x", wikiFuzz::makeTitleSafe( wikiFuzz::makeFuzz( 2 ) ) ) ),
+                               'wpOldTitle'  => wikiFuzz::chooseInput( array( "z", wikiFuzz::makeTitleSafe( wikiFuzz::makeFuzz( 2 ) ), wikiFuzz::makeFuzz( 2 ) ) ),
+                               'wpNewTitle'  => wikiFuzz::chooseInput( array( "y", wikiFuzz::makeTitleSafe( wikiFuzz::makeFuzz( 2 ) ), wikiFuzz::makeFuzz( 2 ) ) ),
+                               'wpReason'    => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ) ) ),
+                               'wpMovetalk'  => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'wpDeleteAndMove'  => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'wpConfirm'   => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'talkmoved'   => wikiFuzz::chooseInput( array( "1", wikiFuzz::makeFuzz( 2 ), "articleexists", 'notalkpage' ) ),
+                               'oldtitle'    => wikiFuzz::makeFuzz( 2 ),
+                               'newtitle'    => wikiFuzz::makeFuzz( 2 ),
+                               'wpMovetalk'  => wikiFuzz::chooseInput( array( "1", "0", wikiFuzz::makeFuzz( 2 ) ) )
+                               );
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 2 ) == 0 ) unset( $this->params["wpEditToken"] );
+               if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["target"] );
+               if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["wpNewTitle"] );
+               if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpReason"] );
+               if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpOldTitle"] );
+       }
+}
+
+
+/**
+ ** a page test for "Special:Undelete"
+ */
+class specialUndeletePageTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Undelete";
+
+               $this->params = array (
+                               "action"      => wikiFuzz::chooseInput( array( "submit", "", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'wpEditToken' => wikiFuzz::chooseInput( array( '', 0, 34987987, wikiFuzz::makeFuzz( 2 ) ) ),
+                               'target'      => wikiFuzz::chooseInput( array( "x", wikiFuzz::makeTitleSafe( wikiFuzz::makeFuzz( 2 ) ) ) ),
+                               'timestamp'   => wikiFuzz::chooseInput( array( "125223", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'file'        => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'restore'     => wikiFuzz::chooseInput( array( "0", "1", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'preview'     => wikiFuzz::chooseInput( array( "0", "1", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'wpComment'   => wikiFuzz::makeFuzz( 2 )
+                               );
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 2 ) == 0 ) unset( $this->params["wpEditToken"] );
+               if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["target"] );
+               if ( wikiFuzz::randnum( 1 ) == 0 ) unset( $this->params["restore"] );
+               if ( wikiFuzz::randnum( 1 ) == 0 ) unset( $this->params["preview"] );
+       }
+}
+
+
+/**
+ ** a page test for "Special:Unlockdb"
+ */
+class specialUnlockdbPageTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Unlockdb";
+
+               $this->params = array (
+                               "action"        => wikiFuzz::chooseInput( array( "submit", "success", "", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'wpEditToken'   => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'wpLockConfirm' => wikiFuzz::chooseInput( array( "0", "1", wikiFuzz::makeFuzz( 2 ) ) )
+                               );
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpEditToken"] );
+               if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["action"] );
+               if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpLockConfirm"] );
+       }
+}
+
+
+/**
+ ** a page test for "Special:Lockdb"
+ */
+class specialLockdbPageTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Lockdb";
+
+               $this->params = array (
+                               "action"       => wikiFuzz::chooseInput( array( "submit", "success", "", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'wpEditToken'  => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'wpLockReason' => wikiFuzz::makeFuzz( 2 ),
+                               'wpLockConfirm' => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) )
+                               );
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpEditToken"] );
+               if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["action"] );
+               if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpLockConfirm"] );
+       }
+}
+
+
+/**
+ ** a page test for "Special:Userrights"
+ */
+class specialUserrights extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Userrights";
+
+               $this->params = array (
+                               'wpEditToken'   => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'user-editname' => wikiFuzz::chooseInput( array( "Nickj2", "Nickj2\n<xyz>", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'ssearchuser'   => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'saveusergroups' => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ), "Save User Groups" ),
+                               'member[]'      => wikiFuzz::chooseInput( array( "0", "bot", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "available[]"   => wikiFuzz::chooseInput( array( "0", "sysop", "bureaucrat", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) )
+                               );
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params['ssearchuser'] );
+               if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params['saveusergroups'] );
+       }
+}
+
+
+/**
+ ** a test for page protection and unprotection.
+ */
+class pageProtectionForm extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Main_Page";
+
+               $this->params = array (
+                               "action"               => "protect",
+                               'wpEditToken'          => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "mwProtect-level-edit" => wikiFuzz::chooseInput( array( '', 'autoconfirmed', 'sysop', wikiFuzz::makeFuzz( 2 ) ) ),
+                               "mwProtect-level-move" => wikiFuzz::chooseInput( array( '', 'autoconfirmed', 'sysop', wikiFuzz::makeFuzz( 2 ) ) ),
+                               "mwProtectUnchained"   => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               'mwProtect-reason'     => wikiFuzz::chooseInput( array( "because it was there", wikiFuzz::makeFuzz( 2 ) ) )
+                               );
+
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["mwProtectUnchained"] );
+               if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params['mwProtect-reason'] );
+       }
+}
+
+
+/**
+ ** a page test for "Special:Blockip".
+ */
+class specialBlockip extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Blockip";
+
+               $this->params = array (
+                               "action"          => wikiFuzz::chooseInput( array( "submit", "",  wikiFuzz::makeFuzz( 2 ) ) ),
+                               'wpEditToken'     => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "wpBlockAddress"  => wikiFuzz::chooseInput( array( "20398702394", "", "Nickj2", wikiFuzz::makeFuzz( 2 ),
+                                                                         // something like an IP address, sometimes invalid:
+                                                                        ( wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) . "."
+                                                                         . wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) ) ) ),
+                               "ip"              => wikiFuzz::chooseInput( array( "20398702394", "", "Nickj2", wikiFuzz::makeFuzz( 2 ),
+                                                                         // something like an IP address, sometimes invalid:
+                                                                        ( wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) . "."
+                                                                         . wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) ) ) ),
+                               "wpBlockOther"    => wikiFuzz::chooseInput( array( '', 'Nickj2', wikiFuzz::makeFuzz( 2 ) ) ),
+                               "wpBlockExpiry"   => wikiFuzz::chooseInput( array( "other", "2 hours", "1 day", "3 days", "1 week", "2 weeks",
+                                                                                 "1 month", "3 months", "6 months", "1 year", "infinite", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "wpBlockReason"   => wikiFuzz::chooseInput( array( "because it was there", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "wpAnonOnly"      => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "wpCreateAccount" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "wpBlock"         => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) )
+                               );
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpBlockOther"] );
+               if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpBlockExpiry"] );
+               if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpBlockReason"] );
+               if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpAnonOnly"] );
+               if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpCreateAccount"] );
+               if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpBlockAddress"] );
+               if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["ip"] );
+       }
+}
+
+
+/**
+ ** a test for the imagepage.
+ */
+class imagepageTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Image:Small-email.png";
+
+               $this->params = array (
+                               "image"         => wikiFuzz::chooseInput( array( "Small-email.png", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "wpReason"      => wikiFuzz::makeFuzz( 2 ),
+                               "oldimage"      => wikiFuzz::chooseInput( array( "Small-email.png", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "wpEditToken"   => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
+                               );
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["image"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpReason"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["oldimage"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpEditToken"] );
+       }
+}
+
+
+/**
+ ** a test for page deletion form.
+ */
+class pageDeletion extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Main_Page&action=delete";
+
+               $this->params = array (
+                               "wpEditToken" => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "wpReason"    => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "wpConfirm"   => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               );
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 5 ) == 0 ) unset( $this->params["wpReason"] );
+               if ( wikiFuzz::randnum( 5 ) == 0 ) unset( $this->params["wpEditToken"] );
+               if ( wikiFuzz::randnum( 5 ) == 0 ) unset( $this->params["wpConfirm"] );
+       }
+}
+
+
+
+/**
+ ** a test for Revision Deletion.
+ */
+class specialRevisionDeletePageTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Revisiondelete";
+
+               $this->params = array (
+                               "target"               => wikiFuzz::chooseInput( array( "Main Page", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "oldid"                => wikiFuzz::makeFuzz( 2 ),
+                               "oldid[]"              => wikiFuzz::makeFuzz( 2 ),
+                               "wpReason"             => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "revdelete-hide-text"  => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "revdelete-hide-comment" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "revdelete-hide-user"  => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "revdelete-hide-restricted" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               );
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["target"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["oldid"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["oldid[]"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpReason"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["revdelete-hide-text"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["revdelete-hide-comment"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["revdelete-hide-user"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["revdelete-hide-restricted"] );
+       }
+}
+
+
+/**
+ ** a test for Special:Import.
+ */
+class specialImportPageTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Import";
+
+               $this->params = array (
+                               "action"         => "submit",
+                               "source"         => wikiFuzz::chooseInput( array( "upload", "interwiki", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "MAX_FILE_SIZE"  => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "xmlimport"      => wikiFuzz::chooseInput( array( "/var/www/hosts/mediawiki/wiki/AdminSettings.php", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "namespace"      => wikiFuzz::chooseInput( array( wikiFuzz::randnum( 30, -6 ), wikiFuzz::makeFuzz( 2 ) ) ),
+                               "interwiki"      => wikiFuzz::makeFuzz( 2 ),
+                               "interwikiHistory" => wikiFuzz::makeFuzz( 2 ),
+                               "frompage"       => wikiFuzz::makeFuzz( 2 ),
+                               );
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["action"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["source"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["MAX_FILE_SIZE"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["xmlimport"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["interwiki"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["interwikiHistory"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["frompage"] );
+
+               // Note: Need to do a file upload to fully test this Special page.
+       }
+}
+
+
+/**
+ ** a test for thumb.php
+ */
+class thumbTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "thumb.php";
+
+               $this->params = array (
+                               "f"  => wikiFuzz::chooseInput( array( "..", "\\", "small-email.png", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "w"  => wikiFuzz::chooseInput( array( "80", wikiFuzz::randnum( 6000, -200 ), wikiFuzz::makeFuzz( 2 ) ) ),
+                               "r"  => wikiFuzz::chooseInput( array( "0", wikiFuzz::makeFuzz( 2 ) ) ),
+                               );
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["f"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["w"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["r"] );
+       }
+}
+
+
+/**
+ ** a test for trackback.php
+ */
+class trackbackTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "trackback.php";
+
+               $this->params = array (
+                               "url"       => wikiFuzz::makeFuzz( 2 ),
+                               "blog_name" => wikiFuzz::chooseInput( array( "80", wikiFuzz::randnum( 6000, -200 ), wikiFuzz::makeFuzz( 2 ) ) ),
+                               "article"   => wikiFuzz::chooseInput( array( "Main Page", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "title"     => wikiFuzz::chooseInput( array( "Main Page", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "excerpt"   => wikiFuzz::makeFuzz( 2 ),
+                               );
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["title"] );
+               if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["excerpt"] );
+
+               // page does not produce HTML.
+               $this->tidyValidate = false;
+       }
+}
+
+
+/**
+ ** a test for profileinfo.php
+ */
+class profileInfo extends pageTest {
+       function __construct() {
+               $this->pagePath = "profileinfo.php";
+
+               $this->params = array (
+                               "expand"  => wikiFuzz::makeFuzz( 2 ),
+                               "sort"    => wikiFuzz::chooseInput( array( "time", "count", "name", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "filter"  => wikiFuzz::chooseInput( array( "Main Page", wikiFuzz::makeFuzz( 2 ) ) ),
+                               );
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["sort"] );
+               if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["filter"] );
+       }
+}
+
+
+/**
+ ** a test for Special:Cite (extension Special page).
+ */
+class specialCitePageTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Cite";
+
+               $this->params = array (
+                               "page"    => wikiFuzz::chooseInput( array( "\" onmouseover=\"alert(1);\"", "Main Page", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "id"      => wikiFuzz::chooseInput( array( "-1", "0", "------'-------0", "+1", "-9823412312312412435", wikiFuzz::makeFuzz( 2 ) ) ),
+                               );
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["page"] );
+               if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["id"] );
+       }
+}
+
+
+/**
+ ** a test for Special:Filepath (extension Special page).
+ */
+class specialFilepathPageTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Filepath";
+
+               $this->params = array (
+                               "file"    => wikiFuzz::chooseInput( array( "Small-email.png", "Small-email.png" . wikiFuzz::makeFuzz( 1 ), wikiFuzz::makeFuzz( 2 ) ) ),
+                               );
+       }
+}
+
+
+/**
+ ** a test for Special:Makebot (extension Special page).
+ */
+class specialMakebot extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Makebot";
+
+               $this->params = array (
+                               "username" => wikiFuzz::chooseInput( array( "Nickj2", "192.168.0.2", wikiFuzz::makeFuzz( 1 ) ) ),
+                               "dosearch" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "grant"    => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "comment"  => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "token"    => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
+                               );
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 2 ) == 0 ) unset( $this->params["dosearch"] );
+               if ( wikiFuzz::randnum( 2 ) == 0 ) unset( $this->params["grant"] );
+               if ( wikiFuzz::randnum( 5 ) == 0 ) unset( $this->params["token"] );
+       }
+}
+
+
+/**
+ ** a test for Special:Makesysop (extension Special page).
+ */
+class specialMakesysop extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Makesysop";
+
+               $this->params = array (
+                               "wpMakesysopUser"   => wikiFuzz::chooseInput( array( "Nickj2", "192.168.0.2", wikiFuzz::makeFuzz( 1 ) ) ),
+                               "action"            => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "wpMakesysopSubmit" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "wpEditToken"       => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
+                               "wpSetBureaucrat"   => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
+                               );
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["wpMakesysopSubmit"] );
+               if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["wpEditToken"] );
+               if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["wpSetBureaucrat"] );
+       }
+}
+
+
+/**
+ ** a test for Special:Renameuser (extension Special page).
+ */
+class specialRenameuserPageTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Renameuser";
+
+               $this->params = array (
+                               "oldusername"   => wikiFuzz::chooseInput( array( "Nickj2", "192.168.0.2", wikiFuzz::makeFuzz( 1 ) ) ),
+                               "newusername"   => wikiFuzz::chooseInput( array( "Nickj2", "192.168.0.2", wikiFuzz::makeFuzz( 1 ) ) ),
+                               "token"         => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
+                               );
+       }
+}
+
+
+/**
+ ** a test for Special:Linksearch (extension Special page).
+ */
+class specialLinksearch extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special%3ALinksearch";
+
+               $this->params = array (
+                               "target" => wikiFuzz::makeFuzz( 2 ),
+                               );
+
+               // sometimes we don't want to specify certain parameters.
+               if ( wikiFuzz::randnum( 10 ) == 0 ) unset( $this->params["target"] );
+       }
+}
+
+
+/**
+ ** a test for Special:CategoryTree (extension Special page).
+ */
+class specialCategoryTree extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:CategoryTree";
+
+               $this->params = array (
+                               "target" => wikiFuzz::makeFuzz( 2 ),
+                               "from"   => wikiFuzz::makeFuzz( 2 ),
+                               "until"  => wikiFuzz::makeFuzz( 2 ),
+                               "showas" => wikiFuzz::makeFuzz( 2 ),
+                               "mode"   => wikiFuzz::chooseInput( array( "pages", "categories", "all", wikiFuzz::makeFuzz( 2 ) ) ),
+                               );
+
+               // sometimes we do want to specify certain parameters.
+               if ( wikiFuzz::randnum( 5 ) == 0 ) $this->params["notree"] = wikiFuzz::chooseInput( array( "1", 0, "", wikiFuzz::makeFuzz( 2 ) ) );
+       }
+}
+
+
+/**
+ ** a test for "Special:Chemicalsources" (extension Special page).
+ */
+class specialChemicalsourcesTest extends pageTest {
+       function __construct() {
+               $this->pagePath = "index.php?title=Special:Chemicalsources";
+
+               // choose an input format to use.
+               $format =  wikiFuzz::chooseInput(
+                                       array(  'go',
+                                                       'CAS',
+                                                       'EINECS',
+                                                       'CHEBI',
+                                                       'PubChem',
+                                                       'SMILES',
+                                                       'InChI',
+                                                       'ATCCode',
+                                                       'KEGG',
+                                                       'RTECS',
+                                                       'ECNumber',
+                                                       'DrugBank',
+                                                       'Formula',
+                                                       'Name'
+                                                )
+                                       );
+
+               // values for different formats usually start with either letters or numbers.
+               switch ( $format ) {
+                       case 'Name'   : $value = "A"; break;
+                       case 'InChI'  :
+                       case 'SMILES' :
+                       case 'Formula': $value = "C"; break;
+                       default       : $value = "0"; break;
+               }
+
+               // and then we append the fuzz input.
+               $this->params = array ( $format => $value . wikiFuzz::makeFuzz( 2 ) );
+       }
+}
+
+
+/**
+ ** A test for api.php (programmatic interface to MediaWiki in XML/JSON/RSS/etc formats).
+ ** Quite involved to test because there are lots of options/parameters, and because
+ ** for a lot of the functionality if all the parameters don't make sense then it just
+ ** returns the help screen - so currently a lot of the tests aren't actually doing much
+ ** because something wasn't right in the query.
+ **
+ ** @todo: Incomplete / unfinished; Runs too fast (suggests not much testing going on).
+ */
+class api extends pageTest {
+
+       // API login mode.
+       private static function loginMode() {
+               $arr =  array ( "lgname"        => wikiFuzz::makeFuzz( 2 ),
+                                               "lgpassword"    => wikiFuzz::makeFuzz( 2 ),
+                                          );
+               // sometimes we want to specify the extra "lgdomain" parameter.
+               if ( wikiFuzz::randnum( 3 ) == 0 ) {
+                       $arr["lgdomain"] = wikiFuzz::chooseInput( array( "1", 0, "", wikiFuzz::makeFuzz( 2 ) ) );
+               }
+
+               return $arr;
+       }
+
+       // API OpenSearch mode.
+       private static function opensearchMode() {
+               return array ( "search"        => wikiFuzz::makeFuzz( 2 ) );
+       }
+
+       // API watchlist feed mode.
+       private static function feedwatchlistMode() {
+               // @todo FIXME: Add "wikiFuzz::makeFuzz(2)" as possible value below?
+               return array ( "feedformat"    => wikiFuzz::chooseInput( array( "rss", "atom" ) ) );
+       }
+
+       // API query mode.
+       private static function queryMode() {
+               // @todo FIXME: Add "wikiFuzz::makeFuzz(2)" as possible params for the elements below?
+               //        Suspect this will stuff up the tests more, but need to check.
+               $params = array (
+                                        // @todo FIXME: More titles.
+                                        "titles"        => wikiFuzz::chooseInput( array( "Main Page" ) ),
+                                        // @todo FIXME: More pageids.
+                                        "pageids"       => 1,
+                                        "prop"          => wikiFuzz::chooseInput( array( "info", "revisions", "watchlist" ) ),
+                                        "list"          => wikiFuzz::chooseInput( array( "allpages", "logevents", "watchlist", "usercontribs", "recentchanges", "backlinks", "embeddedin", "imagelinks" ) ),
+                                        "meta"          => wikiFuzz::chooseInput( array( "siteinfo" ) ),
+                                        "generator"     => wikiFuzz::chooseInput( array( "allpages", "logevents", "watchlist", "info", "revisions" ) ),
+                                        "siprop"        => wikiFuzz::chooseInput( array( "general", "namespaces", "general|namespaces" ) ),
+                                  );
+
+                // Add extra parameters based on what list choice we got.
+                switch ( $params["list"] ) {
+                       case "usercontribs" : self::addListParams ( $params, "uc", array( "limit", "start", "end", "user", "dir" ) ); break;
+                       case "allpages"     : self::addListParams ( $params, "ap", array( "from", "prefix", "namespace", "filterredir", "limit" ) ); break;
+                       case "watchlist"    : self::addListParams ( $params, "wl", array( "allrev", "start", "end", "namespace", "dir", "limit", "prop" ) ); break;
+                       case "logevents"    : self::addListParams ( $params, "le", array( "limit", "type", "start", "end", "user", "dir" ) ); break;
+                       case "recentchanges": self::addListParams ( $params, "rc", array( "limit", "prop", "show", "namespace", "start", "end", "dir" ) ); break;
+                       case "backlinks"    : self::addListParams ( $params, "bl", array( "continue", "namespace", "redirect", "limit" ) ); break;
+                       case "embeddedin"   : self::addListParams ( $params, "ei", array( "continue", "namespace", "redirect", "limit" ) ); break;
+                       case "imagelinks"   : self::addListParams ( $params, "il", array( "continue", "namespace", "redirect", "limit" ) ); break;
+                }
+
+                if ( $params["prop"] == "revisions" ) {
+                       self::addListParams ( $params, "rv", array( "prop", "limit", "startid", "endid", "end", "dir" ) );
+                }
+
+                // Sometimes we want redirects, sometimes we don't.
+                if ( wikiFuzz::randnum( 3 ) == 0 ) {
+                       $params["redirects"] = wikiFuzz::chooseInput( array( "1", 0, "", wikiFuzz::makeFuzz( 2 ) ) );
+                }
+
+                return $params;
+       }
+
+       // Adds all the elements to the array, using the specified prefix.
+       private static function addListParams( &$array, $prefix, $elements )  {
+               foreach ( $elements as $element ) {
+                       $array[$prefix . $element] = self::getParamDetails( $element );
+               }
+       }
+
+       // For a given element name, returns the data for that element.
+       private static function getParamDetails( $element ) {
+               switch ( $element ) {
+                       case 'startid'    :
+                       case 'endid'      :
+                       case 'start'      :
+                       case 'end'        :
+                       case 'limit'      : return wikiFuzz::chooseInput( array( "0", "-1", "---'----------0", "+1", "8134", "320742734234235", "20060230121212", wikiFuzz::randnum( 9000, -100 ), wikiFuzz::makeFuzz( 2 ) ) );
+                       case 'dir'        : return wikiFuzz::chooseInput( array( "newer", "older", wikiFuzz::makeFuzz( 2 ) ) );
+                       case 'user'       : return wikiFuzz::chooseInput( array( USER_ON_WIKI, wikiFuzz::makeFuzz( 2 ) ) );
+                       case 'namespace'  : return wikiFuzz::chooseInput( array( -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 200000, wikiFuzz::makeFuzz( 2 ) ) );
+                       case 'filterredir': return wikiFuzz::chooseInput( array( "all", "redirects", "nonredirectsallpages", wikiFuzz::makeFuzz( 2 ) ) );
+                       case 'allrev'     : return wikiFuzz::chooseInput( array( "1", 0, "", wikiFuzz::makeFuzz( 2 ) ) );
+                       case 'prop'       : return wikiFuzz::chooseInput( array( "user", "comment", "timestamp", "patrol", "flags", "user|user|comment|flags", wikiFuzz::makeFuzz( 2 ) ) );
+                       case 'type'       : return wikiFuzz::chooseInput( array( "block", "protect", "rights", "delete", "upload", "move", "import", "renameuser", "newusers", "makebot", wikiFuzz::makeFuzz( 2 ) ) );
+                       case 'hide'       : return wikiFuzz::chooseInput( array( "minor", "bots", "anons", "liu", "liu|bots|", wikiFuzz::makeFuzz( 2 ) ) );
+                       case 'show'       : return wikiFuzz::chooseInput( array( 'minor', '!minor', 'bot', '!bot', 'anon', '!anon', wikiFuzz::makeFuzz( 2 ) ) );
+                       default           : return wikiFuzz::makeFuzz( 2 );
+               }
+       }
+
+       // Entry point.
+       function __construct() {
+               $this->pagePath = "api.php";
+
+               $modes = array ( "help",
+                                               "login",
+                                               "opensearch",
+                                               "feedwatchlist",
+                                               "query" );
+               $action = wikiFuzz::chooseInput( array_merge ( $modes, array( wikiFuzz::makeFuzz( 2 ) ) ) );
+
+               switch ( $action ) {
+                       case "login"         : $this->params = self::loginMode();
+                                                                  break;
+                       case "opensearch"    : $this->params = self::opensearchMode();
+                                                                  break;
+                       case "feedwatchlist" : $this->params = self::feedwatchlistMode();
+                                                                  break;
+                       case "query"         : $this->params = self::queryMode();
+                                                                  break;
+                       case "help"         :
+                       default             :  // Do something random - "Crazy Ivan" mode.
+                                                                  $random_mode = wikiFuzz::chooseInput( $modes ) . "Mode";
+                                                                  // There is no "helpMode".
+                                                                  if ( $random_mode == "helpMode" ) $random_mode = "queryMode";
+                                                                  $this->params = self::$random_mode();
+                                                                  break;
+               }
+
+               // Save the selected action.
+               $this->params["action"] = $action;
+
+               // Set the cookie:
+               // @todo FIXME: Need to get this cookie dynamically set, rather than hard-coded.
+               $this->cookie = "wikidbUserID=10001; wikidbUserName=Test; wikidb_session=178df0fe68c75834643af65dec9ec98a; wikidbToken=1adc6753d62c44aec950c024d7ae0540";
+
+               // Output format
+               $this->params["format"] = wikiFuzz::chooseInput( array( "json", "jsonfm", "php", "phpfm",
+                                                                                                                          "wddx", "wddxfm", "xml", "xmlfm",
+                                                                                                                          "yaml", "yamlfm", "raw", "rawfm",
+                                                                                                                          wikiFuzz::makeFuzz( 2 ) ) );
+
+               // Page does not produce HTML (sometimes).
+               $this->tidyValidate = false;
+       }
+}
+
+
+/**
+ ** a page test for the GeSHi extension.
+ */
+class GeSHi_Test extends pageTest {
+
+       private function getGeSHiContent() {
+               return "<source lang=\"" . $this->getLang() . "\" "
+                          . ( wikiFuzz::randnum( 2 ) == 0 ? "line " : "" )
+                          . ( wikiFuzz::randnum( 2 ) == 0 ? "strict " : "" )
+                          . "start=" . wikiFuzz::chooseInput( array( wikiFuzz::randnum( 6000, -6000 ), wikiFuzz::makeFuzz( 2 ) ) )
+                          . ">"
+                          . wikiFuzz::makeFuzz( 2 )
+                          . "</source>";
+       }
+
+       private function getLang() {
+       return wikiFuzz::chooseInput( array( "actionscript", "ada", "apache", "applescript", "asm", "asp", "autoit", "bash", "blitzbasic", "bnf", "c", "c_mac", "caddcl", "cadlisp",
+                               "cfdg", "cfm", "cpp", "cpp-qt", "csharp", "css", "d", "delphi", "diff", "div", "dos", "eiffel", "fortran", "freebasic", "gml", "groovy", "html4strict", "idl",
+                               "ini", "inno", "io", "java", "java5", "javascript", "latex", "lisp", "lua", "matlab", "mirc", "mpasm", "mysql", "nsis", "objc", "ocaml", "ocaml-brief", "oobas",
+                               "oracle8", "pascal", "perl", "php", "php-brief", "plsql", "python", "qbasic", "rails", "reg", "robots", "ruby", "sas", "scheme", "sdlbasic", "smalltalk", "smarty",
+                               "sql", "tcl", "text", "thinbasic", "tsql", "vb", "vbnet", "vhdl", "visualfoxpro", "winbatch", "xml", "xpp", "z80", wikiFuzz::makeFuzz( 1 ) ) );
+       }
+
+       function __construct() {
+               $this->pagePath = "index.php?title=WIKIFUZZ";
+
+               $this->params = array (
+                               "action"        => "submit",
+                               "wpMinoredit"   => "test",
+                               "wpPreview"     => "test",
+                               "wpSection"     => "test",
+                               "wpEdittime"    => "test",
+                               "wpSummary"     => "test",
+                               "wpScrolltop"   => "test",
+                               "wpStarttime"   => "test",
+                               "wpAutoSummary" => "test",
+                               "wpTextbox1"    => $this->getGeSHiContent() // the main wiki text, contains fake GeSHi content.
+                               );
+       }
+}
+
+
+/**
+ ** selects a page test to run.
+ */
+function selectPageTest( $count ) {
+
+       // if the user only wants a specific test, then only ever give them that.
+       if ( defined( "SPECIFIC_TEST" ) ) {
+               $testType = SPECIFIC_TEST;
+               return new $testType ();
+       }
+
+       // Some of the time we test Special pages, the remaining
+       // time we test using the standard edit page.
+       switch ( $count % 100 ) {
+               case 0 : return new successfulUserLoginTest();
+               case 1 : return new listusersTest();
+               case 2 : return new searchTest();
+               case 3 : return new recentchangesTest();
+               case 4 : return new prefixindexTest();
+               case 5 : return new mimeSearchTest();
+               case 6 : return new specialLogTest();
+               case 7 : return new userLoginTest();
+               case 8 : return new ipblocklistTest();
+               case 9 : return new newImagesTest();
+               case 10: return new imagelistTest();
+               case 11: return new specialExportTest();
+               case 12: return new specialBooksourcesTest();
+               case 13: return new specialAllpagesTest();
+               case 14: return new pageHistoryTest();
+               case 15: return new contributionsTest();
+               case 16: return new viewPageTest();
+               case 17: return new specialAllmessagesTest();
+               case 18: return new specialNewpagesPageTest();
+               case 19: return new searchTest();
+               case 20: return new redirectTest();
+               case 21: return new confirmEmail();
+               case 22: return new watchlistTest();
+               case 23: return new specialBlockmeTest();
+               case 24: return new specialUndeletePageTest();
+               case 25: return new specialMovePage();
+               case 26: return new specialUnlockdbPageTest();
+               case 27: return new specialLockdbPageTest();
+               case 28: return new specialUserrights();
+               case 29: return new pageProtectionForm();
+               case 30: return new specialBlockip();
+               case 31: return new imagepageTest();
+               case 32: return new pageDeletion();
+               case 33: return new specialRevisionDeletePageTest();
+               case 34: return new specialImportPageTest();
+               case 35: return new thumbTest();
+               case 36: return new trackbackTest();
+               case 37: return new profileInfo();
+               case 38: return new specialCitePageTest();
+               case 39: return new specialFilepathPageTest();
+               case 40: return new specialMakebot();
+               case 41: return new specialMakesysop();
+               case 42: return new specialRenameuserPageTest();
+               case 43: return new specialLinksearch();
+               case 44: return new specialCategoryTree();
+               case 45: return new api();
+               case 45: return new specialChemicalsourcesTest();
+               default: return new editPageTest();
+       }
+}
+
+
+// /////////////////////  SAVING OUTPUT  /////////////////////////
+
+/**
+ ** Utility function for saving a file. Currently has no error checking.
+ */
+function saveFile( $data, $name ) {
+       file_put_contents( $name, $data );
+}
+
+
+/**
+ ** Returns a test as an experimental GET-to-POST URL.
+ **        This doesn't seem to always work though, and sometimes the output is too long
+ **        to be a valid GET URL, so we also save in other formats.
+ */
+function getAsURL( pageTest $test ) {
+       $used_question_mark = ( strpos( $test->getPagePath(), "?" ) !== false );
+       $retval = "http://get-to-post.nickj.org/?" . WIKI_BASE_URL . $test->getPagePath();
+       foreach ( $test->getParams() as $param => $value ) {
+               if ( !$used_question_mark ) {
+                       $retval .= "?";
+                       $used_question_mark = true;
+               }
+               else {
+                       $retval .= "&";
+               }
+               $retval .= $param . "=" . urlencode( $value );
+       }
+       return $retval;
+}
+
+
+/**
+ ** Saves a plain-text human-readable version of a test.
+ */
+function saveTestAsText( pageTest $test, $filename ) {
+       $str = "Test: " . $test->getPagePath();
+       foreach ( $test->getParams() as $param => $value ) {
+               $str .= "\n$param: $value";
+       }
+       $str .= "\nGet-to-post URL: " . getAsURL( $test ) . "\n";
+       saveFile( $str, $filename );
+}
+
+
+/**
+ ** Saves a test as a standalone basic PHP script that shows this one problem.
+ **        Resulting script requires PHP-Curl be installed in order to work.
+ */
+function saveTestAsPHP( pageTest $test, $filename ) {
+       $str = "<?php\n"
+               . "\$params = " . var_export( escapeForCurl( $test->getParams() ), true ) . ";\n"
+               . "\$ch = curl_init();\n"
+               . "curl_setopt(\$ch, CURLOPT_POST, 1);\n"
+               . "curl_setopt(\$ch, CURLOPT_POSTFIELDS, \$params );\n"
+               . "curl_setopt(\$ch, CURLOPT_URL, " . var_export( WIKI_BASE_URL . $test->getPagePath(), true ) . ");\n"
+               . "curl_setopt(\$ch, CURLOPT_RETURNTRANSFER,1);\n"
+               .  ( $test->getCookie() ? "curl_setopt(\$ch, CURLOPT_COOKIE, " . var_export( $test->getCookie(), true ) . ");\n" : "" )
+               . "\$result=curl_exec(\$ch);\n"
+               . "curl_close (\$ch);\n"
+               . "print \$result;\n"
+               . "\n";
+       saveFile( $str, $filename );
+}
+
+
+/**
+ ** Escapes a value so that it can be used on the command line by Curl.
+ **        Specifically, "<" and "@" need to be escaped if they are the first character,
+ **        otherwise  curl interprets these as meaning that we want to insert a file.
+ */
+function escapeForCurl( array $input_params ) {
+       $output_params = array();
+       foreach ( $input_params as $param => $value ) {
+               if ( strlen( $value ) > 0 && ( $value[0] == "@" || $value[0] == "<" ) ) {
+                       $value = "\\" . $value;
+               }
+               $output_params[$param] = $value;
+       }
+       return $output_params;
+}
+
+
+/**
+ ** Saves a test as a standalone CURL shell script that shows this one problem.
+ **        Resulting script requires standalone Curl be installed in order to work.
+ */
+function saveTestAsCurl( pageTest $test, $filename ) {
+       $str = "#!/bin/bash\n"
+               . "curl --silent --include --globoff \\\n"
+               . ( $test->getCookie() ? " --cookie " . escapeshellarg( $test->getCookie() ) . " \\\n" : "" );
+       foreach ( escapeForCurl( $test->getParams() ) as $param => $value ) {
+               $str .= " -F " . escapeshellarg( $param ) . "=" . escapeshellarg( $value ) . " \\\n";
+       }
+       $str .= " " . escapeshellarg( WIKI_BASE_URL . $test->getPagePath() ); // beginning space matters.
+       $str .= "\n";
+       saveFile( $str, $filename );
+       chmod( $filename, 0755 ); // make executable
+}
+
+
+/**
+ ** Saves the internal data structure to file.
+ */
+function saveTestData ( pageTest $test, $filename ) {
+       saveFile( serialize( $test ),  $filename );
+}
+
+
+/**
+ ** saves a test in the various formats.
+ */
+function saveTest( pageTest $test, $testname ) {
+       $base_name = DIRECTORY . "/" . $testname;
+       saveTestAsText( $test, $base_name . INFO_FILE );
+       saveTestAsPHP ( $test, $base_name . PHP_TEST );
+       saveTestAsCurl( $test, $base_name . CURL_TEST );
+       saveTestData  ( $test, $base_name . DATA_FILE );
+}
+
+
+// ////////////////// MEDIAWIKI OUTPUT /////////////////////////
+
+/**
+ ** Asks MediaWiki for the HTML output of a test.
+ */
+function wikiTestOutput( pageTest $test ) {
+
+       $ch = curl_init();
+
+       // specify the cookie, if required.
+       if ( $test->getCookie() ) curl_setopt( $ch, CURLOPT_COOKIE, $test->getCookie() );
+       curl_setopt( $ch, CURLOPT_POST, 1 );                          // save form using a POST
+
+       $params = escapeForCurl( $test->getParams() );
+       curl_setopt( $ch, CURLOPT_POSTFIELDS, $params );             // load the POST variables
+
+       curl_setopt( $ch, CURLOPT_URL, WIKI_BASE_URL . $test->getPagePath() );  // set url to post to
+       curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );                 // return into a variable
+
+       $result = curl_exec ( $ch );
+
+       // if we encountered an error, then say so, and return an empty string.
+       if ( curl_error( $ch ) ) {
+               print "\nCurl error #: " . curl_errno( $ch ) . " - " . curl_error ( $ch );
+               $result = "";
+       }
+
+       curl_close ( $ch );
+
+       return $result;
+}
+
+
+// ////////////////// HTML VALIDATION /////////////////////////
+
+/*
+ ** Asks the validator whether this is valid HTML, or not.
+ */
+function validateHTML( $text ) {
+
+       $params = array ( "fragment"   => $text );
+
+       $ch = curl_init();
+
+       curl_setopt( $ch, CURLOPT_POST, 1 );                    // save form using a POST
+       curl_setopt( $ch, CURLOPT_POSTFIELDS, $params );        // load the POST variables
+       curl_setopt( $ch, CURLOPT_URL, VALIDATOR_URL );         // set url to post to
+       curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );           // return into a variable
+
+       $result = curl_exec ( $ch );
+
+       // if we encountered an error, then log it, and exit.
+       if ( curl_error( $ch ) ) {
+               trigger_error( "Curl error #: " . curl_errno( $ch ) . " - " . curl_error ( $ch ) );
+               print "Curl error #: " . curl_errno( $ch ) . " - " . curl_error ( $ch ) . " - exiting.\n";
+               exit( 1 );
+       }
+
+       curl_close ( $ch );
+
+       $valid = ( strpos( $result, "Failed validation" ) === false );
+
+       return array( $valid, $result );
+}
+
+
+/**
+ ** Get tidy to check for no HTML errors in the output file (e.g. unescaped strings).
+ */
+function tidyCheckFile( $name ) {
+       $file = DIRECTORY . "/" . $name;
+       $command = PATH_TO_TIDY . " -output /tmp/out.html -quiet $file 2>&1";
+       $x = `$command`;
+
+       // Look for the most interesting Tidy errors and warnings.
+       if (   strpos( $x, "end of file while parsing attributes" ) !== false
+                       || strpos( $x, "attribute with missing trailing quote mark" ) !== false
+                       || strpos( $x, "missing '>' for end of tag" ) !== false
+                       || strpos( $x, "Error:" ) !== false ) {
+               print "\nTidy found something - view details with: $command";
+               return false;
+       } else {
+               return true;
+       }
+}
+
+
+/**
+ ** Returns whether or not an database error log file has changed in size since
+ **        the last time this was run. This is used to tell if a test caused a DB error.
+ */
+function dbErrorLogged() {
+       static $filesize;
+
+       // first time running this function
+       if ( !isset( $filesize ) ) {
+               // create log if it does not exist
+               if ( DB_ERROR_LOG_FILE && !file_exists( DB_ERROR_LOG_FILE ) ) {
+                       saveFile( '', DB_ERROR_LOG_FILE );
+               }
+               $filesize = filesize( DB_ERROR_LOG_FILE );
+               return false;
+       }
+
+       $newsize = filesize( DB_ERROR_LOG_FILE );
+       // if the log has grown, then assume the current test caused it.
+       if ( $newsize != $filesize ) {
+               $filesize = $newsize;
+               return true;
+       }
+
+       return false;
+}
+
+// //////////////// TOP-LEVEL PROBLEM-FINDING FUNCTION ////////////////////////
+
+/**
+ ** takes a page test, and runs it and tests it for problems in the output.
+ **        Returns: False on finding a problem, or True on no problems being found.
+ */
+function runWikiTest( pageTest $test, &$testname, $can_overwrite = false ) {
+
+       // by default don't overwrite a previous test of the same name.
+       while ( ! $can_overwrite && file_exists( DIRECTORY . "/" . $testname . DATA_FILE ) ) {
+               $testname .= "-" . mt_rand( 0, 9 );
+       }
+
+       $filename = DIRECTORY . "/" . $testname . DATA_FILE;
+
+       // Store the time before and after, to find slow pages.
+       $before = microtime( true );
+
+       // Get MediaWiki to give us the output of this test.
+       $wiki_preview = wikiTestOutput( $test );
+
+       $after = microtime( true );
+
+       // if we received no response, then that's interesting.
+       if ( $wiki_preview == "" ) {
+               print "\nNo response received for: $filename";
+               return false;
+       }
+
+       // save output HTML to file.
+       $html_file = DIRECTORY . "/" . $testname . HTML_FILE;
+       saveFile( $wiki_preview,  $html_file );
+
+       // if there were PHP errors in the output, then that's interesting too.
+       if (       strpos( $wiki_preview, "<b>Warning</b>: "        ) !== false
+                       || strpos( $wiki_preview, "<b>Fatal error</b>: "    ) !== false
+                       || strpos( $wiki_preview, "<b>Notice</b>: "         ) !== false
+                       || strpos( $wiki_preview, "<b>Error</b>: "          ) !== false
+                       || strpos( $wiki_preview, "<b>Strict Standards:</b>" ) !== false
+                       ) {
+               $error = substr( $wiki_preview, strpos( $wiki_preview, "</b>:" ) + 7, 50 );
+               // Avoid probable PHP bug with bad session ids; http://bugs.php.net/bug.php?id=38224
+               if ( $error != "Unknown: The session id contains illegal character" ) {
+                       print "\nPHP error/warning/notice in HTML output: $html_file ; $error";
+                       return false;
+               }
+       }
+
+       // if there was a MediaWiki Backtrace message in the output, then that's also interesting.
+       if ( strpos( $wiki_preview, "Backtrace:" ) !== false ) {
+               print "\nInternal MediaWiki error in HTML output: $html_file";
+               return false;
+       }
+
+       // if there was a Parser error comment in the output, then that's potentially interesting.
+       if ( strpos( $wiki_preview, "!-- ERR" ) !== false ) {
+               print "\nParser Error comment in HTML output: $html_file";
+               return false;
+       }
+
+       // if a database error was logged, then that's definitely interesting.
+       if ( dbErrorLogged() ) {
+               print "\nDatabase Error logged for: $filename";
+               return false;
+       }
+
+       // validate result
+       $valid = true;
+       if ( VALIDATE_ON_WEB ) {
+               list ( $valid, $validator_output ) = validateHTML( $wiki_preview );
+               if ( !$valid ) print "\nW3C web validation failed - view details with: html2text " . DIRECTORY . "/" . $testname . ".validator_output.html";
+       }
+
+       // Get tidy to check the page, unless we already know it produces non-XHTML output.
+       if ( $test->tidyValidate() ) {
+               $valid = tidyCheckFile( $testname . HTML_FILE ) && $valid;
+       }
+
+       // if it took more than 2 seconds to render, then it may be interesting too. (Possible DoS attack?)
+       if ( ( $after - $before ) >= 2 ) {
+               print "\nParticularly slow to render (" . round( $after - $before, 2 ) . " seconds): $filename";
+               return false;
+       }
+
+       if ( $valid ) {
+               // Remove temp HTML file if test was valid:
+               unlink( $html_file );
+       } elseif ( VALIDATE_ON_WEB ) {
+               saveFile( $validator_output,   DIRECTORY . "/" . $testname . ".validator_output.html" );
+       }
+
+       return $valid;
+}
+
+
+// ///////////////// RERUNNING OLD TESTS ///////////////////
+
+/**
+ ** We keep our failed tests so that they can be rerun.
+ **        This function does that retesting.
+ */
+function rerunPreviousTests() {
+       print "Retesting previously found problems.\n";
+
+       $dir_contents = scandir ( DIRECTORY );
+
+       // sort file into the order a normal person would use.
+       natsort ( $dir_contents );
+
+       foreach ( $dir_contents as $file ) {
+
+               // if file is not a test, then skip it.
+               // Note we need to escape any periods or will be treated as "any character".
+               $matches = array();
+               if ( !preg_match( "/(.*)" . str_replace( ".", "\.", DATA_FILE ) . "$/", $file, $matches ) ) continue;
+
+               // reload the test.
+               $full_path = DIRECTORY . "/" . $file;
+               $test = unserialize( file_get_contents( $full_path ) );
+
+               // if this is not a valid test, then skip it.
+               if ( ! $test instanceof pageTest ) {
+                       print "\nSkipping invalid test - $full_path";
+                       continue;
+               }
+
+               // The date format is in Apache log format, which makes it easier to locate
+               // which retest caused which error in the Apache logs (only happens usually if
+               // apache segfaults).
+               if ( !QUIET ) print "[" . date ( "D M d H:i:s Y" ) . "] Retesting $file (" . get_class( $test ) . ")";
+
+               // run test
+               $testname = $matches[1];
+               $valid = runWikiTest( $test, $testname, true );
+
+               if ( !$valid ) {
+                       saveTest( $test, $testname );
+                       if ( QUIET ) {
+                               print "\nTest: " . get_class( $test ) . " ; Testname: $testname\n------";
+                       } else {
+                               print "\n";
+                       }
+               }
+               else {
+                       if ( !QUIET ) print "\r";
+                       if ( DELETE_PASSED_RETESTS ) {
+                               $prefix = DIRECTORY . "/" . $testname;
+                               if ( is_file( $prefix . DATA_FILE ) ) unlink( $prefix . DATA_FILE );
+                               if ( is_file( $prefix . PHP_TEST ) ) unlink( $prefix . PHP_TEST );
+                               if ( is_file( $prefix . CURL_TEST ) ) unlink( $prefix . CURL_TEST );
+                               if ( is_file( $prefix . INFO_FILE ) ) unlink( $prefix . INFO_FILE );
+                       }
+               }
+       }
+
+       print "\nDone retesting.\n";
+}
+
+
+// ////////////////////  MAIN LOOP  ////////////////////////
+
+
+// first check whether CURL is installed, because sometimes it's not.
+if ( ! function_exists( 'curl_init' ) ) {
+       die( "Could not find 'curl_init' function. Is the curl extension compiled into PHP?\n" );
+}
+
+// Initialization of types. wikiFuzz doesn't have a constructor because we want to
+// access it staticly and not have any globals.
+wikiFuzz::$types = array_keys( wikiFuzz::$data );
+
+// Make directory if doesn't exist
+if ( !is_dir( DIRECTORY ) ) {
+       mkdir ( DIRECTORY, 0700 );
+}
+// otherwise, we first retest the things that we have found in previous runs
+else if ( RERUN_OLD_TESTS ) {
+       rerunPreviousTests();
+}
+
+// main loop.
+$start_time = date( "U" );
+$num_errors = 0;
+if ( !QUIET ) {
+       print "Beginning main loop. Results are stored in the " . DIRECTORY . " directory.\n";
+       print "Press CTRL+C to stop testing.\n";
+}
+
+for ( $count = 0; true; $count++ ) {
+       if ( !QUIET ) {
+               // spinning progress indicator.
+               switch( $count % 4 ) {
+                       case '0': print "\r/";  break;
+                       case '1': print "\r-";  break;
+                       case '2': print "\r\\"; break;
+                       case '3': print "\r|";  break;
+               }
+               print " $count";
+       }
+
+       // generate a page test to run.
+       $test = selectPageTest( $count );
+
+       $mins = ( date( "U" ) - $start_time ) / 60;
+       if ( !QUIET && $mins > 0 ) {
+               print ".  $num_errors poss errors. "
+                       . floor( $mins ) . " mins. "
+                       . round ( $count / $mins, 0 ) . " tests/min. "
+                       . get_class( $test ); // includes the current test name.
+       }
+
+       // run this test against MediaWiki, and see if the output was valid.
+       $testname = $count;
+       $valid = runWikiTest( $test, $testname, false );
+
+       // save the failed test
+       if ( ! $valid ) {
+               if ( QUIET ) {
+                       print "\nTest: " . get_class( $test ) . " ; Testname: $testname\n------";
+               } else {
+                       print "\n";
+               }
+               saveTest( $test, $testname );
+               $num_errors += 1;
+       } else if ( KEEP_PASSED_TESTS ) {
+               // print current time, with microseconds (matches "strace" format), and the test name.
+               print " " . date( "H:i:s." ) . substr( current( explode( " ", microtime() ) ), 2 ) . " " . $testname;
+               saveTest( $test, $testname );
+       }
+
+       // stop if we have reached max number of errors.
+       if ( defined( "MAX_ERRORS" ) && $num_errors >= MAX_ERRORS ) {
+               break;
+       }
+
+       // stop if we have reached max number of mins runtime.
+       if ( defined( "MAX_RUNTIME" ) && $mins >= MAX_RUNTIME ) {
+               break;
+       }
+}
+
+