]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - maintenance/fuzz-tester.php
MediaWiki 1.17.0
[autoinstallsdev/mediawiki.git] / maintenance / fuzz-tester.php
1 <?php
2 /**
3 * @file
4 * @ingroup Maintenance
5 * @author Nick Jenkins ( http://nickj.org/ ).
6 * @copyright 2006 Nick Jenkins
7 * @licence GNU General Public Licence 2.0
8
9 Started: 18 May 2006.
10
11 Description:
12   Performs fuzz-style testing of MediaWiki's parser and forms.
13
14 How:
15   - Generate lots of nasty wiki text.
16   - Ask the Parser to render that wiki text to HTML, or ask MediaWiki's forms
17         to deal with that wiki text.
18   - Check MediaWiki's output for problems.
19   - Repeat.
20
21 Why:
22   - To help find bugs.
23   - To help find security issues, or potential security issues.
24
25 What type of problems are being checked for:
26   - Unclosed tags.
27   - Errors or interesting warnings from Tidy.
28   - PHP errors / warnings / notices.
29   - MediaWiki internal errors.
30   - Very slow responses.
31   - No response from apache.
32   - Optionally checking for malformed HTML using the W3C validator.
33
34 Background:
35   Many of the wikiFuzz class methods are a modified PHP port,
36   of a "shameless" Python port, of LCAMTUF'S MANGELME:
37   - http://www.securiteam.com/tools/6Z00N1PBFK.html
38   - http://www.securityfocus.com/archive/1/378632/2004-10-15/2004-10-21/0
39
40 Video:
41   There's an XviD video discussing this fuzz tester. You can get it from:
42   http://files.nickj.org/MediaWiki/Fuzz-Testing-MediaWiki-xvid.avi
43
44 Requirements:
45   To run this, you will need:
46   - Command-line PHP5, with PHP-curl enabled (not all installations have this
47         enabled - try "apt-get install php5-curl" if you're on Debian to install).
48   - the Tidy standalone executable. ("apt-get install tidy").
49
50 Optional:
51   - If you want to run the curl scripts, you'll need standalone curl installed
52         ("apt-get install curl")
53   - For viewing the W3C validator output on a command line, the "html2text"
54         program may be useful ("apt-get install html2text")
55
56 Saving tests and test results:
57   Any of the fuzz tests which find problems are saved for later review.
58   In order to help track down problems, tests are saved in a number of
59   different formats. The default filename extensions and their meanings are:
60   - ".test.php" : PHP script that reproduces just that one problem using PHP-Curl.
61   - ".curl.sh"  : Shell script that reproduces that problem using standalone curl.
62   - ".data.bin" : The serialized PHP data so that this script can re-run the test.
63   - ".info.txt" : A human-readable text file with details of the field contents.
64
65 Wiki configuration for testing:
66   You should make some additions to LocalSettings.php in order to catch the most
67   errors. Note this configuration is for **TESTING PURPOSES ONLY**, and is IN NO
68   WAY, SHAPE, OR FORM suitable for deployment on a hostile network. That said,
69   personally I find these additions to be the most helpful for testing purposes:
70
71   // --------- Start ---------
72   // Everyone can do everything. Very useful for testing, yet useless for deployment.
73   $wgGroupPermissions['*']['autoconfirmed']   = true;
74   $wgGroupPermissions['*']['block']           = true;
75   $wgGroupPermissions['*']['bot']             = true;
76   $wgGroupPermissions['*']['delete']          = true;
77   $wgGroupPermissions['*']['deletedhistory']  = true;
78   $wgGroupPermissions['*']['deleterevision']  = true;
79   $wgGroupPermissions['*']['editinterface']   = true;
80   $wgGroupPermissions['*']['hiderevision']    = true;
81   $wgGroupPermissions['*']['import']          = true;
82   $wgGroupPermissions['*']['importupload']    = true;
83   $wgGroupPermissions['*']['minoredit']       = true;
84   $wgGroupPermissions['*']['move']            = true;
85   $wgGroupPermissions['*']['patrol']          = true;
86   $wgGroupPermissions['*']['protect']         = true;
87   $wgGroupPermissions['*']['proxyunbannable'] = true;
88   $wgGroupPermissions['*']['renameuser']      = true;
89   $wgGroupPermissions['*']['reupload']        = true;
90   $wgGroupPermissions['*']['reupload-shared'] = true;
91   $wgGroupPermissions['*']['rollback']        = true;
92   $wgGroupPermissions['*']['siteadmin']       = true;
93   $wgGroupPermissions['*']['trackback']       = true;
94   $wgGroupPermissions['*']['unwatchedpages']  = true;
95   $wgGroupPermissions['*']['upload']          = true;
96   $wgGroupPermissions['*']['userrights']      = true;
97   $wgGroupPermissions['*']['renameuser']      = true;
98   $wgGroupPermissions['*']['makebot']         = true;
99   $wgGroupPermissions['*']['makesysop']       = true;
100
101   // Enable weird and wonderful options:
102                                                           // Increase default error reporting level.
103   error_reporting (E_ALL);    // At a later date could be increased to E_ALL | E_STRICT
104   $wgBlockOpenProxies = true; // Some block pages require this to be true in order to test.
105   $wgEnableUploads = true;    // enable uploads.
106   //$wgUseTrackbacks = true;  // enable trackbacks; However this breaks the viewPageTest, so currently disabled.
107   $wgDBerrorLog = "/root/mediawiki-db-error-log.txt";  // log DB errors, replace with suitable path.
108   $wgShowSQLErrors = true;    // Show SQL errors (instead of saying the query was hidden).
109   $wgShowExceptionDetails = true;  // want backtraces.
110   $wgEnableAPI = true;        // enable API.
111   $wgEnableWriteAPI = true;   // enable API.
112
113   // Install & enable Parser Hook extensions to increase code coverage. E.g.:
114   require_once("extensions/ParserFunctions/ParserFunctions.php");
115   require_once("extensions/Cite/Cite.php");
116   require_once("extensions/inputbox/inputbox.php");
117   require_once("extensions/Sort/Sort.php");
118   require_once("extensions/wikihiero/wikihiero.php");
119   require_once("extensions/CharInsert/CharInsert.php");
120   require_once("extensions/FixedImage/FixedImage.php");
121
122   // Install & enable Special Page extensions to increase code coverage. E.g.:
123   require_once("extensions/Cite/SpecialCite.php");
124   require_once("extensions/Filepath/SpecialFilepath.php");
125   require_once("extensions/Makebot/Makebot.php");
126   require_once("extensions/Makesysop/SpecialMakesysop.php");
127   require_once("extensions/Renameuser/SpecialRenameuser.php");
128   require_once("extensions/LinkSearch/LinkSearch.php");
129   // --------- End ---------
130
131   If you want to try E_STRICT error logging, add this to the above:
132   // --------- Start ---------
133   error_reporting (E_ALL | E_STRICT);
134   set_error_handler( 'error_handler' );
135   function error_handler ($type, $message, $file=__FILE__, $line=__LINE__) {
136          if ($message == "var: Deprecated. Please use the public/private/protected modifiers") return;
137          print "<br />\n<b>Strict Standards:</b> Type: <b>$type</b>:  $message in <b>$file</b> on line <b>$line</b><br />\n";
138   }
139   // --------- End ---------
140
141   Also add/change this in LocalSettings.php:
142   // --------- Start ---------
143   $wgEnableProfileInfo = true;
144   $wgDBserver = "localhost"; // replace with DB server hostname
145   // --------- End ---------
146
147 Usage:
148   Run with "php fuzz-tester.php".
149   To see the various command-line options, run "php fuzz-tester.php --help".
150   To stop the script, press Ctrl-C.
151
152 Console output:
153   - If requested, first any previously failed tests will be rerun.
154   - Then new tests will be generated and run. Any tests that fail will be saved,
155         and a brief message about why they failed will be printed on the console.
156   - The console will show the number of tests run, time run, number of tests
157         failed, number of tests being done per minute, and the name of the current test.
158
159 TODO:
160   Some known things that could improve this script:
161   - Logging in with cookie jar storage needed for some tests (as there are some
162         pages that cannot be tested without being logged in, and which are currently
163         untested - e.g. Special:Emailuser, Special:Preferences, adding to Watchist).
164   - Testing of Timeline extension (I cannot test as ploticus has/had issues on
165         my architecture).
166
167 */
168
169 // ///////////////////////// COMMAND LINE HELP ////////////////////////////////////
170
171 // This is a command line script, load MediaWiki env (gives command line options);
172 require_once( dirname( __FILE__ ) . '/commandLine.inc' );
173
174 // if the user asked for an explanation of command line options.
175 if ( isset( $options["help"] ) ) {
176         print <<<ENDS
177 MediaWiki $wgVersion fuzz tester
178 Usage: php {$_SERVER["SCRIPT_NAME"]} [--quiet] [--base-url=<url-to-test-wiki>]
179                                                    [--directory=<failed-test-path>] [--include-binary]
180                                                    [--w3c-validate] [--delete-passed-retests] [--help]
181                                                    [--user=<username>] [--password=<password>]
182                                                    [--rerun-failed-tests] [--max-errors=<int>]
183                                                    [--max-runtime=<num-minutes>]
184                                                    [--specific-test=<test-name>]
185
186 Options:
187   --quiet                 : Hides passed tests, shows only failed tests.
188   --base-url              : URL to a wiki on which to run the tests.
189                                                         The "http://" is optional and can be omitted.
190   --directory             : Full path to directory for storing failed tests.
191                                                         Will be created if it does not exist.
192   --include-binary        : Includes non-alphanumeric characters in the tests.
193   --w3c-validate          : Validates pages using the W3C's web validator.
194                                                         Slow. Currently many pages fail validation.
195   --user                  : Login name of a valid user on your test wiki.
196   --password              : Password for the valid user on your test wiki.
197   --delete-passed-retests : Will delete retests that now pass.
198                                                         Requires --rerun-failed-tests to be meaningful.
199   --rerun-failed-tests    : Whether to rerun any previously failed tests.
200   --max-errors            : Maximum number of errors to report before exiting.
201                                                         Does not include errors from --rerun-failed-tests
202   --max-runtime           : Maximum runtime, in minutes, to run before exiting.
203                                                         Only applies to new tests, not --rerun-failed-tests
204   --specific-test         : Runs only the specified fuzz test.
205                                                         Only applies to new tests, not --rerun-failed-tests
206   --keep-passed-tests     : Saves all test files, even those that pass.
207   --help                  : Show this help message.
208
209 Example:
210   If you wanted to fuzz test a nightly MediaWiki checkout using cron for 1 hour,
211   and only wanted to be informed of errors, and did not want to redo previously
212   failed tests, and wanted a maximum of 100 errors, then you could do:
213   php {$_SERVER["SCRIPT_NAME"]} --quiet --max-errors=100 --max-runtime=60
214
215
216 ENDS;
217
218         exit( 0 );
219 }
220
221
222 // if we got command line options, check they look valid.
223 $validOptions = array ( "quiet", "base-url", "directory", "include-binary",
224                 "w3c-validate", "user", "password", "delete-passed-retests",
225                 "rerun-failed-tests", "max-errors",
226                 "max-runtime", "specific-test", "keep-passed-tests", "help" );
227 if ( !empty( $options ) ) {
228         $unknownArgs = array_diff ( array_keys( $options ), $validOptions );
229         foreach ( $unknownArgs as $invalidArg ) {
230                 print "Ignoring invalid command-line option: --$invalidArg\n";
231         }
232 }
233
234
235 // /////////////////////////// CONFIGURATION ////////////////////////////////////
236
237 // URL to some wiki on which we can run our tests.
238 if ( !empty( $options["base-url"] ) ) {
239         define( "WIKI_BASE_URL", $options["base-url"] );
240 } else {
241         define( "WIKI_BASE_URL", $wgServer . $wgScriptPath . '/' );
242 }
243
244 // The directory name where we store the output.
245 // Example for Windows: "c:\\temp\\wiki-fuzz"
246 if ( !empty( $options["directory"] ) ) {
247         define( "DIRECTORY", $options["directory"] );
248 } else {
249         define( "DIRECTORY", "{$wgUploadDirectory}/fuzz-tests" );
250 }
251
252 // Should our test fuzz data include binary strings?
253 define( "INCLUDE_BINARY",  isset( $options["include-binary"] ) );
254
255 // Whether we want to validate HTML output on the web.
256 // At the moment very few generated pages will validate, so not recommended.
257 define( "VALIDATE_ON_WEB", isset( $options["w3c-validate"] ) );
258 // URL to use to validate our output:
259 define( "VALIDATOR_URL",  "http://validator.w3.org/check" );
260
261 // Location of Tidy standalone executable.
262 define( "PATH_TO_TIDY",  "/usr/bin/tidy" );
263
264 // The name of a user who has edited on your wiki. Used
265 // when testing the Special:Contributions and Special:Userlogin page.
266 if ( !empty( $options["user"] ) ) {
267         define( "USER_ON_WIKI", $options["user"] );
268 } else {
269         define( "USER_ON_WIKI", "nickj" );
270 }
271
272 // The password of the above user. Used when testing the login page,
273 // and to do this we sometimes need to login successfully.
274 if ( !empty( $options["password"] ) ) {
275         define( "USER_PASSWORD", $options["password"] );
276 } else {
277         // And no, this is not a valid password on any public wiki.
278         define( "USER_PASSWORD", "nickj" );
279 }
280
281 // If we have a test that failed, and then we run it again, and it passes,
282 // do you want to delete it or keep it?
283 define( "DELETE_PASSED_RETESTS", isset( $options["delete-passed-retests"] ) );
284
285 // Do we want to rerun old saved tests at script startup?
286 // Set to true to help catch regressions, or false if you only want new stuff.
287 define( "RERUN_OLD_TESTS", isset( $options["rerun-failed-tests"] ) );
288
289 // File where the database errors are logged. Should be defined in LocalSettings.php.
290 define( "DB_ERROR_LOG_FILE", $wgDBerrorLog );
291
292 // Run in chatty mode (all output, default), or run in quiet mode (only prints out details of failed tests)?
293 define( "QUIET", isset( $options["quiet"] ) );
294
295 // Keep all test files, even those that pass. Potentially useful to tracking input that causes something
296 // unusual to happen, if you don't know what "unusual" is until later.
297 define( "KEEP_PASSED_TESTS", isset( $options["keep-passed-tests"] ) );
298
299 // The maximum runtime, if specified.
300 if ( !empty( $options["max-runtime"] ) && intval( $options["max-runtime"] ) > 0 ) {
301         define( "MAX_RUNTIME", intval( $options["max-runtime"] ) );
302 }
303
304 // The maximum number of problems to find, if specified. Excludes retest errors.
305 if ( !empty( $options["max-errors"] ) && intval( $options["max-errors"] ) > 0 ) {
306         define( "MAX_ERRORS", intval( $options["max-errors"] ) );
307 }
308
309 // if the user has requested a specific test (instead of all tests), and the test they asked for looks valid.
310 if ( !empty( $options["specific-test"] ) ) {
311         if ( class_exists( $options["specific-test"] ) && get_parent_class( $options["specific-test"] ) == "pageTest" ) {
312                 define( "SPECIFIC_TEST", $options["specific-test"] );
313         }
314         else {
315                 print "Ignoring invalid --specific-test\n";
316         }
317 }
318
319 // Define the file extensions we'll use:
320 define( "PHP_TEST" , ".test.php" );
321 define( "CURL_TEST", ".curl.sh" );
322 define( "DATA_FILE", ".data.bin" );
323 define( "INFO_FILE", ".info.txt" );
324 define( "HTML_FILE", ".wiki_preview.html" );
325
326 // If it goes wrong, we want to know about it.
327 error_reporting( E_ALL | E_STRICT );
328
329 // //////////////  A CLASS THAT GENERATES RANDOM NASTY WIKI & HTML STRINGS  //////////////////////
330
331 class wikiFuzz {
332
333         // Only some HTML tags are understood with params by MediaWiki, the rest are ignored.
334         // List the tags that accept params below, as well as what those params are.
335         public static $data = array(
336                         "B"          => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
337                         "CAPTION"    => array( "CLASS", "ID", "STYLE", "align", "lang", "dir", "title" ),
338                         "CENTER"     => array( "CLASS", "STYLE", "ID", "lang", "dir", "title" ),
339                         "DIV"        => array( "CLASS", "STYLE", "ID", "align", "lang", "dir", "title" ),
340                         "FONT"       => array( "CLASS", "STYLE", "ID", "lang", "dir", "title", "face", "size", "color" ),
341                         "H1"         => array( "STYLE", "CLASS", "ID", "align", "lang", "dir", "title" ),
342                         "H2"         => array( "STYLE", "CLASS", "ID", "align", "lang", "dir", "title" ),
343                         "HR"         => array( "STYLE", "CLASS", "ID", "WIDTH", "lang", "dir", "title", "size", "noshade" ),
344                         "LI"         => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "type", "value" ),
345                         "TABLE"      => array( "STYLE", "CLASS", "ID", "BGCOLOR", "WIDTH", "ALIGN", "BORDER", "CELLPADDING",
346                                                                    "CELLSPACING", "lang", "dir", "title", "summary", "frame", "rules" ),
347                         "TD"         => array( "STYLE", "CLASS", "ID", "BGCOLOR", "WIDTH", "ALIGN", "COLSPAN", "ROWSPAN",
348                                                                   "VALIGN", "abbr", "axis", "headers", "scope", "nowrap", "height", "lang",
349                                                                   "dir", "title", "char", "charoff" ),
350                         "TH"         => array( "STYLE", "CLASS", "ID", "BGCOLOR", "WIDTH", "ALIGN", "COLSPAN", "ROWSPAN",
351                                                                   "VALIGN", "abbr", "axis", "headers", "scope", "nowrap", "height", "lang",
352                                                                   "dir", "title", "char", "charoff" ),
353                         "TR"         => array( "CLASS", "STYLE", "ID", "BGCOLOR", "ALIGN", "VALIGN", "lang", "dir", "title", "char", "charoff" ),
354                         "UL"         => array( "CLASS", "STYLE", "ID", "lang", "dir", "title", "type" ),
355                         "P"          => array( "style", "class", "id", "align", "lang", "dir", "title" ),
356                         "blockquote" => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "cite" ),
357                         "span"       => array( "CLASS", "ID", "STYLE", "align", "lang", "dir", "title" ),
358                         "code"       => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
359                         "tt"         => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
360                         "small"      => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
361                         "big"        => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
362                         "s"          => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
363                         "u"          => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
364                         "del"        => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "datetime", "cite" ),
365                         "ins"        => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "datetime", "cite" ),
366                         "sub"        => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
367                         "sup"        => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
368                         "ol"         => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "type", "start" ),
369                         "br"         => array( "CLASS", "ID", "STYLE", "title", "clear" ),
370                         "cite"       => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
371                         "var"        => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
372                         "dl"         => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
373                         "ruby"       => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
374                         "rt"         => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
375                         "rp"         => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
376                         "dt"         => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
377                         "dl"         => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
378                         "em"         => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
379                         "strong"     => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
380                         "i"          => array( "CLASS", "ID", "STYLE", "lang", "dir", "title" ),
381                         "thead"      => array( "CLASS", "ID", "STYLE", "lang", "dir", "title",  'align', 'char', 'charoff', 'valign' ),
382                         "tfoot"      => array( "CLASS", "ID", "STYLE", "lang", "dir", "title",  'align', 'char', 'charoff', 'valign' ),
383                         "tbody"      => array( "CLASS", "ID", "STYLE", "lang", "dir", "title",  'align', 'char', 'charoff', 'valign' ),
384                         "colgroup"   => array( "CLASS", "ID", "STYLE", "lang", "dir", "title",  'align', 'char', 'charoff', 'valign', 'span', 'width' ),
385                         "col"        => array( "CLASS", "ID", "STYLE", "lang", "dir", "title",  'align', 'char', 'charoff', 'valign', 'span', 'width' ),
386                         "pre"        => array( "CLASS", "ID", "STYLE", "lang", "dir", "title", "width" ),
387
388                         // extension tags that accept parameters:
389                         "sort"         => array( "order", "class" ),
390                         "ref"          => array( "name" ),
391                         "categorytree" => array( "hideroot", "mode", "style" ),
392                         "chemform"     => array( "link", "wikilink", "query" ),
393                         "section"      => array( "begin", "new" ),
394
395                         // older MW transclusion.
396                         "transclude"   => array( "page" ),
397                                 );
398
399         // The types of the HTML that we will be testing were defined above
400         // Note: this needs to be initialized later to be equal to: array_keys(wikiFuzz::$data);
401         // as such, it also needs to also be publicly modifiable.
402         public static $types;
403
404
405         // Some attribute values.
406         static private $other = array( "&", "=", ":", "?", "\"", "\n", "%n%n%n%n%n%n%n%n%n%n%n%n", "\\" );
407         static private $ints  = array(
408                         // various numbers
409                         "0", "-1", "127", "-7897", "89000", "808080", "90928345",
410                         "0xfffffff", "ffff",
411
412                         // Different ways of saying: '
413                         "&#0000039;", // Long UTF-8 Unicode encoding
414                         "&#39;",  // dec version.
415                         "&#x27;", // hex version.
416                         "&#xA7;", // malformed hex variant, MSB not zero.
417
418                         // Different ways of saying: "
419                         "&#0000034;", // Long UTF-8 Unicode encoding
420                         "&#34;",
421                         "&#x22;", // hex version.
422                         "&#xA2;", // malformed hex variant, MSB not zero.
423
424                         // Different ways of saying: <
425                         "<",
426                         "&#0000060",  // Long UTF-8 Unicode encoding without semicolon (Mediawiki wants the colon)
427                         "&#0000060;", // Long UTF-8 Unicode encoding with semicolon
428                         "&#60;",
429                         "&#x3C;",     // hex version.
430                         "&#xBC;",     // malformed hex variant, MSB not zero.
431                         "&#x0003C;",  // mid-length hex version
432                         "&#X00003C;", // slightly longer hex version, with capital "X"
433
434                         // Different ways of saying: >
435                         ">",
436                         "&#0000062;", // Long UTF-8 Unicode encoding
437                         "&#62;",
438                         "&#x3E;",     // hex version.
439                         "&#xBE;",     // malformed variant, MSB not zero.
440
441                         // Different ways of saying: [
442                         "&#0000091;", // Long UTF-8 Unicode encoding
443                         "&#91;",
444                         "&#x5B;",     // hex version.
445
446                         // Different ways of saying: {{
447                         "&#0000123;&#0000123;", // Long UTF-8 Unicode encoding
448                         "&#123;&#123;",
449                         "&#x7B;&#x7B;",         // hex version.
450
451                         // Different ways of saying: |
452                         "&#0000124;", // Long UTF-8 Unicode encoding
453                         "&#124;",
454                         "&#x7C;",     // hex version.
455                         "&#xFC;",     // malformed hex variant, MSB not zero.
456
457                         // a "lignature" - http://www.robinlionheart.com/stds/html4/spchars#ligature
458                         // &#8204; == &zwnj;
459                         "&#8204;"
460                                 );
461
462         // Defines various wiki-related bits of syntax, that can potentially cause
463         // MediaWiki to do something other than just print that literal text.
464         static private $ext = array(
465                         // links, templates, parameters.
466                         "[[", "]]", "{{", "}}", "|", "[", "]", "{{{", "}}}", "|]]",
467
468                         // wiki tables.
469                         "\n{|", "\n|}",
470                         "!",
471                         "\n!",
472                         "!!",
473                         "||",
474                         "\n|-", "| ", "\n|",
475
476                         // section headings.
477                         "=", "==", "===", "====", "=====", "======",
478
479                         // lists (ordered and unordered) and indentation.
480                         "\n*", "*", "\n:", ":",
481                         "\n#", "#",
482
483                         // definition lists (dl, dt, dd), newline, and newline with pre, and a tab.
484                         "\n;", ";", "\n ",
485
486                         // Whitespace: newline, tab, space.
487                         "\n", "\t", " ",
488
489                         // Some XSS attack vectors from http://ha.ckers.org/xss.html
490                         "&#x09;", // tab
491                         "&#x0A;", // newline
492                         "&#x0D;", // carriage return
493                         "\0",     // null character
494                         " &#14; ", // spaces and meta characters
495                         "'';!--\"<XSS>=&{()}", // compact injection of XSS & SQL tester
496
497                         // various NULL fields
498                         "%00",
499                         "&#00;",
500                         "\0",
501
502                         // horizontal rule.
503                         "-----", "\n-----",
504
505                         // signature, redirect, bold, italics.
506                         "~~~~", "#REDIRECT [[", "'''", "''",
507
508                         // comments.
509                         "<!--", "-->",
510
511                         // quotes.
512                         "\"", "'",
513
514                         // tag start and tag end.
515                         "<", ">",
516
517                         // implicit link creation on URIs.
518                         "http://",
519                         "https://",
520                         "ftp://",
521                         "irc://",
522                         "news:",
523                         'gopher://',
524                         'telnet://',
525                         'nntp://',
526                         'worldwind://',
527                         'mailto:',
528
529                         // images.
530                         "[[image:",
531                         ".gif",
532                         ".png",
533                         ".jpg",
534                         ".jpeg",
535                         'thumbnail=',
536                         'thumbnail',
537                         'thumb=',
538                         'thumb',
539                         'right',
540                         'none',
541                         'left',
542                         'framed',
543                         'frame',
544                         'enframed',
545                         'centre',
546                         'center',
547                         "Image:",
548                         "[[:Image",
549                         'px',
550                         'upright=',
551                         'border',
552
553                         // misc stuff to throw at the Parser.
554                         '%08X',
555                         '/',
556                         ":x{|",
557                         "\n|+",
558                         "<noinclude>",
559                         "</noinclude>",
560                         " \302\273",
561                         " :",
562                         " !",
563                         " ;",
564                         "\302\253",
565                         "[[category:",
566                         "?=",
567                         "(",
568                         ")",
569                         "]]]",
570                         "../",
571                         "{{{{",
572                         "}}}}",
573                         "[[Special:",
574                         "<includeonly>",
575                         "</includeonly>",
576                         "<!--MWTEMPLATESECTION=",
577                         '<!--MWTOC-->',
578
579                         // implicit link creation on booknum, RFC, and PubMed ID usage (both with and without IDs)
580                         "ISBN 2",
581                         "RFC 000",
582                         "PMID 000",
583                         "ISBN ",
584                         "RFC ",
585                         "PMID ",
586
587                         // magic words:
588                         '__NOTOC__',
589                         '__FORCETOC__',
590                         '__NOEDITSECTION__',
591                         '__START__',
592                         '__NOTITLECONVERT__',
593                         '__NOCONTENTCONVERT__',
594                         '__END__',
595                         '__TOC__',
596                         '__NOTC__',
597                         '__NOCC__',
598                         "__FORCETOC__",
599                         "__NEWSECTIONLINK__",
600                         "__NOGALLERY__",
601
602                         // more magic words / internal templates.
603                         '{{PAGENAME}}',
604                         '{{PAGENAMEE}}',
605                         '{{NAMESPACE}}',
606                         "{{MSG:",
607                         "}}",
608                         "{{MSGNW:",
609                         "}}",
610                         "{{INT:",
611                         "}}",
612                         '{{SITENAME}}',
613                         "{{NS:",
614                         "}}",
615                         "{{LOCALURL:",
616                         "}}",
617                         "{{LOCALURLE:",
618                         "}}",
619                         "{{SCRIPTPATH}}",
620                         "{{GRAMMAR:gentiv|",
621                         "}}",
622                         "{{REVISIONID}}",
623                         "{{SUBPAGENAME}}",
624                         "{{SUBPAGENAMEE}}",
625                         "{{ns:0}}",
626                         "{{fullurle:",
627                         "}}",
628                         "{{subst::",
629                         "}}",
630                         "{{UCFIRST:",
631                         "}}",
632                         "{{UC:",
633                         '{{SERVERNAME}}',
634                         '{{SERVER}}',
635                         "{{RAW:",
636                         "}}",
637                         "{{PLURAL:",
638                         "}}",
639                         "{{LCFIRST:",
640                         "}}",
641                         "{{LC:",
642                         "}}",
643                         '{{CURRENTWEEK}}',
644                         '{{CURRENTDOW}}',
645                         "{{INT:{{LC:contribs-showhideminor}}|",
646                         "}}",
647                         "{{INT:googlesearch|",
648                         "}}",
649                         "{{BASEPAGENAME}}",
650                         "{{CONTENTLANGUAGE}}",
651                         "{{PAGESINNAMESPACE:}}",
652                         "{{#language:",
653                         "}}",
654                         "{{#special:",
655                         "}}",
656                         "{{#special:emailuser",
657                         "}}",
658
659                         // Some raw link for magic words.
660                         "{{NUMBEROFPAGES:R",
661                         "}}",
662                         "{{NUMBEROFUSERS:R",
663                         "}}",
664                         "{{NUMBEROFARTICLES:R",
665                         "}}",
666                         "{{NUMBEROFFILES:R",
667                         "}}",
668                         "{{NUMBEROFADMINS:R",
669                         "}}",
670                         "{{padleft:",
671                         "}}",
672                         "{{padright:",
673                         "}}",
674                         "{{DEFAULTSORT:",
675                         "}}",
676
677                         // internal Math "extension":
678                         "<math>",
679                         "</math>",
680
681                         // Parser extension functions:
682                         "{{#expr:",
683                         "{{#if:",
684                         "{{#ifeq:",
685                         "{{#ifexist:",
686                         "{{#ifexpr:",
687                         "{{#switch:",
688                         "{{#time:",
689                         "}}",
690
691                         // references table for the Cite extension.
692                         "<references/>",
693
694                         // Internal Parser tokens - try inserting some of these.
695                         "UNIQ25f46b0524f13e67NOPARSE",
696                         "UNIQ17197916557e7cd6-HTMLCommentStrip46238afc3bb0cf5f00000002",
697                         "\x07UNIQ17197916557e7cd6-HTMLCommentStrip46238afc3bb0cf5f00000002-QINU",
698
699                         // Inputbox extension:
700                         "<inputbox>\ntype=search\nsearchbuttonlabel=\n",
701                         "</inputbox>",
702
703                         // charInsert extension:
704                         "<charInsert>",
705                         "</charInsert>",
706
707                         // wikiHiero extension:
708                         "<hiero>",
709                         "</hiero>",
710
711                         // Image gallery:
712                         "<gallery>",
713                         "</gallery>",
714
715                         // FixedImage extension.
716                         "<fundraising/>",
717
718                         // Timeline extension: currently untested.
719
720                         // Nowiki:
721                         "<nOwIkI>",
722                         "</nowiki>",
723
724                         // an external image to test the external image displaying code
725                         "http://debian.org/Pics/debian.png",
726
727                         // LabeledSectionTransclusion extension.
728                         "{{#lstx:",
729                         "}}",
730                         "{{#lst:",
731                         "}}",
732                         "{{#lst:Main Page|",
733                         "}}"
734                         );
735
736         /**
737          ** Randomly returns one element of the input array.
738          */
739         static public function chooseInput( array $input ) {
740                 $randindex = wikiFuzz::randnum( count( $input ) - 1 );
741                 return $input[$randindex];
742         }
743
744         // Max number of parameters for HTML attributes.
745         static private $maxparams = 10;
746
747         /**
748          ** Returns random number between finish and start.
749          */
750         static public function randnum( $finish, $start = 0 ) {
751                 return mt_rand( $start, $finish );
752         }
753
754         /**
755          ** Returns a mix of random text and random wiki syntax.
756          */
757         static private function randstring() {
758                 $thestring = "";
759
760                 for ( $i = 0; $i < 40; $i++ ) {
761                         $what = wikiFuzz::randnum( 1 );
762
763                         if ( $what == 0 ) { // include some random wiki syntax
764                                 $which = wikiFuzz::randnum( count( wikiFuzz::$ext ) - 1 );
765                                 $thestring .= wikiFuzz::$ext[$which];
766                         }
767                         else { // include some random text
768                                 $char = INCLUDE_BINARY
769                                         // Decimal version:
770                                         // "&#" . wikiFuzz::randnum(255) . ";"
771                                         // Hex version:
772                                         ? "&#x" . str_pad( dechex( wikiFuzz::randnum( 255 ) ), wikiFuzz::randnum( 2, 7 ), "0", STR_PAD_LEFT ) . ";"
773                                         // A truly binary version:
774                                         // ? chr(wikiFuzz::randnum(0,255))
775                                         : chr( wikiFuzz::randnum( 126, 32 ) );
776
777                                 $length = wikiFuzz::randnum( 8 );
778                                 $thestring .= str_repeat ( $char, $length );
779                         }
780                 }
781                 return $thestring;
782         }
783
784         /**
785          ** Returns either random text, or random wiki syntax, or random data from "ints",
786          **        or random data from "other".
787          */
788         static private function makestring() {
789                 $what = wikiFuzz::randnum( 2 );
790                 if ( $what == 0 ) {
791                         return wikiFuzz::randstring();
792                 }
793                 elseif ( $what == 1 ) {
794                         return wikiFuzz::$ints[wikiFuzz::randnum( count( wikiFuzz::$ints ) - 1 )];
795                 }
796                 else {
797                         return wikiFuzz::$other[wikiFuzz::randnum( count( wikiFuzz::$other ) - 1 )];
798                 }
799         }
800
801
802         /**
803          ** Strips out the stuff that Mediawiki balks at in a page's title.
804          **        Implementation copied/pasted from cleanupTable.inc & cleanupImages.php
805          */
806         static public function makeTitleSafe( $str ) {
807                 $legalTitleChars = " %!\"$&'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF";
808                 return preg_replace_callback(
809                                 "/([^$legalTitleChars])/",
810                                 create_function(
811                                         // single quotes are essential here,
812                                         // or alternative escape all $ as \$
813                                         '$matches',
814                                         'return sprintf( "\\x%02x", ord( $matches[1] ) );'
815                                         ),
816                                 $str );
817         }
818
819         /**
820          ** Returns a string of fuzz text.
821          */
822         static private function loop() {
823                 switch ( wikiFuzz::randnum( 3 ) ) {
824                         case 1: // an opening tag, with parameters.
825                                 $string = "";
826                                 $i = wikiFuzz::randnum( count( wikiFuzz::$types ) - 1 );
827                                 $t = wikiFuzz::$types[$i];
828                                 $arr = wikiFuzz::$data[$t];
829                                 $string .= "<" . $t . " ";
830                                 $num_params = min( wikiFuzz::$maxparams, count( $arr ) );
831                                 for ( $z = 0; $z < $num_params; $z++ ) {
832                                         $badparam = $arr[wikiFuzz::randnum( count( $arr ) - 1 )];
833                                         $badstring = wikiFuzz::makestring();
834                                         $string .= $badparam . "=" . wikiFuzz::getRandQuote() . $badstring . wikiFuzz::getRandQuote() . " ";
835                                 }
836                                 $string .= ">\n";
837                                 return $string;
838                         case 2: // a closing tag.
839                                 $i = wikiFuzz::randnum( count( wikiFuzz::$types ) - 1 );
840                                 return "</" . wikiFuzz::$types[$i] . ">";
841                         case 3: // a random string, between tags.
842                                 return wikiFuzz::makeString();
843                 }
844                 return "";    // catch-all, should never be called.
845         }
846
847         /**
848          ** Returns one of the three styles of random quote: ', ", and nothing.
849          */
850         static private function getRandQuote() {
851                 switch ( wikiFuzz::randnum( 3 ) ) {
852                         case 1 : return "'";
853                         case 2 : return "\"";
854                         default: return "";
855                 }
856         }
857
858         /**
859          ** Returns fuzz text, with the parameter indicating approximately how many lines of text you want.
860          */
861         static public function makeFuzz( $maxtypes = 2 ) {
862                 $page = "";
863                 for ( $k = 0; $k < $maxtypes; $k++ ) {
864                         $page .= wikiFuzz::loop();
865                 }
866                 return $page;
867         }
868 }
869
870
871 // //////   MEDIAWIKI PAGES TO TEST, AND HOW TO TEST THEM  ///////
872
873 /**
874  ** A page test has just these things:
875  **        1) Form parameters.
876  **        2) the URL we are going to test those parameters on.
877  **        3) Any cookies required for the test.
878  **        4) Whether Tidy should validate the page. Defaults to true, but can be turned off.
879  **        Declared abstract because it should be extended by a class
880  **        that supplies these parameters.
881  */
882 abstract class pageTest {
883         protected $params;
884         protected $pagePath;
885         protected $cookie = "";
886         protected $tidyValidate = true;
887
888         public function getParams() {
889                 return $this->params;
890         }
891
892         public function getPagePath() {
893                 return $this->pagePath;
894         }
895
896         public function getCookie() {
897                 return $this->cookie;
898         }
899
900         public function tidyValidate() {
901                 return $this->tidyValidate;
902         }
903 }
904
905
906 /**
907  ** a page test for the "Edit" page. Tests Parser.php and Sanitizer.php.
908  */
909 class editPageTest extends pageTest {
910         function __construct() {
911                 $this->pagePath = "index.php?title=WIKIFUZZ";
912
913                 $this->params = array (
914                                 "action"        => "submit",
915                                 "wpMinoredit"   => wikiFuzz::makeFuzz( 2 ),
916                                 "wpPreview"     => wikiFuzz::makeFuzz( 2 ),
917                                 "wpSection"     => wikiFuzz::makeFuzz( 2 ),
918                                 "wpEdittime"    => wikiFuzz::makeFuzz( 2 ),
919                                 "wpSummary"     => wikiFuzz::makeFuzz( 2 ),
920                                 "wpScrolltop"   => wikiFuzz::makeFuzz( 2 ),
921                                 "wpStarttime"   => wikiFuzz::makeFuzz( 2 ),
922                                 "wpAutoSummary" => wikiFuzz::makeFuzz( 2 ),
923                                 "wpTextbox1"    => wikiFuzz::makeFuzz( 40 )  // the main wiki text, need lots of this.
924                                 );
925
926                 // sometimes we don't want to specify certain parameters.
927                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpSection"] );
928                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpEdittime"] );
929                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpSummary"] );
930                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpScrolltop"] );
931                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpStarttime"] );
932                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpAutoSummary"] );
933                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpTextbox1"] );
934         }
935 }
936
937
938 /**
939  ** a page test for "Special:Listusers".
940  */
941 class listusersTest extends pageTest {
942         function __construct() {
943                 $this->pagePath = "index.php?title=Special:Listusers";
944
945                 $this->params = array (
946                                 "title"        => wikiFuzz::makeFuzz( 2 ),
947                                 "group"        => wikiFuzz::makeFuzz( 2 ),
948                                 "username"     => wikiFuzz::makeFuzz( 2 ),
949                                 "Go"           => wikiFuzz::makeFuzz( 2 ),
950                                 "limit"        => wikiFuzz::chooseInput( array( "0", "-1", "---'----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
951                                 "offset"       => wikiFuzz::chooseInput( array( "0", "-1", "--------'-----0", "+1", "81343242346234234", wikiFuzz::makeFuzz( 2 ) ) )
952                                 );
953         }
954 }
955
956
957 /**
958  ** a page test for "Special:Search".
959  */
960 class searchTest extends pageTest {
961         function __construct() {
962                 $this->pagePath = "index.php?title=Special:Search";
963
964                 $this->params = array (
965                                 "action"        => "index.php?title=Special:Search",
966                                 "ns0"           => wikiFuzz::makeFuzz( 2 ),
967                                 "ns1"           => wikiFuzz::makeFuzz( 2 ),
968                                 "ns2"           => wikiFuzz::makeFuzz( 2 ),
969                                 "ns3"           => wikiFuzz::makeFuzz( 2 ),
970                                 "ns4"           => wikiFuzz::makeFuzz( 2 ),
971                                 "ns5"           => wikiFuzz::makeFuzz( 2 ),
972                                 "ns6"           => wikiFuzz::makeFuzz( 2 ),
973                                 "ns7"           => wikiFuzz::makeFuzz( 2 ),
974                                 "ns8"           => wikiFuzz::makeFuzz( 2 ),
975                                 "ns9"           => wikiFuzz::makeFuzz( 2 ),
976                                 "ns10"          => wikiFuzz::makeFuzz( 2 ),
977                                 "ns11"          => wikiFuzz::makeFuzz( 2 ),
978                                 "ns12"          => wikiFuzz::makeFuzz( 2 ),
979                                 "ns13"          => wikiFuzz::makeFuzz( 2 ),
980                                 "ns14"          => wikiFuzz::makeFuzz( 2 ),
981                                 "ns15"          => wikiFuzz::makeFuzz( 2 ),
982                                 "redirs"        => wikiFuzz::makeFuzz( 2 ),
983                                 "search"        => wikiFuzz::makeFuzz( 2 ),
984                                 "offset"        => wikiFuzz::chooseInput( array( "", "0", "-1", "--------'-----0", "+1", "81343242346234234", wikiFuzz::makeFuzz( 2 ) ) ),
985                                 "fulltext"      => wikiFuzz::chooseInput( array( "", "0", "1", "--------'-----0", "+1", wikiFuzz::makeFuzz( 2 ) ) ),
986                                 "searchx"       => wikiFuzz::chooseInput( array( "", "0", "1", "--------'-----0", "+1", wikiFuzz::makeFuzz( 2 ) ) )
987                                         );
988         }
989 }
990
991
992 /**
993  ** a page test for "Special:Recentchanges".
994  */
995 class recentchangesTest extends pageTest {
996         function __construct() {
997                 $this->pagePath = "index.php?title=Special:Recentchanges";
998
999                 $this->params = array (
1000                                 "action"        => wikiFuzz::makeFuzz( 2 ),
1001                                 "title"         => wikiFuzz::makeFuzz( 2 ),
1002                                 "namespace"     => wikiFuzz::chooseInput( range( -1, 15 ) ),
1003                                 "Go"            => wikiFuzz::makeFuzz( 2 ),
1004                                 "invert"        => wikiFuzz::chooseInput( array( "-1", "---'----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
1005                                 "hideanons"     => wikiFuzz::chooseInput( array( "-1", "------'-------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
1006                                 'limit'         => wikiFuzz::chooseInput( array( "0", "-1", "---------'----0", "+1", "81340909772349234",  wikiFuzz::makeFuzz( 2 ) ) ),
1007                                 "days"          => wikiFuzz::chooseInput( array( "-1", "----------'---0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
1008                                 "hideminor"     => wikiFuzz::chooseInput( array( "-1", "-----------'--0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
1009                                 "hidebots"      => wikiFuzz::chooseInput( array( "-1", "---------'----0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
1010                                 "hideliu"       => wikiFuzz::chooseInput( array( "-1", "-------'------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
1011                                 "hidepatrolled" => wikiFuzz::chooseInput( array( "-1", "-----'--------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
1012                                 "hidemyself"    => wikiFuzz::chooseInput( array( "-1", "--'-----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
1013                                 'categories_any' => wikiFuzz::chooseInput( array( "-1", "--'-----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
1014                                 'categories'    => wikiFuzz::chooseInput( array( "-1", "--'-----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
1015                                 'feed'          => wikiFuzz::chooseInput( array( "-1", "--'-----------0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) )
1016                                 );
1017         }
1018 }
1019
1020
1021 /**
1022  ** a page test for "Special:Prefixindex".
1023  */
1024 class prefixindexTest extends pageTest {
1025         function __construct() {
1026                 $this->pagePath = "index.php?title=Special:Prefixindex";
1027
1028                 $this->params = array (
1029                                 "title"         => "Special:Prefixindex",
1030                                 "namespace"     => wikiFuzz::randnum( -10, 101 ),
1031                                 "Go"            => wikiFuzz::makeFuzz( 2 )
1032                                 );
1033
1034                 // sometimes we want 'prefix', sometimes we want 'from', and sometimes we want nothing.
1035                 if ( wikiFuzz::randnum( 3 ) == 0 ) {
1036                         $this->params["prefix"] = wikiFuzz::chooseInput( array( "-1", "-----'--------0", "+++--+1",
1037                                                                                                  wikiFuzz::randnum( -10, 8134 ), wikiFuzz::makeFuzz( 2 ) ) );
1038                 }
1039                 if ( wikiFuzz::randnum( 3 ) == 0 ) {
1040                         $this->params["from"]   = wikiFuzz::chooseInput( array( "-1", "-----'--------0", "+++--+1",
1041                                                                                                 wikiFuzz::randnum( -10, 8134 ), wikiFuzz::makeFuzz( 2 ) ) );
1042                 }
1043         }
1044 }
1045
1046
1047 /**
1048  ** a page test for "Special:MIMEsearch".
1049  */
1050 class mimeSearchTest extends pageTest {
1051         function __construct() {
1052                 $this->pagePath = "index.php?title=Special:MIMEsearch";
1053
1054                 $this->params = array (
1055                                 "action"        => "index.php?title=Special:MIMEsearch",
1056                                 "mime"          => wikiFuzz::makeFuzz( 3 ),
1057                                 'limit'         => wikiFuzz::chooseInput( array( "0", "-1", "-------'------0", "+1", "81342321351235325",  wikiFuzz::makeFuzz( 2 ) ) ),
1058                                 'offset'        => wikiFuzz::chooseInput( array( "0", "-1", "-----'--------0", "+1", "81341231235365252234324",  wikiFuzz::makeFuzz( 2 ) ) )
1059                                 );
1060         }
1061 }
1062
1063
1064 /**
1065  ** a page test for "Special:Log".
1066  */
1067 class specialLogTest extends pageTest {
1068         function __construct() {
1069                 $this->pagePath = "index.php?title=Special:Log";
1070
1071                 $this->params = array (
1072                                 "type"        => wikiFuzz::chooseInput( array( "", wikiFuzz::makeFuzz( 2 ) ) ),
1073                                 "par"         => wikiFuzz::makeFuzz( 2 ),
1074                                 "user"        => wikiFuzz::makeFuzz( 2 ),
1075                                 "page"        => wikiFuzz::makeFuzz( 2 ),
1076                                 "from"        => wikiFuzz::makeFuzz( 2 ),
1077                                 "until"       => wikiFuzz::makeFuzz( 2 ),
1078                                 "title"       => wikiFuzz::makeFuzz( 2 )
1079                                 );
1080         }
1081 }
1082
1083
1084 /**
1085  ** a page test for "Special:Userlogin", with a successful login.
1086  */
1087 class successfulUserLoginTest extends pageTest {
1088         function __construct() {
1089                 $this->pagePath = "index.php?title=Special:Userlogin&action=submitlogin&type=login&returnto=" . wikiFuzz::makeFuzz( 2 );
1090
1091                 $this->params = array (
1092                                 "wpName"          => USER_ON_WIKI,
1093                                 // sometimes real password, sometimes not:
1094                                 'wpPassword'      => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ), USER_PASSWORD ) ),
1095                                 'wpRemember'      => wikiFuzz::makeFuzz( 2 )
1096                                 );
1097
1098                 $this->cookie = "wikidb_session=" .  wikiFuzz::chooseInput( array( "1" , wikiFuzz::makeFuzz( 2 ) ) );
1099         }
1100 }
1101
1102
1103 /**
1104  ** a page test for "Special:Userlogin".
1105  */
1106 class userLoginTest extends pageTest {
1107         function __construct() {
1108
1109                 $this->pagePath = "index.php?title=Special:Userlogin";
1110
1111                 $this->params = array (
1112                                 'wpRetype'        => wikiFuzz::makeFuzz( 2 ),
1113                                 'wpRemember'      => wikiFuzz::makeFuzz( 2 ),
1114                                 'wpRealName'      => wikiFuzz::makeFuzz( 2 ),
1115                                 'wpPassword'      => wikiFuzz::makeFuzz( 2 ),
1116                                 'wpName'          => wikiFuzz::makeFuzz( 2 ),
1117                                 'wpMailmypassword' => wikiFuzz::makeFuzz( 2 ),
1118                                 'wpLoginattempt'  => wikiFuzz::makeFuzz( 2 ),
1119                                 'wpEmail'         => wikiFuzz::makeFuzz( 2 ),
1120                                 'wpDomain'        => wikiFuzz::chooseInput( array( "", "local", wikiFuzz::makeFuzz( 2 ) ) ),
1121                                 'wpCreateaccountMail' => wikiFuzz::chooseInput( array( "", wikiFuzz::makeFuzz( 2 ) ) ),
1122                                 'wpCreateaccount' => wikiFuzz::chooseInput( array( "", wikiFuzz::makeFuzz( 2 ) ) ),
1123                                 'wpCookieCheck'   => wikiFuzz::chooseInput( array( "", wikiFuzz::makeFuzz( 2 ) ) ),
1124                                 'type'            => wikiFuzz::chooseInput( array( "signup", "login", "", wikiFuzz::makeFuzz( 2 ) ) ),
1125                                 'returnto'        => wikiFuzz::makeFuzz( 2 ),
1126                                 'action'          => wikiFuzz::chooseInput( array( "", "submitlogin", wikiFuzz::makeFuzz( 2 ) ) )
1127                                 );
1128
1129                 $this->cookie = "wikidb_session=" . wikiFuzz::chooseInput( array( "1" , wikiFuzz::makeFuzz( 2 ) ) );
1130         }
1131 }
1132
1133
1134 /**
1135  ** a page test for "Special:Ipblocklist" (also includes unblocking)
1136  */
1137 class ipblocklistTest extends pageTest {
1138         function __construct() {
1139                 $this->pagePath = "index.php?title=Special:Ipblocklist";
1140
1141                 $this->params = array (
1142                                 'wpUnblockAddress' => wikiFuzz::makeFuzz( 2 ),
1143                                 'ip'              => wikiFuzz::chooseInput( array( "20398702394", "", "Nickj2", wikiFuzz::makeFuzz( 2 ),
1144                                                                          // something like an IP address, sometimes invalid:
1145                                                                          ( wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) . "."
1146                                                                            . wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) ) ) ),
1147                                 'id'              => wikiFuzz::makeFuzz( 2 ),
1148                                 'wpUnblockReason' => wikiFuzz::makeFuzz( 2 ),
1149                                 'action'          => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ), "success", "submit", "unblock" ) ),
1150                                 'wpEditToken'     => wikiFuzz::makeFuzz( 2 ),
1151                                 'wpBlock'         => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ), "" ) ),
1152                                 'limit'           => wikiFuzz::chooseInput( array( "0", "-1", "--------'-----0", "+1",
1153                                                                                                  "09700982312351132098234",  wikiFuzz::makeFuzz( 2 ) ) ),
1154                                 'offset'          => wikiFuzz::chooseInput( array( "0", "-1", "------'-------0", "+1",
1155                                                                                                  "09700980982341535324234234", wikiFuzz::makeFuzz( 2 ) ) )
1156                                 );
1157
1158                 // sometimes we don't want to specify certain parameters.
1159                 if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["action"] );
1160                 if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["ip"] );
1161                 if ( wikiFuzz::randnum( 2 ) == 0 ) unset( $this->params["id"] );
1162                 if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["wpUnblockAddress"] );
1163         }
1164 }
1165
1166
1167 /**
1168  ** a page test for "Special:Newimages".
1169  */
1170 class newImagesTest extends  pageTest {
1171         function __construct() {
1172                 $this->pagePath = "index.php?title=Special:Newimages";
1173
1174                 $this->params = array (
1175                                 'hidebots'  => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ), "1", "", "-1" ) ),
1176                                 'wpIlMatch' => wikiFuzz::makeFuzz( 2 ),
1177                                 'until'     => wikiFuzz::makeFuzz( 2 ),
1178                                 'from'      => wikiFuzz::makeFuzz( 2 )
1179                                 );
1180
1181                 // sometimes we don't want to specify certain parameters.
1182                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["until"] );
1183                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["from"] );
1184         }
1185 }
1186
1187
1188 /**
1189  ** a page test for the "Special:Imagelist" page.
1190  */
1191 class imagelistTest extends pageTest {
1192         function __construct() {
1193                 $this->pagePath = "index.php?title=Special:Imagelist";
1194
1195                 $this->params = array (
1196                                 'sort'      => wikiFuzz::chooseInput( array( "bysize", "byname" , "bydate", wikiFuzz::makeFuzz( 2 ) ) ),
1197                                 'limit'     => wikiFuzz::chooseInput( array( "0", "-1", "--------'-----0", "+1", "09700982312351132098234",  wikiFuzz::makeFuzz( 2 ) ) ),
1198                                 'offset'    => wikiFuzz::chooseInput( array( "0", "-1", "------'-------0", "+1", "09700980982341535324234234", wikiFuzz::makeFuzz( 2 ) ) ),
1199                                 'wpIlMatch' => wikiFuzz::makeFuzz( 2 )
1200                                 );
1201         }
1202 }
1203
1204
1205 /**
1206  ** a page test for "Special:Export".
1207  */
1208 class specialExportTest extends pageTest {
1209         function __construct() {
1210                 $this->pagePath = "index.php?title=Special:Export";
1211
1212                 $this->params = array (
1213                                 'action'      => wikiFuzz::chooseInput( array( "submit", "", wikiFuzz::makeFuzz( 2 ) ) ),
1214                                 'pages'       => wikiFuzz::makeFuzz( 2 ),
1215                                 'curonly'     => wikiFuzz::chooseInput( array( "", "0", "-1", wikiFuzz::makeFuzz( 2 ) ) ),
1216                                 'listauthors' => wikiFuzz::chooseInput( array( "", "0", "-1", wikiFuzz::makeFuzz( 2 ) ) ),
1217                                 'history'     => wikiFuzz::chooseInput( array( "0", "-1", "------'-------0", "+1", "09700980982341535324234234", wikiFuzz::makeFuzz( 2 ) ) ),
1218
1219                                 );
1220
1221                 // For the time being, need to disable "submit" action as Tidy barfs on MediaWiki's XML export.
1222                 if ( $this->params['action'] == 'submit' ) $this->params['action'] = '';
1223
1224                 // Sometimes remove the history field.
1225                 if ( wikiFuzz::randnum( 2 ) == 0 ) unset( $this->params["history"] );
1226
1227                 // page does not produce HTML.
1228                 $this->tidyValidate = false;
1229         }
1230 }
1231
1232
1233 /**
1234  ** a page test for "Special:Booksources".
1235  */
1236 class specialBooksourcesTest extends pageTest {
1237         function __construct() {
1238                 $this->pagePath = "index.php?title=Special:Booksources";
1239
1240                 $this->params = array (
1241                                 'go'    => wikiFuzz::makeFuzz( 2 ),
1242                                 // ISBN codes have to contain some semi-numeric stuff or will be ignored:
1243                                 'isbn'  => "0X0" . wikiFuzz::makeFuzz( 2 )
1244                                 );
1245         }
1246 }
1247
1248
1249 /**
1250  ** a page test for "Special:Allpages".
1251  */
1252 class specialAllpagesTest extends pageTest {
1253         function __construct() {
1254                 $this->pagePath = "index.php?title=Special%3AAllpages";
1255
1256                 $this->params = array (
1257                                 'from'      => wikiFuzz::makeFuzz( 2 ),
1258                                 'namespace' => wikiFuzz::chooseInput( range( -1, 15 ) ),
1259                                 'go'        => wikiFuzz::makeFuzz( 2 )
1260                                 );
1261         }
1262 }
1263
1264
1265 /**
1266  ** a page test for the page History.
1267  */
1268 class pageHistoryTest extends pageTest {
1269         function __construct() {
1270                 $this->pagePath = "index.php?title=Main_Page&action=history";
1271
1272                 $this->params = array (
1273                                 'limit'     => wikiFuzz::chooseInput( array( "-1", "0", "-------'------0", "+1", "8134",  wikiFuzz::makeFuzz( 2 ) ) ),
1274                                 'offset'    => wikiFuzz::chooseInput( array( "-1", "0", "------'-------0", "+1", "9823412312312412435", wikiFuzz::makeFuzz( 2 ) ) ),
1275                                 "go"        => wikiFuzz::chooseInput( array( "first", "last", wikiFuzz::makeFuzz( 2 ) ) ),
1276                                 "dir"       => wikiFuzz::chooseInput( array( "prev", "next", wikiFuzz::makeFuzz( 2 ) ) ),
1277                                 "diff"      => wikiFuzz::chooseInput( array( "-1", "--------'-----0", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
1278                                 "oldid"     => wikiFuzz::chooseInput( array( "prev", "-1", "+1", "8134", wikiFuzz::makeFuzz( 2 ) ) ),
1279                                 "feed"      => wikiFuzz::makeFuzz( 2 )
1280                                 );
1281         }
1282 }
1283
1284
1285 /**
1286  ** a page test for the Special:Contributions".
1287  */
1288 class contributionsTest extends pageTest {
1289         function __construct() {
1290                 $this->pagePath = "index.php?title=Special:Contributions/" . USER_ON_WIKI;
1291
1292                 $this->params = array (
1293                                 'target'    => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ), "newbies", USER_ON_WIKI ) ),
1294                                 'namespace' => wikiFuzz::chooseInput( array( -1, 15, 1, wikiFuzz::makeFuzz( 2 ) ) ),
1295                                 'offset'    => wikiFuzz::chooseInput( array( "0", "-1", "------'-------0", "+1", "982342131232131231241", wikiFuzz::makeFuzz( 2 ) ) ),
1296                                 'bot'       => wikiFuzz::chooseInput( array( "", "-1", "0", "1", wikiFuzz::makeFuzz( 2 ) ) ),
1297                                 'go'        => wikiFuzz::chooseInput( array( "-1", 'prev', 'next', wikiFuzz::makeFuzz( 2 ) ) )
1298                                 );
1299         }
1300 }
1301
1302
1303 /**
1304  ** a page test for viewing a normal page, whilst posting various params.
1305  */
1306 class viewPageTest extends pageTest {
1307         function __construct() {
1308                 $this->pagePath = "index.php?title=Main_Page";
1309
1310                 $this->params = array (
1311                                 "useskin"        => wikiFuzz::chooseInput( array( "chick", "cologneblue", "myskin",
1312                                                                                 "nostalgia", "simple", "standard", wikiFuzz::makeFuzz( 2 ) ) ),
1313                                 "uselang"        => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ),
1314                                                 "ab", "af", "an", "ar", "arc", "as", "ast", "av", "ay", "az", "ba",
1315                                                 "bat-smg", "be", "bg", "bm", "bn", "bo", "bpy", "br", "bs", "ca",
1316                                                 "ce", "cs", "csb", "cv", "cy", "da", "de", "dv", "dz", "el", "en",
1317                                                 "eo", "es", "et", "eu", "fa", "fi", "fo", "fr", "fur", "fy", "ga",
1318                                                 "gn", "gsw", "gu", "he", "hi", "hr", "hu", "ia", "id", "ii", "is",
1319                                                 "it", "ja", "jv", "ka", "km", "kn", "ko", "ks", "ku", "kv", "la",
1320                                                 "li", "lo", "lt", "lv", "mk", "ml", "ms", "nah", "nap", "nds",
1321                                                 "nds-nl", "nl", "nn", "no", "non", "nv", "oc", "or", "os", "pa",
1322                                                 "pl", "pms", "ps", "pt", "pt-br", "qu", "rmy", "ro", "ru", "sc",
1323                                                 "sd", "sk", "sl", "sq", "sr", "sr-ec", "sr-el",
1324                                                 "su", "sv", "ta", "te", "th", "tr", "tt", "ty", "tyv", "udm",
1325                                                 "ug", "uk", "ur", "utf8", "vec", "vi", "wa", "xal", "yi", "za",
1326                                                 "zh", "zh-cn", "zh-hk", "zh-sg", "zh-tw", "zh-tw" ) ),
1327                                 "returnto"       => wikiFuzz::makeFuzz( 2 ),
1328                                 "feed"           => wikiFuzz::chooseInput( array( "atom", "rss", wikiFuzz::makeFuzz( 2 ) ) ),
1329                                 "rcid"           => wikiFuzz::makeFuzz( 2 ),
1330                                 "action"         => wikiFuzz::chooseInput( array( "view", "raw", "render", wikiFuzz::makeFuzz( 2 ), "markpatrolled" ) ),
1331                                 "printable"      => wikiFuzz::makeFuzz( 2 ),
1332                                 "oldid"          => wikiFuzz::makeFuzz( 2 ),
1333                                 "redirect"       => wikiFuzz::makeFuzz( 2 ),
1334                                 "diff"           => wikiFuzz::makeFuzz( 2 ),
1335                                 "search"         => wikiFuzz::makeFuzz( 2 ),
1336                                 "rdfrom"         => wikiFuzz::makeFuzz( 2 ),  // things from Article.php from here on:
1337                                 "token"          => wikiFuzz::makeFuzz( 2 ),
1338                                 "tbid"           => wikiFuzz::makeFuzz( 2 ),
1339                                 "action"         => wikiFuzz::chooseInput( array( "purge", wikiFuzz::makeFuzz( 2 ) ) ),
1340                                 "wpReason"       => wikiFuzz::makeFuzz( 2 ),
1341                                 "wpEditToken"    => wikiFuzz::makeFuzz( 2 ),
1342                                 "from"           => wikiFuzz::makeFuzz( 2 ),
1343                                 "bot"            => wikiFuzz::makeFuzz( 2 ),
1344                                 "summary"        => wikiFuzz::makeFuzz( 2 ),
1345                                 "direction"      => wikiFuzz::chooseInput( array( "next", "prev", wikiFuzz::makeFuzz( 2 ) ) ),
1346                                 "section"        => wikiFuzz::makeFuzz( 2 ),
1347                                 "preload"        => wikiFuzz::makeFuzz( 2 ),
1348
1349                                 );
1350
1351                 // Tidy does not know how to valid atom or rss, so exclude from testing for the time being.
1352                 if ( $this->params["feed"] == "atom" )     { unset( $this->params["feed"] ); }
1353                 else if ( $this->params["feed"] == "rss" ) { unset( $this->params["feed"] ); }
1354
1355                 // Raw pages cannot really be validated
1356                 if ( $this->params["action"] == "raw" ) unset( $this->params["action"] );
1357
1358                 // sometimes we don't want to specify certain parameters.
1359                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["rcid"] );
1360                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["diff"] );
1361                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["rdfrom"] );
1362                 if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["oldid"] );
1363
1364                 // usually don't want action == purge.
1365                 if ( wikiFuzz::randnum( 6 ) > 1 ) unset( $this->params["action"] );
1366         }
1367 }
1368
1369
1370 /**
1371  ** a page test for "Special:Allmessages".
1372  */
1373 class specialAllmessagesTest extends pageTest {
1374         function __construct() {
1375                 $this->pagePath = "index.php?title=Special:Allmessages";
1376
1377                 // only really has one parameter
1378                 $this->params = array (
1379                                 "ot"     => wikiFuzz::chooseInput( array( "php", "html", wikiFuzz::makeFuzz( 2 ) ) )
1380                                 );
1381         }
1382 }
1383
1384 /**
1385  ** a page test for "Special:Newpages".
1386  */
1387 class specialNewpages extends pageTest {
1388         function __construct() {
1389                 $this->pagePath = "index.php?title=Special:Newpages";
1390
1391                 $this->params = array (
1392                                 "namespace" => wikiFuzz::chooseInput( range( -1, 15 ) ),
1393                                 "feed"      => wikiFuzz::chooseInput( array( "atom", "rss", wikiFuzz::makeFuzz( 2 ) ) ),
1394                                 'limit'     => wikiFuzz::chooseInput( array( "-1", "0", "-------'------0", "+1", "8134",  wikiFuzz::makeFuzz( 2 ) ) ),
1395                                 'offset'    => wikiFuzz::chooseInput( array( "-1", "0", "------'-------0", "+1", "9823412312312412435", wikiFuzz::makeFuzz( 2 ) ) )
1396                                 );
1397
1398                 // Tidy does not know how to valid atom or rss, so exclude from testing for the time being.
1399                 if ( $this->params["feed"] == "atom" )     { unset( $this->params["feed"] ); }
1400                 else if ( $this->params["feed"] == "rss" ) { unset( $this->params["feed"] ); }
1401         }
1402 }
1403
1404 /**
1405  ** a page test for "redirect.php"
1406  */
1407 class redirectTest extends pageTest {
1408         function __construct() {
1409                 $this->pagePath = "redirect.php";
1410
1411                 $this->params = array (
1412                                 "wpDropdown" => wikiFuzz::makeFuzz( 2 )
1413                                 );
1414
1415                 // sometimes we don't want to specify certain parameters.
1416                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpDropdown"] );
1417         }
1418 }
1419
1420
1421 /**
1422  ** a page test for "Special:Confirmemail"
1423  */
1424 class confirmEmail extends pageTest {
1425         function __construct() {
1426                 // sometimes we send a bogus confirmation code, and sometimes we don't.
1427                 $this->pagePath = "index.php?title=Special:Confirmemail" . wikiFuzz::chooseInput( array( "", "/" . wikiFuzz::makeTitleSafe( wikiFuzz::makeFuzz( 1 ) ) ) );
1428
1429                 $this->params = array (
1430                                 "token" => wikiFuzz::makeFuzz( 2 )
1431                                 );
1432         }
1433 }
1434
1435
1436 /**
1437  ** a page test for "Special:Watchlist"
1438  **        Note: this test would be better if we were logged in.
1439  */
1440 class watchlistTest extends pageTest {
1441         function __construct() {
1442                 $this->pagePath = "index.php?title=Special:Watchlist";
1443
1444                 $this->params = array (
1445                                 "remove"   => wikiFuzz::chooseInput( array( "Remove checked items from watchlist", wikiFuzz::makeFuzz( 2 ) ) ),
1446                                 'days'     => wikiFuzz::chooseInput( array( 0, -1, -230, "--", 3, 9, wikiFuzz::makeFuzz( 2 ) ) ),
1447                                 'hideOwn'  => wikiFuzz::chooseInput( array( "", "0", "1", wikiFuzz::makeFuzz( 2 ) ) ),
1448                                 'hideBots' => wikiFuzz::chooseInput( array( "", "0", "1", wikiFuzz::makeFuzz( 2 ) ) ),
1449                                 'namespace' => wikiFuzz::chooseInput( array( "", "0", "1", wikiFuzz::makeFuzz( 2 ) ) ),
1450                                 'action'   => wikiFuzz::chooseInput( array( "submit", "clear", wikiFuzz::makeFuzz( 2 ) ) ),
1451                                 'id[]'     => wikiFuzz::makeFuzz( 2 ),
1452                                 'edit'     => wikiFuzz::makeFuzz( 2 ),
1453                                 'token'    => wikiFuzz::chooseInput( array( "", "1243213", wikiFuzz::makeFuzz( 2 ) ) )
1454                                 );
1455
1456                 // sometimes we specifiy "reset", and sometimes we don't.
1457                 if ( wikiFuzz::randnum( 3 ) == 0 ) $this->params["reset"] = wikiFuzz::chooseInput( array( "", "0", "1", wikiFuzz::makeFuzz( 2 ) ) );
1458         }
1459 }
1460
1461
1462 /**
1463  ** a page test for "Special:Blockme"
1464  */
1465 class specialBlockmeTest extends pageTest {
1466         function __construct() {
1467                 $this->pagePath = "index.php?title=Special:Blockme";
1468
1469                 $this->params = array ( );
1470
1471                 // sometimes we specify "ip", and sometimes we don't.
1472                 if ( wikiFuzz::randnum( 1 ) == 0 ) {
1473                         $this->params["ip"] = wikiFuzz::chooseInput( array( "10.12.41.213", wikiFuzz::randnum( -10, 8134 ), wikiFuzz::makeFuzz( 2 ) ) );
1474                 }
1475         }
1476 }
1477
1478
1479 /**
1480  ** a page test for "Special:Movepage"
1481  */
1482 class specialMovePage extends pageTest {
1483         function __construct() {
1484                 $this->pagePath = "index.php?title=Special:Movepage";
1485
1486                 $this->params = array (
1487                                 "action"      => wikiFuzz::chooseInput( array( "success", "submit", "", wikiFuzz::makeFuzz( 2 ) ) ),
1488                                 'wpEditToken' => wikiFuzz::chooseInput( array( '', 0, 34987987, wikiFuzz::makeFuzz( 2 ) ) ),
1489                                 'target'      => wikiFuzz::chooseInput( array( "x", wikiFuzz::makeTitleSafe( wikiFuzz::makeFuzz( 2 ) ) ) ),
1490                                 'wpOldTitle'  => wikiFuzz::chooseInput( array( "z", wikiFuzz::makeTitleSafe( wikiFuzz::makeFuzz( 2 ) ), wikiFuzz::makeFuzz( 2 ) ) ),
1491                                 'wpNewTitle'  => wikiFuzz::chooseInput( array( "y", wikiFuzz::makeTitleSafe( wikiFuzz::makeFuzz( 2 ) ), wikiFuzz::makeFuzz( 2 ) ) ),
1492                                 'wpReason'    => wikiFuzz::chooseInput( array( wikiFuzz::makeFuzz( 2 ) ) ),
1493                                 'wpMovetalk'  => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
1494                                 'wpDeleteAndMove'  => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
1495                                 'wpConfirm'   => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
1496                                 'talkmoved'   => wikiFuzz::chooseInput( array( "1", wikiFuzz::makeFuzz( 2 ), "articleexists", 'notalkpage' ) ),
1497                                 'oldtitle'    => wikiFuzz::makeFuzz( 2 ),
1498                                 'newtitle'    => wikiFuzz::makeFuzz( 2 ),
1499                                 'wpMovetalk'  => wikiFuzz::chooseInput( array( "1", "0", wikiFuzz::makeFuzz( 2 ) ) )
1500                                 );
1501
1502                 // sometimes we don't want to specify certain parameters.
1503                 if ( wikiFuzz::randnum( 2 ) == 0 ) unset( $this->params["wpEditToken"] );
1504                 if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["target"] );
1505                 if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["wpNewTitle"] );
1506                 if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpReason"] );
1507                 if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpOldTitle"] );
1508         }
1509 }
1510
1511
1512 /**
1513  ** a page test for "Special:Undelete"
1514  */
1515 class specialUndelete extends pageTest {
1516         function __construct() {
1517                 $this->pagePath = "index.php?title=Special:Undelete";
1518
1519                 $this->params = array (
1520                                 "action"      => wikiFuzz::chooseInput( array( "submit", "", wikiFuzz::makeFuzz( 2 ) ) ),
1521                                 'wpEditToken' => wikiFuzz::chooseInput( array( '', 0, 34987987, wikiFuzz::makeFuzz( 2 ) ) ),
1522                                 'target'      => wikiFuzz::chooseInput( array( "x", wikiFuzz::makeTitleSafe( wikiFuzz::makeFuzz( 2 ) ) ) ),
1523                                 'timestamp'   => wikiFuzz::chooseInput( array( "125223", wikiFuzz::makeFuzz( 2 ) ) ),
1524                                 'file'        => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
1525                                 'restore'     => wikiFuzz::chooseInput( array( "0", "1", wikiFuzz::makeFuzz( 2 ) ) ),
1526                                 'preview'     => wikiFuzz::chooseInput( array( "0", "1", wikiFuzz::makeFuzz( 2 ) ) ),
1527                                 'wpComment'   => wikiFuzz::makeFuzz( 2 )
1528                                 );
1529
1530                 // sometimes we don't want to specify certain parameters.
1531                 if ( wikiFuzz::randnum( 2 ) == 0 ) unset( $this->params["wpEditToken"] );
1532                 if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["target"] );
1533                 if ( wikiFuzz::randnum( 1 ) == 0 ) unset( $this->params["restore"] );
1534                 if ( wikiFuzz::randnum( 1 ) == 0 ) unset( $this->params["preview"] );
1535         }
1536 }
1537
1538
1539 /**
1540  ** a page test for "Special:Unlockdb"
1541  */
1542 class specialUnlockdb extends pageTest {
1543         function __construct() {
1544                 $this->pagePath = "index.php?title=Special:Unlockdb";
1545
1546                 $this->params = array (
1547                                 "action"        => wikiFuzz::chooseInput( array( "submit", "success", "", wikiFuzz::makeFuzz( 2 ) ) ),
1548                                 'wpEditToken'   => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
1549                                 'wpLockConfirm' => wikiFuzz::chooseInput( array( "0", "1", wikiFuzz::makeFuzz( 2 ) ) )
1550                                 );
1551
1552                 // sometimes we don't want to specify certain parameters.
1553                 if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpEditToken"] );
1554                 if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["action"] );
1555                 if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpLockConfirm"] );
1556         }
1557 }
1558
1559
1560 /**
1561  ** a page test for "Special:Lockdb"
1562  */
1563 class specialLockdb extends pageTest {
1564         function __construct() {
1565                 $this->pagePath = "index.php?title=Special:Lockdb";
1566
1567                 $this->params = array (
1568                                 "action"       => wikiFuzz::chooseInput( array( "submit", "success", "", wikiFuzz::makeFuzz( 2 ) ) ),
1569                                 'wpEditToken'  => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
1570                                 'wpLockReason' => wikiFuzz::makeFuzz( 2 ),
1571                                 'wpLockConfirm' => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) )
1572                                 );
1573
1574                 // sometimes we don't want to specify certain parameters.
1575                 if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpEditToken"] );
1576                 if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["action"] );
1577                 if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpLockConfirm"] );
1578         }
1579 }
1580
1581
1582 /**
1583  ** a page test for "Special:Userrights"
1584  */
1585 class specialUserrights extends pageTest {
1586         function __construct() {
1587                 $this->pagePath = "index.php?title=Special:Userrights";
1588
1589                 $this->params = array (
1590                                 'wpEditToken'   => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
1591                                 'user-editname' => wikiFuzz::chooseInput( array( "Nickj2", "Nickj2\n<xyz>", wikiFuzz::makeFuzz( 2 ) ) ),
1592                                 'ssearchuser'   => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
1593                                 'saveusergroups' => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ), "Save User Groups" ),
1594                                 'member[]'      => wikiFuzz::chooseInput( array( "0", "bot", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
1595                                 "available[]"   => wikiFuzz::chooseInput( array( "0", "sysop", "bureaucrat", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) )
1596                                 );
1597
1598                 // sometimes we don't want to specify certain parameters.
1599                 if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params['ssearchuser'] );
1600                 if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params['saveusergroups'] );
1601         }
1602 }
1603
1604
1605 /**
1606  ** a test for page protection and unprotection.
1607  */
1608 class pageProtectionForm extends pageTest {
1609         function __construct() {
1610                 $this->pagePath = "index.php?title=Main_Page";
1611
1612                 $this->params = array (
1613                                 "action"               => "protect",
1614                                 'wpEditToken'          => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
1615                                 "mwProtect-level-edit" => wikiFuzz::chooseInput( array( '', 'autoconfirmed', 'sysop', wikiFuzz::makeFuzz( 2 ) ) ),
1616                                 "mwProtect-level-move" => wikiFuzz::chooseInput( array( '', 'autoconfirmed', 'sysop', wikiFuzz::makeFuzz( 2 ) ) ),
1617                                 "mwProtectUnchained"   => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
1618                                 'mwProtect-reason'     => wikiFuzz::chooseInput( array( "because it was there", wikiFuzz::makeFuzz( 2 ) ) )
1619                                 );
1620
1621
1622                 // sometimes we don't want to specify certain parameters.
1623                 if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["mwProtectUnchained"] );
1624                 if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params['mwProtect-reason'] );
1625         }
1626 }
1627
1628
1629 /**
1630  ** a page test for "Special:Blockip".
1631  */
1632 class specialBlockip extends pageTest {
1633         function __construct() {
1634                 $this->pagePath = "index.php?title=Special:Blockip";
1635
1636                 $this->params = array (
1637                                 "action"          => wikiFuzz::chooseInput( array( "submit", "",  wikiFuzz::makeFuzz( 2 ) ) ),
1638                                 'wpEditToken'     => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
1639                                 "wpBlockAddress"  => wikiFuzz::chooseInput( array( "20398702394", "", "Nickj2", wikiFuzz::makeFuzz( 2 ),
1640                                                                           // something like an IP address, sometimes invalid:
1641                                                                          ( wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) . "."
1642                                                                           . wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) ) ) ),
1643                                 "ip"              => wikiFuzz::chooseInput( array( "20398702394", "", "Nickj2", wikiFuzz::makeFuzz( 2 ),
1644                                                                           // something like an IP address, sometimes invalid:
1645                                                                          ( wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) . "."
1646                                                                           . wikiFuzz::randnum( 300, -20 ) . "." . wikiFuzz::randnum( 300, -20 ) ) ) ),
1647                                 "wpBlockOther"    => wikiFuzz::chooseInput( array( '', 'Nickj2', wikiFuzz::makeFuzz( 2 ) ) ),
1648                                 "wpBlockExpiry"   => wikiFuzz::chooseInput( array( "other", "2 hours", "1 day", "3 days", "1 week", "2 weeks",
1649                                                                                   "1 month", "3 months", "6 months", "1 year", "infinite", wikiFuzz::makeFuzz( 2 ) ) ),
1650                                 "wpBlockReason"   => wikiFuzz::chooseInput( array( "because it was there", wikiFuzz::makeFuzz( 2 ) ) ),
1651                                 "wpAnonOnly"      => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
1652                                 "wpCreateAccount" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
1653                                 "wpBlock"         => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) )
1654                                 );
1655
1656                 // sometimes we don't want to specify certain parameters.
1657                 if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpBlockOther"] );
1658                 if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpBlockExpiry"] );
1659                 if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpBlockReason"] );
1660                 if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpAnonOnly"] );
1661                 if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpCreateAccount"] );
1662                 if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["wpBlockAddress"] );
1663                 if ( wikiFuzz::randnum( 4 ) == 0 ) unset( $this->params["ip"] );
1664         }
1665 }
1666
1667
1668 /**
1669  ** a test for the imagepage.
1670  */
1671 class imagepageTest extends pageTest {
1672         function __construct() {
1673                 $this->pagePath = "index.php?title=Image:Small-email.png";
1674
1675                 $this->params = array (
1676                                 "image"         => wikiFuzz::chooseInput( array( "Small-email.png", wikiFuzz::makeFuzz( 2 ) ) ),
1677                                 "wpReason"      => wikiFuzz::makeFuzz( 2 ),
1678                                 "oldimage"      => wikiFuzz::chooseInput( array( "Small-email.png", wikiFuzz::makeFuzz( 2 ) ) ),
1679                                 "wpEditToken"   => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
1680                                 );
1681
1682                 // sometimes we don't want to specify certain parameters.
1683                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["image"] );
1684                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpReason"] );
1685                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["oldimage"] );
1686                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpEditToken"] );
1687         }
1688 }
1689
1690
1691 /**
1692  ** a test for page deletion form.
1693  */
1694 class pageDeletion extends pageTest {
1695         function __construct() {
1696                 $this->pagePath = "index.php?title=Main_Page&action=delete";
1697
1698                 $this->params = array (
1699                                 "wpEditToken" => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
1700                                 "wpReason"    => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
1701                                 "wpConfirm"   => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
1702                                 );
1703
1704                 // sometimes we don't want to specify certain parameters.
1705                 if ( wikiFuzz::randnum( 5 ) == 0 ) unset( $this->params["wpReason"] );
1706                 if ( wikiFuzz::randnum( 5 ) == 0 ) unset( $this->params["wpEditToken"] );
1707                 if ( wikiFuzz::randnum( 5 ) == 0 ) unset( $this->params["wpConfirm"] );
1708         }
1709 }
1710
1711
1712
1713 /**
1714  ** a test for Revision Deletion.
1715  */
1716 class specialRevisionDelete extends pageTest {
1717         function __construct() {
1718                 $this->pagePath = "index.php?title=Special:Revisiondelete";
1719
1720                 $this->params = array (
1721                                 "target"               => wikiFuzz::chooseInput( array( "Main Page", wikiFuzz::makeFuzz( 2 ) ) ),
1722                                 "oldid"                => wikiFuzz::makeFuzz( 2 ),
1723                                 "oldid[]"              => wikiFuzz::makeFuzz( 2 ),
1724                                 "wpReason"             => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
1725                                 "revdelete-hide-text"  => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
1726                                 "revdelete-hide-comment" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
1727                                 "revdelete-hide-user"  => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
1728                                 "revdelete-hide-restricted" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
1729                                 );
1730
1731                 // sometimes we don't want to specify certain parameters.
1732                 if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["target"] );
1733                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["oldid"] );
1734                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["oldid[]"] );
1735                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["wpReason"] );
1736                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["revdelete-hide-text"] );
1737                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["revdelete-hide-comment"] );
1738                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["revdelete-hide-user"] );
1739                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["revdelete-hide-restricted"] );
1740         }
1741 }
1742
1743
1744 /**
1745  ** a test for Special:Import.
1746  */
1747 class specialImport extends pageTest {
1748         function __construct() {
1749                 $this->pagePath = "index.php?title=Special:Import";
1750
1751                 $this->params = array (
1752                                 "action"         => "submit",
1753                                 "source"         => wikiFuzz::chooseInput( array( "upload", "interwiki", wikiFuzz::makeFuzz( 2 ) ) ),
1754                                 "MAX_FILE_SIZE"  => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
1755                                 "xmlimport"      => wikiFuzz::chooseInput( array( "/var/www/hosts/mediawiki/wiki/AdminSettings.php", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
1756                                 "namespace"      => wikiFuzz::chooseInput( array( wikiFuzz::randnum( 30, -6 ), wikiFuzz::makeFuzz( 2 ) ) ),
1757                                 "interwiki"      => wikiFuzz::makeFuzz( 2 ),
1758                                 "interwikiHistory" => wikiFuzz::makeFuzz( 2 ),
1759                                 "frompage"       => wikiFuzz::makeFuzz( 2 ),
1760                                 );
1761
1762                 // sometimes we don't want to specify certain parameters.
1763                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["action"] );
1764                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["source"] );
1765                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["MAX_FILE_SIZE"] );
1766                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["xmlimport"] );
1767                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["interwiki"] );
1768                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["interwikiHistory"] );
1769                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["frompage"] );
1770
1771                 // Note: Need to do a file upload to fully test this Special page.
1772         }
1773 }
1774
1775
1776 /**
1777  ** a test for thumb.php
1778  */
1779 class thumbTest extends pageTest {
1780         function __construct() {
1781                 $this->pagePath = "thumb.php";
1782
1783                 $this->params = array (
1784                                 "f"  => wikiFuzz::chooseInput( array( "..", "\\", "small-email.png", wikiFuzz::makeFuzz( 2 ) ) ),
1785                                 "w"  => wikiFuzz::chooseInput( array( "80", wikiFuzz::randnum( 6000, -200 ), wikiFuzz::makeFuzz( 2 ) ) ),
1786                                 "r"  => wikiFuzz::chooseInput( array( "0", wikiFuzz::makeFuzz( 2 ) ) ),
1787                                 );
1788
1789                 // sometimes we don't want to specify certain parameters.
1790                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["f"] );
1791                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["w"] );
1792                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["r"] );
1793         }
1794 }
1795
1796
1797 /**
1798  ** a test for trackback.php
1799  */
1800 class trackbackTest extends pageTest {
1801         function __construct() {
1802                 $this->pagePath = "trackback.php";
1803
1804                 $this->params = array (
1805                                 "url"       => wikiFuzz::makeFuzz( 2 ),
1806                                 "blog_name" => wikiFuzz::chooseInput( array( "80", wikiFuzz::randnum( 6000, -200 ), wikiFuzz::makeFuzz( 2 ) ) ),
1807                                 "article"   => wikiFuzz::chooseInput( array( "Main Page", wikiFuzz::makeFuzz( 2 ) ) ),
1808                                 "title"     => wikiFuzz::chooseInput( array( "Main Page", wikiFuzz::makeFuzz( 2 ) ) ),
1809                                 "excerpt"   => wikiFuzz::makeFuzz( 2 ),
1810                                 );
1811
1812                 // sometimes we don't want to specify certain parameters.
1813                 if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["title"] );
1814                 if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["excerpt"] );
1815
1816                 // page does not produce HTML.
1817                 $this->tidyValidate = false;
1818         }
1819 }
1820
1821
1822 /**
1823  ** a test for profileinfo.php
1824  */
1825 class profileInfo extends pageTest {
1826         function __construct() {
1827                 $this->pagePath = "profileinfo.php";
1828
1829                 $this->params = array (
1830                                 "expand"  => wikiFuzz::makeFuzz( 2 ),
1831                                 "sort"    => wikiFuzz::chooseInput( array( "time", "count", "name", wikiFuzz::makeFuzz( 2 ) ) ),
1832                                 "filter"  => wikiFuzz::chooseInput( array( "Main Page", wikiFuzz::makeFuzz( 2 ) ) ),
1833                                 );
1834
1835                 // sometimes we don't want to specify certain parameters.
1836                 if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["sort"] );
1837                 if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["filter"] );
1838         }
1839 }
1840
1841
1842 /**
1843  ** a test for Special:Cite (extension Special page).
1844  */
1845 class specialCite extends pageTest {
1846         function __construct() {
1847                 $this->pagePath = "index.php?title=Special:Cite";
1848
1849                 $this->params = array (
1850                                 "page"    => wikiFuzz::chooseInput( array( "\" onmouseover=\"alert(1);\"", "Main Page", wikiFuzz::makeFuzz( 2 ) ) ),
1851                                 "id"      => wikiFuzz::chooseInput( array( "-1", "0", "------'-------0", "+1", "-9823412312312412435", wikiFuzz::makeFuzz( 2 ) ) ),
1852                                 );
1853
1854                 // sometimes we don't want to specify certain parameters.
1855                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["page"] );
1856                 if ( wikiFuzz::randnum( 6 ) == 0 ) unset( $this->params["id"] );
1857         }
1858 }
1859
1860
1861 /**
1862  ** a test for Special:Filepath (extension Special page).
1863  */
1864 class specialFilepath extends pageTest {
1865         function __construct() {
1866                 $this->pagePath = "index.php?title=Special:Filepath";
1867
1868                 $this->params = array (
1869                                 "file"    => wikiFuzz::chooseInput( array( "Small-email.png", "Small-email.png" . wikiFuzz::makeFuzz( 1 ), wikiFuzz::makeFuzz( 2 ) ) ),
1870                                 );
1871         }
1872 }
1873
1874
1875 /**
1876  ** a test for Special:Makebot (extension Special page).
1877  */
1878 class specialMakebot extends pageTest {
1879         function __construct() {
1880                 $this->pagePath = "index.php?title=Special:Makebot";
1881
1882                 $this->params = array (
1883                                 "username" => wikiFuzz::chooseInput( array( "Nickj2", "192.168.0.2", wikiFuzz::makeFuzz( 1 ) ) ),
1884                                 "dosearch" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
1885                                 "grant"    => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
1886                                 "comment"  => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
1887                                 "token"    => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
1888                                 );
1889
1890                 // sometimes we don't want to specify certain parameters.
1891                 if ( wikiFuzz::randnum( 2 ) == 0 ) unset( $this->params["dosearch"] );
1892                 if ( wikiFuzz::randnum( 2 ) == 0 ) unset( $this->params["grant"] );
1893                 if ( wikiFuzz::randnum( 5 ) == 0 ) unset( $this->params["token"] );
1894         }
1895 }
1896
1897
1898 /**
1899  ** a test for Special:Makesysop (extension Special page).
1900  */
1901 class specialMakesysop extends pageTest {
1902         function __construct() {
1903                 $this->pagePath = "index.php?title=Special:Makesysop";
1904
1905                 $this->params = array (
1906                                 "wpMakesysopUser"   => wikiFuzz::chooseInput( array( "Nickj2", "192.168.0.2", wikiFuzz::makeFuzz( 1 ) ) ),
1907                                 "action"            => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
1908                                 "wpMakesysopSubmit" => wikiFuzz::chooseInput( array( "0", "1", "++--34234", wikiFuzz::makeFuzz( 2 ) ) ),
1909                                 "wpEditToken"       => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
1910                                 "wpSetBureaucrat"   => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
1911                                 );
1912
1913                 // sometimes we don't want to specify certain parameters.
1914                 if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["wpMakesysopSubmit"] );
1915                 if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["wpEditToken"] );
1916                 if ( wikiFuzz::randnum( 3 ) == 0 ) unset( $this->params["wpSetBureaucrat"] );
1917         }
1918 }
1919
1920
1921 /**
1922  ** a test for Special:Renameuser (extension Special page).
1923  */
1924 class specialRenameuser extends pageTest {
1925         function __construct() {
1926                 $this->pagePath = "index.php?title=Special:Renameuser";
1927
1928                 $this->params = array (
1929                                 "oldusername"   => wikiFuzz::chooseInput( array( "Nickj2", "192.168.0.2", wikiFuzz::makeFuzz( 1 ) ) ),
1930                                 "newusername"   => wikiFuzz::chooseInput( array( "Nickj2", "192.168.0.2", wikiFuzz::makeFuzz( 1 ) ) ),
1931                                 "token"         => wikiFuzz::chooseInput( array( "20398702394", "", wikiFuzz::makeFuzz( 2 ) ) ),
1932                                 );
1933         }
1934 }
1935
1936
1937 /**
1938  ** a test for Special:Linksearch (extension Special page).
1939  */
1940 class specialLinksearch extends pageTest {
1941         function __construct() {
1942                 $this->pagePath = "index.php?title=Special%3ALinksearch";
1943
1944                 $this->params = array (
1945                                 "target" => wikiFuzz::makeFuzz( 2 ),
1946                                 );
1947
1948                 // sometimes we don't want to specify certain parameters.
1949                 if ( wikiFuzz::randnum( 10 ) == 0 ) unset( $this->params["target"] );
1950         }
1951 }
1952
1953
1954 /**
1955  ** a test for Special:CategoryTree (extension Special page).
1956  */
1957 class specialCategoryTree extends pageTest {
1958         function __construct() {
1959                 $this->pagePath = "index.php?title=Special:CategoryTree";
1960
1961                 $this->params = array (
1962                                 "target" => wikiFuzz::makeFuzz( 2 ),
1963                                 "from"   => wikiFuzz::makeFuzz( 2 ),
1964                                 "until"  => wikiFuzz::makeFuzz( 2 ),
1965                                 "showas" => wikiFuzz::makeFuzz( 2 ),
1966                                 "mode"   => wikiFuzz::chooseInput( array( "pages", "categories", "all", wikiFuzz::makeFuzz( 2 ) ) ),
1967                                 );
1968
1969                 // sometimes we do want to specify certain parameters.
1970                 if ( wikiFuzz::randnum( 5 ) == 0 ) $this->params["notree"] = wikiFuzz::chooseInput( array( "1", 0, "", wikiFuzz::makeFuzz( 2 ) ) );
1971         }
1972 }
1973
1974
1975 /**
1976  ** a test for "Special:Chemicalsources" (extension Special page).
1977  */
1978 class specialChemicalsourcesTest extends pageTest {
1979         function __construct() {
1980                 $this->pagePath = "index.php?title=Special:Chemicalsources";
1981
1982                 // choose an input format to use.
1983                 $format =  wikiFuzz::chooseInput(
1984                                         array(  'go',
1985                                                         'CAS',
1986                                                         'EINECS',
1987                                                         'CHEBI',
1988                                                         'PubChem',
1989                                                         'SMILES',
1990                                                         'InChI',
1991                                                         'ATCCode',
1992                                                         'KEGG',
1993                                                         'RTECS',
1994                                                         'ECNumber',
1995                                                         'DrugBank',
1996                                                         'Formula',
1997                                                         'Name'
1998                                                  )
1999                                         );
2000
2001                 // values for different formats usually start with either letters or numbers.
2002                 switch ( $format ) {
2003                         case 'Name'   : $value = "A"; break;
2004                         case 'InChI'  :
2005                         case 'SMILES' :
2006                         case 'Formula': $value = "C"; break;
2007                         default       : $value = "0"; break;
2008                 }
2009
2010                 // and then we append the fuzz input.
2011                 $this->params = array ( $format => $value . wikiFuzz::makeFuzz( 2 ) );
2012         }
2013 }
2014
2015
2016 /**
2017  ** A test for api.php (programmatic interface to MediaWiki in XML/JSON/RSS/etc formats).
2018  ** Quite involved to test because there are lots of options/parameters, and because
2019  ** for a lot of the functionality if all the parameters don't make sense then it just
2020  ** returns the help screen - so currently a lot of the tests aren't actually doing much
2021  ** because something wasn't right in the query.
2022  **
2023  ** @todo: Incomplete / unfinished; Runs too fast (suggests not much testing going on).
2024  */
2025 class api extends pageTest {
2026
2027         // API login mode.
2028         private static function loginMode() {
2029                 $arr =  array ( "lgname"        => wikiFuzz::makeFuzz( 2 ),
2030                                                 "lgpassword"    => wikiFuzz::makeFuzz( 2 ),
2031                                            );
2032                 // sometimes we want to specify the extra "lgdomain" parameter.
2033                 if ( wikiFuzz::randnum( 3 ) == 0 ) {
2034                         $arr["lgdomain"] = wikiFuzz::chooseInput( array( "1", 0, "", wikiFuzz::makeFuzz( 2 ) ) );
2035                 }
2036
2037                 return $arr;
2038         }
2039
2040         // API OpenSearch mode.
2041         private static function opensearchMode() {
2042                 return array ( "search"        => wikiFuzz::makeFuzz( 2 ) );
2043         }
2044
2045         // API watchlist feed mode.
2046         private static function feedwatchlistMode() {
2047                 // FIXME: add "wikiFuzz::makeFuzz(2)" as possible value below?
2048                 return array ( "feedformat"    => wikiFuzz::chooseInput( array( "rss", "atom" ) ) );
2049         }
2050
2051         // API query mode.
2052         private static function queryMode() {
2053                 // FIXME: add "wikiFuzz::makeFuzz(2)" as possible params for the elements below?
2054                 //        Suspect this will stuff up the tests more, but need to check.
2055                 $params = array (
2056                                          // FIXME: More titles.
2057                                          "titles"        => wikiFuzz::chooseInput( array( "Main Page" ) ),
2058                                          // FIXME: More pageids.
2059                                          "pageids"       => 1,
2060                                          "prop"          => wikiFuzz::chooseInput( array( "info", "revisions", "watchlist" ) ),
2061                                          "list"          => wikiFuzz::chooseInput( array( "allpages", "logevents", "watchlist", "usercontribs", "recentchanges", "backlinks", "embeddedin", "imagelinks" ) ),
2062                                          "meta"          => wikiFuzz::chooseInput( array( "siteinfo" ) ),
2063                                          "generator"     => wikiFuzz::chooseInput( array( "allpages", "logevents", "watchlist", "info", "revisions" ) ),
2064                                          "siprop"        => wikiFuzz::chooseInput( array( "general", "namespaces", "general|namespaces" ) ),
2065                                    );
2066
2067                  // Add extra parameters based on what list choice we got.
2068                  switch ( $params["list"] ) {
2069                         case "usercontribs" : self::addListParams ( $params, "uc", array( "limit", "start", "end", "user", "dir" ) ); break;
2070                         case "allpages"     : self::addListParams ( $params, "ap", array( "from", "prefix", "namespace", "filterredir", "limit" ) ); break;
2071                         case "watchlist"    : self::addListParams ( $params, "wl", array( "allrev", "start", "end", "namespace", "dir", "limit", "prop" ) ); break;
2072                         case "logevents"    : self::addListParams ( $params, "le", array( "limit", "type", "start", "end", "user", "dir" ) ); break;
2073                         case "recentchanges": self::addListParams ( $params, "rc", array( "limit", "prop", "show", "namespace", "start", "end", "dir" ) ); break;
2074                         case "backlinks"    : self::addListParams ( $params, "bl", array( "continue", "namespace", "redirect", "limit" ) ); break;
2075                         case "embeddedin"   : self::addListParams ( $params, "ei", array( "continue", "namespace", "redirect", "limit" ) ); break;
2076                         case "imagelinks"   : self::addListParams ( $params, "il", array( "continue", "namespace", "redirect", "limit" ) ); break;
2077                  }
2078
2079                  if ( $params["prop"] == "revisions" ) {
2080                         self::addListParams ( $params, "rv", array( "prop", "limit", "startid", "endid", "end", "dir" ) );
2081                  }
2082
2083                  // Sometimes we want redirects, sometimes we don't.
2084                  if ( wikiFuzz::randnum( 3 ) == 0 ) {
2085                         $params["redirects"] = wikiFuzz::chooseInput( array( "1", 0, "", wikiFuzz::makeFuzz( 2 ) ) );
2086                  }
2087
2088                  return $params;
2089         }
2090
2091         // Adds all the elements to the array, using the specified prefix.
2092         private static function addListParams( &$array, $prefix, $elements )  {
2093                 foreach ( $elements as $element ) {
2094                         $array[$prefix . $element] = self::getParamDetails( $element );
2095                 }
2096         }
2097
2098         // For a given element name, returns the data for that element.
2099         private static function getParamDetails( $element ) {
2100                 switch ( $element ) {
2101                         case 'startid'    :
2102                         case 'endid'      :
2103                         case 'start'      :
2104                         case 'end'        :
2105                         case 'limit'      : return wikiFuzz::chooseInput( array( "0", "-1", "---'----------0", "+1", "8134", "320742734234235", "20060230121212", wikiFuzz::randnum( 9000, -100 ), wikiFuzz::makeFuzz( 2 ) ) );
2106                         case 'dir'        : return wikiFuzz::chooseInput( array( "newer", "older", wikiFuzz::makeFuzz( 2 ) ) );
2107                         case 'user'       : return wikiFuzz::chooseInput( array( USER_ON_WIKI, wikiFuzz::makeFuzz( 2 ) ) );
2108                         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 ) ) );
2109                         case 'filterredir': return wikiFuzz::chooseInput( array( "all", "redirects", "nonredirectsallpages", wikiFuzz::makeFuzz( 2 ) ) );
2110                         case 'allrev'     : return wikiFuzz::chooseInput( array( "1", 0, "", wikiFuzz::makeFuzz( 2 ) ) );
2111                         case 'prop'       : return wikiFuzz::chooseInput( array( "user", "comment", "timestamp", "patrol", "flags", "user|user|comment|flags", wikiFuzz::makeFuzz( 2 ) ) );
2112                         case 'type'       : return wikiFuzz::chooseInput( array( "block", "protect", "rights", "delete", "upload", "move", "import", "renameuser", "newusers", "makebot", wikiFuzz::makeFuzz( 2 ) ) );
2113                         case 'hide'       : return wikiFuzz::chooseInput( array( "minor", "bots", "anons", "liu", "liu|bots|", wikiFuzz::makeFuzz( 2 ) ) );
2114                         case 'show'       : return wikiFuzz::chooseInput( array( 'minor', '!minor', 'bot', '!bot', 'anon', '!anon', wikiFuzz::makeFuzz( 2 ) ) );
2115                         default           : return wikiFuzz::makeFuzz( 2 );
2116                 }
2117         }
2118
2119         // Entry point.
2120         function __construct() {
2121                 $this->pagePath = "api.php";
2122
2123                 $modes = array ( "help",
2124                                                 "login",
2125                                                 "opensearch",
2126                                                 "feedwatchlist",
2127                                                 "query" );
2128                 $action = wikiFuzz::chooseInput( array_merge ( $modes, array( wikiFuzz::makeFuzz( 2 ) ) ) );
2129
2130                 switch ( $action ) {
2131                         case "login"         : $this->params = self::loginMode();
2132                                                                    break;
2133                         case "opensearch"    : $this->params = self::opensearchMode();
2134                                                                    break;
2135                         case "feedwatchlist" : $this->params = self::feedwatchlistMode();
2136                                                                    break;
2137                         case "query"         : $this->params = self::queryMode();
2138                                                                    break;
2139                         case "help"         :
2140                         default             :  // Do something random - "Crazy Ivan" mode.
2141                                                                    $random_mode = wikiFuzz::chooseInput( $modes ) . "Mode";
2142                                                                    // There is no "helpMode".
2143                                                                    if ( $random_mode == "helpMode" ) $random_mode = "queryMode";
2144                                                                    $this->params = self::$random_mode();
2145                                                                    break;
2146                 }
2147
2148                 // Save the selected action.
2149                 $this->params["action"] = $action;
2150
2151                 // Set the cookie:
2152                 // FIXME: need to get this cookie dynamically set, rather than hard-coded.
2153                 $this->cookie = "wikidbUserID=10001; wikidbUserName=Test; wikidb_session=178df0fe68c75834643af65dec9ec98a; wikidbToken=1adc6753d62c44aec950c024d7ae0540";
2154
2155                 // Output format
2156                 $this->params["format"] = wikiFuzz::chooseInput( array( "json", "jsonfm", "php", "phpfm",
2157                                                                                                                            "wddx", "wddxfm", "xml", "xmlfm",
2158                                                                                                                            "yaml", "yamlfm", "raw", "rawfm",
2159                                                                                                                            wikiFuzz::makeFuzz( 2 ) ) );
2160
2161                 // Page does not produce HTML (sometimes).
2162                 $this->tidyValidate = false;
2163         }
2164 }
2165
2166
2167 /**
2168  ** a page test for the GeSHi extension.
2169  */
2170 class GeSHi_Test extends pageTest {
2171
2172         private function getGeSHiContent() {
2173                 return "<source lang=\"" . $this->getLang() . "\" "
2174                            . ( wikiFuzz::randnum( 2 ) == 0 ? "line " : "" )
2175                            . ( wikiFuzz::randnum( 2 ) == 0 ? "strict " : "" )
2176                            . "start=" . wikiFuzz::chooseInput( array( wikiFuzz::randnum( -6000, 6000 ), wikiFuzz::makeFuzz( 2 ) ) )
2177                            . ">"
2178                            . wikiFuzz::makeFuzz( 2 )
2179                            . "</source>";
2180         }
2181
2182         private function getLang() {
2183         return wikiFuzz::chooseInput( array( "actionscript", "ada", "apache", "applescript", "asm", "asp", "autoit", "bash", "blitzbasic", "bnf", "c", "c_mac", "caddcl", "cadlisp",
2184                                 "cfdg", "cfm", "cpp", "cpp-qt", "csharp", "css", "d", "delphi", "diff", "div", "dos", "eiffel", "fortran", "freebasic", "gml", "groovy", "html4strict", "idl",
2185                                 "ini", "inno", "io", "java", "java5", "javascript", "latex", "lisp", "lua", "matlab", "mirc", "mpasm", "mysql", "nsis", "objc", "ocaml", "ocaml-brief", "oobas",
2186                                 "oracle8", "pascal", "perl", "php", "php-brief", "plsql", "python", "qbasic", "rails", "reg", "robots", "ruby", "sas", "scheme", "sdlbasic", "smalltalk", "smarty",
2187                                 "sql", "tcl", "text", "thinbasic", "tsql", "vb", "vbnet", "vhdl", "visualfoxpro", "winbatch", "xml", "xpp", "z80", wikiFuzz::makeFuzz( 1 ) ) );
2188         }
2189
2190         function __construct() {
2191                 $this->pagePath = "index.php?title=WIKIFUZZ";
2192
2193                 $this->params = array (
2194                                 "action"        => "submit",
2195                                 "wpMinoredit"   => "test",
2196                                 "wpPreview"     => "test",
2197                                 "wpSection"     => "test",
2198                                 "wpEdittime"    => "test",
2199                                 "wpSummary"     => "test",
2200                                 "wpScrolltop"   => "test",
2201                                 "wpStarttime"   => "test",
2202                                 "wpAutoSummary" => "test",
2203                                 "wpTextbox1"    => $this->getGeSHiContent() // the main wiki text, contains fake GeSHi content.
2204                                 );
2205         }
2206 }
2207
2208
2209 /**
2210  ** selects a page test to run.
2211  */
2212 function selectPageTest( $count ) {
2213
2214         // if the user only wants a specific test, then only ever give them that.
2215         if ( defined( "SPECIFIC_TEST" ) ) {
2216                 $testType = SPECIFIC_TEST;
2217                 return new $testType ();
2218         }
2219
2220         // Some of the time we test Special pages, the remaining
2221         // time we test using the standard edit page.
2222         switch ( $count % 100 ) {
2223                 case 0 : return new successfulUserLoginTest();
2224                 case 1 : return new listusersTest();
2225                 case 2 : return new searchTest();
2226                 case 3 : return new recentchangesTest();
2227                 case 4 : return new prefixindexTest();
2228                 case 5 : return new mimeSearchTest();
2229                 case 6 : return new specialLogTest();
2230                 case 7 : return new userLoginTest();
2231                 case 8 : return new ipblocklistTest();
2232                 case 9 : return new newImagesTest();
2233                 case 10: return new imagelistTest();
2234                 case 11: return new specialExportTest();
2235                 case 12: return new specialBooksourcesTest();
2236                 case 13: return new specialAllpagesTest();
2237                 case 14: return new pageHistoryTest();
2238                 case 15: return new contributionsTest();
2239                 case 16: return new viewPageTest();
2240                 case 17: return new specialAllmessagesTest();
2241                 case 18: return new specialNewpages();
2242                 case 19: return new searchTest();
2243                 case 20: return new redirectTest();
2244                 case 21: return new confirmEmail();
2245                 case 22: return new watchlistTest();
2246                 case 23: return new specialBlockmeTest();
2247                 case 24: return new specialUndelete();
2248                 case 25: return new specialMovePage();
2249                 case 26: return new specialUnlockdb();
2250                 case 27: return new specialLockdb();
2251                 case 28: return new specialUserrights();
2252                 case 29: return new pageProtectionForm();
2253                 case 30: return new specialBlockip();
2254                 case 31: return new imagepageTest();
2255                 case 32: return new pageDeletion();
2256                 case 33: return new specialRevisionDelete();
2257                 case 34: return new specialImport();
2258                 case 35: return new thumbTest();
2259                 case 36: return new trackbackTest();
2260                 case 37: return new profileInfo();
2261                 case 38: return new specialCite();
2262                 case 39: return new specialFilepath();
2263                 case 40: return new specialMakebot();
2264                 case 41: return new specialMakesysop();
2265                 case 42: return new specialRenameuser();
2266                 case 43: return new specialLinksearch();
2267                 case 44: return new specialCategoryTree();
2268                 case 45: return new api();
2269                 case 45: return new specialChemicalsourcesTest();
2270                 default: return new editPageTest();
2271         }
2272 }
2273
2274
2275 // /////////////////////  SAVING OUTPUT  /////////////////////////
2276
2277 /**
2278  ** Utility function for saving a file. Currently has no error checking.
2279  */
2280 function saveFile( $data, $name ) {
2281         file_put_contents( $name, $data );
2282 }
2283
2284
2285 /**
2286  ** Returns a test as an experimental GET-to-POST URL.
2287  **        This doesn't seem to always work though, and sometimes the output is too long
2288  **        to be a valid GET URL, so we also save in other formats.
2289  */
2290 function getAsURL( pageTest $test ) {
2291         $used_question_mark = ( strpos( $test->getPagePath(), "?" ) !== false );
2292         $retval = "http://get-to-post.nickj.org/?" . WIKI_BASE_URL . $test->getPagePath();
2293         foreach ( $test->getParams() as $param => $value ) {
2294                 if ( !$used_question_mark ) {
2295                         $retval .= "?";
2296                         $used_question_mark = true;
2297                 }
2298                 else {
2299                         $retval .= "&";
2300                 }
2301                 $retval .= $param . "=" . urlencode( $value );
2302         }
2303         return $retval;
2304 }
2305
2306
2307 /**
2308  ** Saves a plain-text human-readable version of a test.
2309  */
2310 function saveTestAsText( pageTest $test, $filename ) {
2311         $str = "Test: " . $test->getPagePath();
2312         foreach ( $test->getParams() as $param => $value ) {
2313                 $str .= "\n$param: $value";
2314         }
2315         $str .= "\nGet-to-post URL: " . getAsURL( $test ) . "\n";
2316         saveFile( $str, $filename );
2317 }
2318
2319
2320 /**
2321  ** Saves a test as a standalone basic PHP script that shows this one problem.
2322  **        Resulting script requires PHP-Curl be installed in order to work.
2323  */
2324 function saveTestAsPHP( pageTest $test, $filename ) {
2325         $str = "<?php\n"
2326                 . "\$params = " . var_export( escapeForCurl( $test->getParams() ), true ) . ";\n"
2327                 . "\$ch = curl_init();\n"
2328                 . "curl_setopt(\$ch, CURLOPT_POST, 1);\n"
2329                 . "curl_setopt(\$ch, CURLOPT_POSTFIELDS, \$params );\n"
2330                 . "curl_setopt(\$ch, CURLOPT_URL, " . var_export( WIKI_BASE_URL . $test->getPagePath(), true ) . ");\n"
2331                 . "curl_setopt(\$ch, CURLOPT_RETURNTRANSFER,1);\n"
2332                 .  ( $test->getCookie() ? "curl_setopt(\$ch, CURLOPT_COOKIE, " . var_export( $test->getCookie(), true ) . ");\n" : "" )
2333                 . "\$result=curl_exec(\$ch);\n"
2334                 . "curl_close (\$ch);\n"
2335                 . "print \$result;\n"
2336                 . "?>\n";
2337         saveFile( $str, $filename );
2338 }
2339
2340
2341 /**
2342  ** Escapes a value so that it can be used on the command line by Curl.
2343  **        Specifically, "<" and "@" need to be escaped if they are the first character,
2344  **        otherwise  curl interprets these as meaning that we want to insert a file.
2345  */
2346 function escapeForCurl( array $input_params ) {
2347         $output_params = array();
2348         foreach ( $input_params as $param => $value ) {
2349                 if ( strlen( $value ) > 0 && ( $value[0] == "@" || $value[0] == "<" ) ) {
2350                         $value = "\\" . $value;
2351                 }
2352                 $output_params[$param] = $value;
2353         }
2354         return $output_params;
2355 }
2356
2357
2358 /**
2359  ** Saves a test as a standalone CURL shell script that shows this one problem.
2360  **        Resulting script requires standalone Curl be installed in order to work.
2361  */
2362 function saveTestAsCurl( pageTest $test, $filename ) {
2363         $str = "#!/bin/bash\n"
2364                 . "curl --silent --include --globoff \\\n"
2365                 . ( $test->getCookie() ? " --cookie " . escapeshellarg( $test->getCookie() ) . " \\\n" : "" );
2366         foreach ( escapeForCurl( $test->getParams() ) as $param => $value ) {
2367                 $str .= " -F " . escapeshellarg( $param ) . "=" . escapeshellarg( $value ) . " \\\n";
2368         }
2369         $str .= " " . escapeshellarg( WIKI_BASE_URL . $test->getPagePath() ); // beginning space matters.
2370         $str .= "\n";
2371         saveFile( $str, $filename );
2372         chmod( $filename, 0755 ); // make executable
2373 }
2374
2375
2376 /**
2377  ** Saves the internal data structure to file.
2378  */
2379 function saveTestData ( pageTest $test, $filename ) {
2380         saveFile( serialize( $test ),  $filename );
2381 }
2382
2383
2384 /**
2385  ** saves a test in the various formats.
2386  */
2387 function saveTest( pageTest $test, $testname ) {
2388         $base_name = DIRECTORY . "/" . $testname;
2389         saveTestAsText( $test, $base_name . INFO_FILE );
2390         saveTestAsPHP ( $test, $base_name . PHP_TEST );
2391         saveTestAsCurl( $test, $base_name . CURL_TEST );
2392         saveTestData  ( $test, $base_name . DATA_FILE );
2393 }
2394
2395
2396 // ////////////////// MEDIAWIKI OUTPUT /////////////////////////
2397
2398 /**
2399  ** Asks MediaWiki for the HTML output of a test.
2400  */
2401 function wikiTestOutput( pageTest $test ) {
2402
2403         $ch = curl_init();
2404
2405         // specify the cookie, if required.
2406         if ( $test->getCookie() ) curl_setopt( $ch, CURLOPT_COOKIE, $test->getCookie() );
2407         curl_setopt( $ch, CURLOPT_POST, 1 );                          // save form using a POST
2408
2409         $params = escapeForCurl( $test->getParams() );
2410         curl_setopt( $ch, CURLOPT_POSTFIELDS, $params );             // load the POST variables
2411
2412         curl_setopt( $ch, CURLOPT_URL, WIKI_BASE_URL . $test->getPagePath() );  // set url to post to
2413         curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );                 // return into a variable
2414
2415         $result = curl_exec ( $ch );
2416
2417         // if we encountered an error, then say so, and return an empty string.
2418         if ( curl_error( $ch ) ) {
2419                 print "\nCurl error #: " . curl_errno( $ch ) . " - " . curl_error ( $ch );
2420                 $result = "";
2421         }
2422
2423         curl_close ( $ch );
2424
2425         return $result;
2426 }
2427
2428
2429 // ////////////////// HTML VALIDATION /////////////////////////
2430
2431 /*
2432  ** Asks the validator whether this is valid HTML, or not.
2433  */
2434 function validateHTML( $text ) {
2435
2436         $params = array ( "fragment"   => $text );
2437
2438         $ch = curl_init();
2439
2440         curl_setopt( $ch, CURLOPT_POST, 1 );                    // save form using a POST
2441         curl_setopt( $ch, CURLOPT_POSTFIELDS, $params );        // load the POST variables
2442         curl_setopt( $ch, CURLOPT_URL, VALIDATOR_URL );         // set url to post to
2443         curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );           // return into a variable
2444
2445         $result = curl_exec ( $ch );
2446
2447         // if we encountered an error, then log it, and exit.
2448         if ( curl_error( $ch ) ) {
2449                 trigger_error( "Curl error #: " . curl_errno( $ch ) . " - " . curl_error ( $ch ) );
2450                 print "Curl error #: " . curl_errno( $ch ) . " - " . curl_error ( $ch ) . " - exiting.\n";
2451                 exit( 1 );
2452         }
2453
2454         curl_close ( $ch );
2455
2456         $valid = ( strpos( $result, "Failed validation" ) === false ? true : false );
2457
2458         return array( $valid, $result );
2459 }
2460
2461
2462 /**
2463  ** Get tidy to check for no HTML errors in the output file (e.g. unescaped strings).
2464  */
2465 function tidyCheckFile( $name ) {
2466         $file = DIRECTORY . "/" . $name;
2467         $command = PATH_TO_TIDY . " -output /tmp/out.html -quiet $file 2>&1";
2468         $x = `$command`;
2469
2470         // Look for the most interesting Tidy errors and warnings.
2471         if (   strpos( $x, "end of file while parsing attributes" ) !== false
2472                         || strpos( $x, "attribute with missing trailing quote mark" ) !== false
2473                         || strpos( $x, "missing '>' for end of tag" ) !== false
2474                         || strpos( $x, "Error:" ) !== false ) {
2475                 print "\nTidy found something - view details with: $command";
2476                 return false;
2477         } else {
2478                 return true;
2479         }
2480 }
2481
2482
2483 /**
2484  ** Returns whether or not an database error log file has changed in size since
2485  **        the last time this was run. This is used to tell if a test caused a DB error.
2486  */
2487 function dbErrorLogged() {
2488         static $filesize;
2489
2490         // first time running this function
2491         if ( !isset( $filesize ) ) {
2492                 // create log if it does not exist
2493                 if ( !file_exists( DB_ERROR_LOG_FILE ) ) {
2494                         saveFile( "", DB_ERROR_LOG_FILE );
2495                 }
2496                 $filesize = filesize( DB_ERROR_LOG_FILE );
2497                 return false;
2498         }
2499
2500         $newsize = filesize( DB_ERROR_LOG_FILE );
2501         // if the log has grown, then assume the current test caused it.
2502         if ( $newsize != $filesize ) {
2503                 $filesize = $newsize;
2504                 return true;
2505         }
2506
2507         return false;
2508 }
2509
2510 // //////////////// TOP-LEVEL PROBLEM-FINDING FUNCTION ////////////////////////
2511
2512 /**
2513  ** takes a page test, and runs it and tests it for problems in the output.
2514  **        Returns: False on finding a problem, or True on no problems being found.
2515  */
2516 function runWikiTest( pageTest $test, &$testname, $can_overwrite = false ) {
2517
2518         // by default don't overwrite a previous test of the same name.
2519         while ( ! $can_overwrite && file_exists( DIRECTORY . "/" . $testname . DATA_FILE ) ) {
2520                 $testname .= "-" . mt_rand( 0, 9 );
2521         }
2522
2523         $filename = DIRECTORY . "/" . $testname . DATA_FILE;
2524
2525         // Store the time before and after, to find slow pages.
2526         $before = microtime( true );
2527
2528         // Get MediaWiki to give us the output of this test.
2529         $wiki_preview = wikiTestOutput( $test );
2530
2531         $after = microtime( true );
2532
2533         // if we received no response, then that's interesting.
2534         if ( $wiki_preview == "" ) {
2535                 print "\nNo response received for: $filename";
2536                 return false;
2537         }
2538
2539         // save output HTML to file.
2540         $html_file = DIRECTORY . "/" . $testname . HTML_FILE;
2541         saveFile( $wiki_preview,  $html_file );
2542
2543         // if there were PHP errors in the output, then that's interesting too.
2544         if (       strpos( $wiki_preview, "<b>Warning</b>: "        ) !== false
2545                         || strpos( $wiki_preview, "<b>Fatal error</b>: "    ) !== false
2546                         || strpos( $wiki_preview, "<b>Notice</b>: "         ) !== false
2547                         || strpos( $wiki_preview, "<b>Error</b>: "          ) !== false
2548                         || strpos( $wiki_preview, "<b>Strict Standards:</b>" ) !== false
2549                         ) {
2550                 $error = substr( $wiki_preview, strpos( $wiki_preview, "</b>:" ) + 7, 50 );
2551                 // Avoid probable PHP bug with bad session ids; http://bugs.php.net/bug.php?id=38224
2552                 if ( $error != "Unknown: The session id contains illegal character" ) {
2553                         print "\nPHP error/warning/notice in HTML output: $html_file ; $error";
2554                         return false;
2555                 }
2556         }
2557
2558         // if there was a MediaWiki Backtrace message in the output, then that's also interesting.
2559         if ( strpos( $wiki_preview, "Backtrace:" ) !== false ) {
2560                 print "\nInternal MediaWiki error in HTML output: $html_file";
2561                 return false;
2562         }
2563
2564         // if there was a Parser error comment in the output, then that's potentially interesting.
2565         if ( strpos( $wiki_preview, "!-- ERR" ) !== false ) {
2566                 print "\nParser Error comment in HTML output: $html_file";
2567                 return false;
2568         }
2569
2570         // if a database error was logged, then that's definitely interesting.
2571         if ( dbErrorLogged() ) {
2572                 print "\nDatabase Error logged for: $filename";
2573                 return false;
2574         }
2575
2576         // validate result
2577         $valid = true;
2578         if ( VALIDATE_ON_WEB ) {
2579                 list ( $valid, $validator_output ) = validateHTML( $wiki_preview );
2580                 if ( !$valid ) print "\nW3C web validation failed - view details with: html2text " . DIRECTORY . "/" . $testname . ".validator_output.html";
2581         }
2582
2583         // Get tidy to check the page, unless we already know it produces non-XHTML output.
2584         if ( $test->tidyValidate() ) {
2585                 $valid = tidyCheckFile( $testname . HTML_FILE ) && $valid;
2586         }
2587
2588         // if it took more than 2 seconds to render, then it may be interesting too. (Possible DoS attack?)
2589         if ( ( $after - $before ) >= 2 ) {
2590                 print "\nParticularly slow to render (" . round( $after - $before, 2 ) . " seconds): $filename";
2591                 return false;
2592         }
2593
2594         if ( $valid ) {
2595                 // Remove temp HTML file if test was valid:
2596                 unlink( $html_file );
2597         } elseif ( VALIDATE_ON_WEB ) {
2598                 saveFile( $validator_output,   DIRECTORY . "/" . $testname . ".validator_output.html" );
2599         }
2600
2601         return $valid;
2602 }
2603
2604
2605 // ///////////////// RERUNNING OLD TESTS ///////////////////
2606
2607 /**
2608  ** We keep our failed tests so that they can be rerun.
2609  **        This function does that retesting.
2610  */
2611 function rerunPreviousTests() {
2612         print "Retesting previously found problems.\n";
2613
2614         $dir_contents = scandir ( DIRECTORY );
2615
2616         // sort file into the order a normal person would use.
2617         natsort ( $dir_contents );
2618
2619         foreach ( $dir_contents as $file ) {
2620
2621                 // if file is not a test, then skip it.
2622                 // Note we need to escape any periods or will be treated as "any character".
2623                 $matches = array();
2624                 if ( !preg_match( "/(.*)" . str_replace( ".", "\.", DATA_FILE ) . "$/", $file, $matches ) ) continue;
2625
2626                 // reload the test.
2627                 $full_path = DIRECTORY . "/" . $file;
2628                 $test = unserialize( file_get_contents( $full_path ) );
2629
2630                 // if this is not a valid test, then skip it.
2631                 if ( ! $test instanceof pageTest ) {
2632                         print "\nSkipping invalid test - $full_path";
2633                         continue;
2634                 }
2635
2636                 // The date format is in Apache log format, which makes it easier to locate
2637                 // which retest caused which error in the Apache logs (only happens usually if
2638                 // apache segfaults).
2639                 if ( !QUIET ) print "[" . date ( "D M d H:i:s Y" ) . "] Retesting $file (" . get_class( $test ) . ")";
2640
2641                 // run test
2642                 $testname = $matches[1];
2643                 $valid = runWikiTest( $test, $testname, true );
2644
2645                 if ( !$valid ) {
2646                         saveTest( $test, $testname );
2647                         if ( QUIET ) {
2648                                 print "\nTest: " . get_class( $test ) . " ; Testname: $testname\n------";
2649                         } else {
2650                                 print "\n";
2651                         }
2652                 }
2653                 else {
2654                         if ( !QUIET ) print "\r";
2655                         if ( DELETE_PASSED_RETESTS ) {
2656                                 $prefix = DIRECTORY . "/" . $testname;
2657                                 if ( is_file( $prefix . DATA_FILE ) ) unlink( $prefix . DATA_FILE );
2658                                 if ( is_file( $prefix . PHP_TEST ) ) unlink( $prefix . PHP_TEST );
2659                                 if ( is_file( $prefix . CURL_TEST ) ) unlink( $prefix . CURL_TEST );
2660                                 if ( is_file( $prefix . INFO_FILE ) ) unlink( $prefix . INFO_FILE );
2661                         }
2662                 }
2663         }
2664
2665         print "\nDone retesting.\n";
2666 }
2667
2668
2669 // ////////////////////  MAIN LOOP  ////////////////////////
2670
2671
2672 // first check whether CURL is installed, because sometimes it's not.
2673 if ( ! function_exists( 'curl_init' ) ) {
2674         die( "Could not find 'curl_init' function. Is the curl extension compiled into PHP?\n" );
2675 }
2676
2677 // Initialization of types. wikiFuzz doesn't have a constructor because we want to
2678 // access it staticly and not have any globals.
2679 wikiFuzz::$types = array_keys( wikiFuzz::$data );
2680
2681 // Make directory if doesn't exist
2682 if ( !is_dir( DIRECTORY ) ) {
2683         mkdir ( DIRECTORY, 0700 );
2684 }
2685 // otherwise, we first retest the things that we have found in previous runs
2686 else if ( RERUN_OLD_TESTS ) {
2687         rerunPreviousTests();
2688 }
2689
2690 // main loop.
2691 $start_time = date( "U" );
2692 $num_errors = 0;
2693 if ( !QUIET ) {
2694         print "Beginning main loop. Results are stored in the " . DIRECTORY . " directory.\n";
2695         print "Press CTRL+C to stop testing.\n";
2696 }
2697
2698 for ( $count = 0; true; $count++ ) {
2699         if ( !QUIET ) {
2700                 // spinning progress indicator.
2701                 switch( $count % 4 ) {
2702                         case '0': print "\r/";  break;
2703                         case '1': print "\r-";  break;
2704                         case '2': print "\r\\"; break;
2705                         case '3': print "\r|";  break;
2706                 }
2707                 print " $count";
2708         }
2709
2710         // generate a page test to run.
2711         $test = selectPageTest( $count );
2712
2713         $mins = ( date( "U" ) - $start_time ) / 60;
2714         if ( !QUIET && $mins > 0 ) {
2715                 print ".  $num_errors poss errors. "
2716                         . floor( $mins ) . " mins. "
2717                         . round ( $count / $mins, 0 ) . " tests/min. "
2718                         . get_class( $test ); // includes the current test name.
2719         }
2720
2721         // run this test against MediaWiki, and see if the output was valid.
2722         $testname = $count;
2723         $valid = runWikiTest( $test, $testname, false );
2724
2725         // save the failed test
2726         if ( ! $valid ) {
2727                 if ( QUIET ) {
2728                         print "\nTest: " . get_class( $test ) . " ; Testname: $testname\n------";
2729                 } else {
2730                         print "\n";
2731                 }
2732                 saveTest( $test, $testname );
2733                 $num_errors += 1;
2734         } else if ( KEEP_PASSED_TESTS ) {
2735                 // print current time, with microseconds (matches "strace" format), and the test name.
2736                 print " " . date( "H:i:s." ) . substr( current( explode( " ", microtime() ) ), 2 ) . " " . $testname;
2737                 saveTest( $test, $testname );
2738         }
2739
2740         // stop if we have reached max number of errors.
2741         if ( defined( "MAX_ERRORS" ) && $num_errors >= MAX_ERRORS ) {
2742                 break;
2743         }
2744
2745         // stop if we have reached max number of mins runtime.
2746         if ( defined( "MAX_RUNTIME" ) && $mins >= MAX_RUNTIME ) {
2747                 break;
2748         }
2749 }
2750
2751