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