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