patch by Nick Jenkins:
[lhc/web/wiklou.git] / maintenance / wiki-mangleme.php
1 <?php
2 /**
3
4 Author : Nick Jenkins, http://nickj.org/
5 Date : 18 May 2006.
6 License: GPL v 2.
7
8 Desc:
9 Performs fuzz-style testing of MediaWiki's parser.
10 The script feeds the parser some randomized malformed wiki-text, and stores
11 the HTML output.
12
13 Checks the HTML output for:
14 - unclosed tags
15 - errors in Tidy
16 both can indicate potential security issues.
17
18 Can optionally W3C validate of the HTML output (indicates malformed HTML
19 output).
20
21 Background:
22 Contains a PHP port, of a "shameless" Python PORT, OF LCAMTUF'S MANGELME
23 http://www.securiteam.com/tools/6Z00N1PBFK.html
24
25 Requirements:
26 You need PHP4 or PHP5, with PHP-curl enabled, and Tidy installed.
27
28 Usage:
29 Update the "Configuration" section, especially the "WIKI_URL" to point
30 to a local wiki you can test stuff on. You can optionally set
31 "VALIDATE_ON_WEB" to true, although at the moment very few generated pages
32 will validate. Then run "php wiki-mangleme.php".
33
34 This will print a list of HTML output that had unclosed tags, and/or that
35 caused tidy errors. It will keep running until you press Ctrl-C. All output
36 files are stored in the "mangleme" subdirectory.
37 */
38
39 # This is a command line script, load mediawiki env:
40 include('commandLine.inc');
41
42 // Configuration:
43
44 # The directory name where we store the output
45 # for windows: "c:\\temp\\mangleme"
46 define("DIRECTORY", "/tmp/mangleme");
47
48 # URL to some wiki on which we can run our tests:
49 define("WIKI_URL", $wgServer . $wgScriptPath . '/index.php?title=WIKIMANGLE' );
50
51 # Should our test output include binary strings?
52 define("INCLUDE_BINARY", false);
53
54 # Whether we want to send output on the web for validation:
55 define("VALIDATE_ON_WEB", false);
56 # URL to use to validate our output:
57 define("VALIDATOR_URL", "http://validator.w3.org/check");
58
59
60 // If it goes wrong, we want to know about it.
61 error_reporting(E_ALL);
62
63 ///////////////////// DEFINE THE DATA THAT WILL BE USED //////////////////////
64 /* Note: Only some HTML tags are understood by MediaWiki, the rest is ignored.
65 The tags that are ignored have been commented out below. */
66
67 $data = array();
68 // $data["A"] = array("NAME", "HREF", "REF", "REV", "TITLE", "TARGET", "SHAPE", "onLoad", "STYLE");
69 // $data["APPLET"] = array("CODEBASE", "CODE", "NAME", "ALIGN", "ALT", "HEIGHT", "WIDTH", "HSPACE", "VSPACE", "DOWNLOAD", "HEIGHT", "NAME", "TITLE", "onLoad", "STYLE");
70 // $data["AREA"] = array("SHAPE", "ALT", "CO-ORDS", "HREF", "onLoad", "STYLE");
71 $data["B"] = array("onLoad", "STYLE");
72 // $data["BANNER"] = array("onLoad", "STYLE");
73 // $data["BASE"] = array("HREF", "TARGET", "onLoad", "STYLE");
74 // $data["BASEFONT"] = array("SIZE", "onLoad", "STYLE");
75 // $data["BGSOUND"] = array("SRC", "LOOP", "onLoad", "STYLE");
76 // $data["BQ"] = array("CLEAR", "NOWRAP", "onLoad", "STYLE");
77 // $data["BODY"] = array("BACKGROUND", "BGCOLOR", "TEXT", "LINK", "ALINK", "VLINK", "LEFTMARGIN", "TOPMARGIN", "BGPROPERTIES", "onLoad", "STYLE");
78 $data["CAPTION"] = array("ALIGN", "VALIGN", "onLoad", "STYLE");
79 $data["CENTER"] = array("onLoad", "STYLE");
80 // $data["COL"] = array("ALIGN", "SPAN", "onLoad", "STYLE");
81 // $data["COLGROUP"] = array("ALIGN", "VALIGN", "HALIGN", "WIDTH", "SPAN", "onLoad", "STYLE");
82 $data["DIV"] = array("ALIGN", "CLASS", "LANG", "onLoad", "STYLE");
83 // $data["EMBED"] = array("SRC", "HEIGHT", "WIDTH", "UNITS", "NAME", "PALETTE", "onLoad", "STYLE");
84 // $data["FIG"] = array("SRC", "ALIGN", "HEIGHT", "WIDTH", "UNITS", "IMAGEMAP", "onLoad", "STYLE");
85 // $data["FN"] = array("ID", "onLoad", "STYLE");
86 $data["FONT"] = array("SIZE", "COLOR", "FACE", "onLoad", "STYLE");
87 // $data["FORM"] = array("ACTION", "METHOD", "ENCTYPE", "TARGET", "SCRIPT", "onLoad", "STYLE");
88 // $data["FRAME"] = array("SRC", "NAME", "MARGINWIDTH", "MARGINHEIGHT", "SCROLLING", "FRAMESPACING", "onLoad", "STYLE");
89 // $data["FRAMESET"] = array("ROWS", "COLS", "onLoad", "STYLE");
90 $data["H1"] = array("SRC", "DINGBAT", "onLoad", "STYLE");
91 // $data["HEAD"] = array("onLoad", "STYLE");
92 $data["HR"] = array("SRC", "SIZE", "WIDTH", "ALIGN", "COLOR", "onLoad", "STYLE");
93 // $data["HTML"] = array("onLoad", "STYLE");
94 // $data["IFRAME"] = array("ALIGN", "FRAMEBORDER", "HEIGHT", "MARGINHEIGHT", "MARGINWIDTH", "NAME", "SCROLLING", "SRC", "ADDRESS", "WIDTH", "onLoad", "STYLE");
95 // $data["IMG"] = array("ALIGN", "ALT", "SRC", "BORDER", "DYNSRC", "HEIGHT", "HSPACE", "ISMAP", "LOOP", "LOWSRC", "START", "UNITS", "USEMAP", "WIDTH", "VSPACE", "onLoad", "STYLE");
96 // $data["INPUT"] = array("TYPE", "NAME", "VALUE", "onLoad", "STYLE");
97 // $data["ISINDEX"] = array("HREF", "PROMPT", "onLoad", "STYLE");
98 $data["LI"] = array("SRC", "DINGBAT", "SKIP", "TYPE", "VALUE", "onLoad", "STYLE");
99 // $data["LINK"] = array("REL", "REV", "HREF", "TITLE", "onLoad", "STYLE");
100 // $data["MAP"] = array("NAME", "onLoad", "STYLE");
101 // $data["MARQUEE"] = array("ALIGN", "BEHAVIOR", "BGCOLOR", "DIRECTION", "HEIGHT", "HSPACE", "LOOP", "SCROLLAMOUNT", "SCROLLDELAY", "WIDTH", "VSPACE", "onLoad", "STYLE");
102 // $data["MENU"] = array("onLoad", "STYLE");
103 // $data["META"] = array("HTTP-EQUIV", "CONTENT", "NAME", "onLoad", "STYLE");
104 // $data["MULTICOL"] = array("COLS", "GUTTER", "WIDTH", "onLoad", "STYLE");
105 // $data["NOFRAMES"] = array("onLoad", "STYLE");
106 // $data["NOTE"] = array("CLASS", "SRC", "onLoad", "STYLE");
107 // $data["OVERLAY"] = array("SRC", "X", "Y", "HEIGHT", "WIDTH", "UNITS", "IMAGEMAP", "onLoad", "STYLE");
108 // $data["PARAM"] = array("NAME", "VALUE", "onLoad", "STYLE");
109 // $data["RANGE"] = array("FROM", "UNTIL", "onLoad", "STYLE");
110 // $data["SCRIPT"] = array("LANGUAGE", "onLoad", "STYLE");
111 // $data["SELECT"] = array("NAME", "SIZE", "MULTIPLE", "WIDTH", "HEIGHT", "UNITS", "onLoad", "STYLE");
112 // $data["OPTION"] = array("VALUE", "SHAPE", "onLoad", "STYLE");
113 // $data["SPACER"] = array("TYPE", "SIZE", "WIDTH", "HEIGHT", "ALIGN", "onLoad", "STYLE");
114 // $data["SPOT"] = array("ID", "onLoad", "STYLE");
115 // $data["TAB"] = array("INDENT", "TO", "ALIGN", "DP", "onLoad", "STYLE");
116 $data["TABLE"] = array("ALIGN", "WIDTH", "BORDER", "CELLPADDING", "CELLSPACING", "BGCOLOR", "VALIGN", "COLSPEC", "UNITS", "DP", "onLoad", "STYLE");
117 // $data["TBODY"] = array("CLASS", "ID", "onLoad", "STYLE");
118 $data["TD"] = array("COLSPAN", "ROWSPAN", "ALIGN", "VALIGN", "BGCOLOR", "onLoad", "STYLE");
119 // $data["TEXTAREA"] = array("NAME", "COLS", "ROWS", "onLoad", "STYLE");
120 // $data["TEXTFLOW"] = array("CLASS", "ID", "onLoad", "STYLE");
121 // $data["TFOOT"] = array("COLSPAN", "ROWSPAN", "ALIGN", "VALIGN", "BGCOLOR", "onLoad", "STYLE");
122 $data["TH"] = array("ALIGN", "CLASS", "ID", "onLoad", "STYLE");
123 // $data["TITLE"] = array("onLoad", "STYLE");
124 $data["TR"] = array("ALIGN", "VALIGN", "BGCOLOR", "CLASS", "onLoad", "STYLE");
125 $data["UL"] = array("SRC", "DINGBAT", "SKIP", "TYPE", "VALUE", "onLoad", "STYLE");
126
127 // Now add in a few that were not in the original, but which MediaWiki understands, even with
128 // extraneous attributes:
129 $data["gallery"] = array("CLASS", "ID", "onLoad", "STYLE");
130 $data["pre"] = array("CLASS", "ID", "onLoad", "STYLE");
131 $data["nowiki"] = array("CLASS", "ID", "onLoad", "STYLE");
132 $data["blockquote"] = array("CLASS", "ID", "onLoad", "STYLE");
133 $data["span"] = array("CLASS", "ID", "onLoad", "STYLE");
134 $data["code"] = array("CLASS", "ID", "onLoad", "STYLE");
135 $data["tt"] = array("CLASS", "ID", "onLoad", "STYLE");
136 $data["small"] = array("CLASS", "ID", "onLoad", "STYLE");
137 $data["big"] = array("CLASS", "ID", "onLoad", "STYLE");
138 $data["s"] = array("CLASS", "ID", "onLoad", "STYLE");
139 $data["u"] = array("CLASS", "ID", "onLoad", "STYLE");
140 $data["del"] = array("CLASS", "ID", "onLoad", "STYLE");
141 $data["ins"] = array("CLASS", "ID", "onLoad", "STYLE");
142 $data["sub"] = array("CLASS", "ID", "onLoad", "STYLE");
143 $data["ol"] = array("CLASS", "ID", "onLoad", "STYLE");
144
145
146 // The types of the HTML that we will be testing were defined above
147 $types = array_keys($data);
148
149 // Some attribute values.
150 $other = array("&","=",":","?","\"","\n","%n%n%n%n%n%n%n%n%n%n%n%n","\\");
151 $ints = array("0","-1","127","7897","89000","808080","90928345","74326794236234","0xfffffff","ffff");
152
153 ///////////////////////////////// WIKI-SYNTAX ///////////////////////////
154 /* Note: Defines various wiki-related bits of syntax, that can potentially cause
155 MediaWiki to do something other than just print that literal text */
156 $ext = array(
157 "[[", "]]", "\n{|", "|}", "{{", "}}", "|", "[[image:", "[", "]",
158 "=", "==", "===", "====", "=====", "======", "\n*", "*", "\n:", ":",
159 "{{{", "}}}",
160 "\n", "\n#", "#", "\n;", ";", "\n ",
161 "----", "\n----",
162 "|]]", "~~~", "#REDIRECT [[", "'''", "''",
163 "ISBN 2", "\n|-", "| ", "\n| ",
164 "<!--", "-->",
165 "\"", "'",
166 ">",
167 "http://","https://","url://","ftp://","file://","irc://","javascript:",
168 "!",
169 "\n! ",
170 "!!",
171 "||",
172 ".gif",
173 ".png",
174 ".jpg",
175 ".jpeg",
176 "<!--()()",
177 '%08X',
178 '/',
179 ":x{|",
180 "\n|-",
181 "\n|+",
182 "<noinclude>",
183 "</noinclude>",
184 "\n-----",
185 "UNIQ25f46b0524f13e67NOPARSE",
186 " \302\273",
187 " :",
188 " !",
189 " ;",
190 "\302\253",
191 "RFC 000",
192 "PMID 000",
193 "?=",
194 "(",
195 ")".
196 "]]]",
197 "../",
198 "{{{{",
199 "}}}}",
200 "{{subst:",
201 '__NOTOC__',
202 '__FORCETOC__',
203 '__NOEDITSECTION__',
204 '__START__',
205 '{{PAGENAME}}',
206 '{{PAGENAMEE}}',
207 '{{NAMESPACE}}',
208 '{{MSG:',
209 '{{MSGNW:',
210 '__END__',
211 '{{INT:',
212 '{{SITENAME}}',
213 '{{NS:',
214 '{{LOCALURL:',
215 '{{LOCALURLE:',
216 '{{SCRIPTPATH}}',
217 '{{GRAMMAR:',
218 '__NOTITLECONVERT__',
219 '__NOCONTENTCONVERT__',
220 "<!--MWTEMPLATESECTION=",
221 "<!--LINK 987-->",
222 "<!--IWLINK 987-->",
223 "Image:",
224 "[[category:",
225 "{{REVISIONID}}",
226 "{{SUBPAGENAME}}",
227 "{{SUBPAGENAMEE}}",
228 "{{ns:0}}",
229 "[[:Image",
230 "[[Special:",
231 "{{fullurl:}}",
232 '__TOC__',
233 "<includeonly>",
234 "</includeonly>",
235 "<math>",
236 "</math>"
237 );
238
239
240 ///////////////////// A CLASS THAT GENERATES RANDOM STRINGS OF DATA //////////////////////
241
242 class htmler {
243 var $maxparams = 4;
244 var $maxtypes = 40;
245
246 function randnum($finish,$start=0) {
247 return mt_rand($start,$finish);
248 }
249
250 function randstring() {
251 global $ext;
252 $thestring = "";
253
254 for ($i=0; $i<40; $i++) {
255 $what = $this->randnum(1);
256
257 if ($what == 0) { // include some random wiki syntax
258 $which = $this->randnum(count($ext) - 1);
259 $thestring .= $ext[$which];
260 }
261 else { // include some random text
262 $char = chr(INCLUDE_BINARY ? $this->randnum(255) : $this->randnum(126,32));
263 if ($char == "<") $char = ""; // we don't want the '<' character, it stuffs us up.
264 $length = $this->randnum(8);
265 $thestring .= str_repeat ($char, $length);
266 }
267 }
268 return $thestring;
269 }
270
271 function makestring() {
272 global $ints, $other;
273 $what = $this->randnum(2);
274 if ($what == 0) {
275 return $this->randstring();
276 }
277 elseif ($what == 1) {
278 return $ints[$this->randnum(count($ints) - 1)];
279 }
280 else {
281 return $other[$this->randnum(count($other) - 1)];
282 }
283 }
284
285 function loop() {
286 global $types, $data;
287 $string = "";
288 $i = $this->randnum(count($types) - 1);
289 $t = $types[$i];
290 $arr = $data[$t];
291 $string .= "<" . $types[$i] . " ";
292 for ($z=0; $z<$this->maxparams; $z++) {
293 $badparam = $arr[$this->randnum(count($arr) - 1)];
294 $badstring = $this->makestring();
295 $string .= $badparam . "=" . $badstring . " ";
296 }
297 $string .= ">\n";
298 return $string;
299 }
300
301 function main() {
302 $page = "";
303 for ($k=0; $k<$this->maxtypes; $k++) {
304 $page .= $this->loop();
305 }
306 return $page;
307 }
308 }
309
310
311 //////////////////// SAVING OUTPUT /////////////////////////
312
313
314 /**
315 ** @desc: Utility function for saving a file. Currently has no error checking.
316 */
317 function saveFile($string, $name) {
318 $fp = fopen ( DIRECTORY . "/" . $name, "w");
319 fwrite($fp, $string);
320 fclose ($fp);
321 }
322
323
324 //////////////////// MEDIAWIKI PREVIEW /////////////////////////
325
326 /*
327 ** @desc: Asks MediaWiki for a preview of a string. Returns the HTML.
328 */
329 function wikiPreview($text) {
330
331 $params = array (
332 "action" => "submit",
333 "wpMinoredit" => "1",
334 "wpPreview" => "Show preview",
335 "wpSection" => "new",
336 "wpEdittime" => "",
337 "wpSummary" => "This is a test",
338 "wpTextbox1" => $text
339 );
340
341 if( function_exists('curl_init') ) {
342 $ch = curl_init();
343 } else {
344 die("Could not found 'curl_init' function. Is curl extension enabled ?\n");
345 }
346
347 curl_setopt($ch, CURLOPT_POST, 1); // save form using a POST
348 curl_setopt($ch, CURLOPT_POSTFIELDS, $params); // load the POST variables
349 curl_setopt($ch, CURLOPT_URL, WIKI_URL); // set url to post to
350 curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); // return into a variable
351
352 $result=curl_exec ($ch);
353
354 // if we encountered an error, then log it, and exit.
355 if (curl_error($ch)) {
356 trigger_error("Curl error #: " . curl_errno($ch) . " - " . curl_error ($ch) );
357 print "Curl error #: " . curl_errno($ch) . " - " . curl_error ($ch) . " - exiting.\n";
358 exit();
359 }
360
361 curl_close ($ch);
362
363 return $result;
364 }
365
366
367 //////////////////// HTML VALIDATION /////////////////////////
368
369 /*
370 ** @desc: Asks the validator whether this is valid HTML, or not.
371 */
372 function validateHTML($text) {
373
374 $params = array ("fragment" => $text);
375
376 $ch = curl_init();
377
378 curl_setopt($ch, CURLOPT_POST, 1); // save form using a POST
379 curl_setopt($ch, CURLOPT_POSTFIELDS, $params); // load the POST variables
380 curl_setopt($ch, CURLOPT_URL, VALIDATOR_URL); // set url to post to
381 curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); // return into a variable
382
383 $result=curl_exec ($ch);
384
385 // if we encountered an error, then log it, and exit.
386 if (curl_error($ch)) {
387 trigger_error("Curl error #: " . curl_errno($ch) . " - " . curl_error ($ch) );
388 print "Curl error #: " . curl_errno($ch) . " - " . curl_error ($ch) . " - exiting.\n";
389 exit();
390 }
391
392 curl_close ($ch);
393
394 $valid = (strpos($result, "Failed validation") === false ? true : false);
395
396 return array($valid, $result);
397 }
398
399
400
401 /**
402 ** @desc: checks the string to see if tags are balanced.
403 */
404 function checkOpenCloseTags($string, $filename) {
405 $valid = true;
406
407 $lines = explode("\n", $string);
408
409 $num_lines = count($lines);
410 // print "Num lines: " . $num_lines . "\n";
411
412 foreach ($lines as $line_num => $line) {
413
414 // skip mediawiki's own unbalanced lines.
415 if ($line_num == 15) continue;
416 if ($line == "\t\t<style type=\"text/css\">/*<![CDATA[*/") continue;
417 if ($line == "<textarea tabindex='1' accesskey=\",\" name=\"wpTextbox1\" id=\"wpTextbox1\" rows='25'") continue;
418
419 if ($line == "/*<![CDATA[*/") continue;
420 if ($line == "/*]]>*/") continue;
421 if (ereg("^<form id=\"editform\" name=\"editform\" method=\"post\" action=\"", $line)) continue;
422 if (ereg("^enctype=\"multipart/form-data\"><input type=\"hidden\" name=\"wikidb_session\" value=\"", $line)) continue; // line num and content changes.
423 if ($line == "<textarea tabindex='1' accesskey=\",\" name=\"wpTextbox1\" rows='25'") continue;
424 if (ereg("^cols='80'>", $line)) continue; // line num and content changes.
425
426 if ($num_lines - $line_num == 246) continue;
427 if ($num_lines - $line_num == 65) continue;
428 if ($num_lines - $line_num == 62) continue;
429 if ($num_lines - $line_num == 52) continue;
430 if ($num_lines - $line_num == 50) continue;
431 if ($num_lines - $line_num == 29) continue;
432 if ($num_lines - $line_num == 28) continue;
433 if ($num_lines - $line_num == 27) continue;
434 if ($num_lines - $line_num == 23) continue;
435
436 if (substr_count($line, "<") > substr_count($line, ">")) {
437 print "\nUnclosed tag in " . DIRECTORY . "/" . $filename . " on line: " . ($line_num + 1) . " \n$line\n";
438 $valid = false;
439 }
440 }
441 return $valid;
442 }
443
444
445 /**
446 ** @desc: Get tidy to check for no HTML errors in the output file (e.g. unescaped strings).
447 */
448 function tidyCheckFile($name) {
449 $file = DIRECTORY . "/" . $name;
450 $x = `tidy -errors -quiet --show-warnings false $file 2>&1`;
451 if (trim($x) != "") {
452 print "Tidy errors found in $file:\n$x";
453 return false;
454 } else {
455 return true;
456 }
457 }
458
459
460 ////////////////////// TESTING FUNCTION ////////////////////////
461 /**
462 ** @desc: takes a wiki markup string, and tests it for security or validation problems.
463 */
464 function testWikiMarkup($raw_markup, $testname) {
465
466 // don't overwrite a previous test of the same name.
467 while (file_exists(DIRECTORY . "/" . $testname . ".raw_markup.txt")) {
468 $testname .= "-" . mt_rand(0,9);
469 }
470
471 // upload to MediaWiki install.
472 $wiki_preview = wikiPreview($raw_markup);
473
474 // save output files
475 saveFile($raw_markup, $testname . ".raw_markup.txt");
476 saveFile($wiki_preview, $testname . ".wiki_preview.html");
477
478 // validate result
479 $valid = true;
480 if (VALIDATE_ON_WEB) list ($valid, $validator_output) = validateHTML($wiki_preview);
481 $valid = $valid && checkOpenCloseTags ($wiki_preview, $testname . ".wiki_preview.html");
482 $valid = $valid && tidyCheckFile( $testname . ".wiki_preview.html" );
483
484
485 if( $valid ) {
486 // Remove valid tests:
487 unlink( DIRECTORY . "/" . $testname . ".raw_markup.txt" );
488 unlink( DIRECTORY . "/" . $testname . ".wiki_preview.html");
489 } elseif( VALIDATE_ON_WEB ) {
490 saveFile($validator_output, $testname . ".validator_output.html");
491 }
492 }
493
494
495 ////////////////////// MAIN LOOP ////////////////////////
496
497 // Make directory if doesn't exist
498 if (!is_dir(DIRECTORY)) {
499 mkdir (DIRECTORY, 0700 );
500 }
501 // otherwise, retest the things that we have found in previous runs
502 else {
503 print "Retesting previously found problems.\n";
504
505 // create a handler for the directory
506 $handler = opendir(DIRECTORY);
507
508 // keep going until all files in directory have been read
509 while ($file = readdir($handler)) {
510
511 // if file is not raw markup, or is a retest, then skip it.
512 if (!ereg("\.raw_markup.txt$", $file)) continue;
513 if ( ereg("^retest-", $file)) continue;
514
515 print "Retesting " . DIRECTORY . "/" . $file . "\n";
516
517 // get file contents
518 $markup = file_get_contents(DIRECTORY . "/" . $file);
519
520 // run retest
521 testWikiMarkup($markup, "retest-" . $file);
522 }
523
524 // tidy up: close the handler
525 closedir($handler);
526
527 print "Done retesting.\n";
528 }
529
530 // seed the random number generator
531 mt_srand(crc32(microtime()));
532
533 // main loop.
534 $h = new htmler();
535
536 print "Beginning main loop. Results are stored in the ".DIRECTORY." directory.\n";
537 print "Press CTRL+C to stop testing.\n";
538 for ($count=0; true /*$count<10000 */ ; $count++) { // while (true)
539 switch( $count % 4 ) {
540 case '0': print "\r/"; break;
541 case '1': print "\r-"; break;
542 case '2': print "\r\\"; break;
543 case '3': print "\r|"; break;
544 }
545 print " $count";
546
547 // generate and save text to test.
548 $raw_markup = $h->main();
549
550 // test this wiki markup
551 testWikiMarkup($raw_markup, $count);
552 }
553 ?>