1<?php 2/** 3* Plugin Skeleton: Displays "Hello World!" 4* 5* @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6* @author Christopher Smith <chris@jalakai.co.uk> 7*/ 8 9if (!defined('DOKU_INC')) define('DOKU_INC', realpath(dirname(__FILE__) . '/../../') . '/'); 10if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC . 'lib/plugins/'); 11require_once(DOKU_PLUGIN . 'syntax.php'); 12require_once(DOKU_INC . 'inc/cache.php'); 13 14define('HTML_OK_INVALIDID', 0); 15define('HTML_OK_NOTSUPPORTED', 1); 16define('HTML_OK_EXCLUDEDWINDOW', 2); 17define('HTML_OK_BADCLASSNAME', 3); 18define('HTML_OK_BADSELECTOR', 4); 19define('HTML_OK_BADFNNAME', 5); 20// ini_set('display_errors', "on"); 21// ini_set('error_reporting', E_ALL); 22/** 23* All DokuWiki plugins to extend the parser/rendering mechanism 24* need to inherit from this class 25*/ 26class syntax_plugin_htmlOKay extends DokuWiki_Syntax_Plugin 27{ 28 // $access_levels = array('none'=>0, 'strict'=>1, 'medium'=>2, 'lax'=>3, 'su'=>4)); 29 var $access_level = 0; 30 var $client; 31 var $msgs; 32 var $htmlOK_errors; 33 var $cycle = 0; 34 var $open_div = 0; 35 var $closed_div = 0; 36 var $divs_reported = false; 37 var $JS_ErrString = ""; 38 var $helper; 39 40 function __construct() 41 { 42 43 $this->htmlOK_errors = array('Invalid ID', 'Element or Attribute not supported', 44 'Internal Window Elements Not Supported', 'Invalid CSS Class name(s)', 45 'ID Selectors not supported', 'Invalid Javascript function name(s)'); 46 47 $this->msgs = ""; 48 49 } 50 51 52 /** 53 * What kind of syntax are we? 54 */ 55 function getType() 56 { 57 return 'protected'; 58 } 59 60 /** 61 * What about paragraphs? (optional) 62 */ 63 function getPType() 64 { 65 return 'block'; 66 } 67 68 /** 69 * Where to sort in? 70 */ 71 function getSort() 72 { 73 return 180; 74 } 75 76 /** 77 * Connect pattern to lexer 78 * $this->Lexer->addPattern('<(?i)\w+\s+.*?ID\s*=.*?>','plugin_htmlOKay'); 79 * $this->Lexer->addPattern('<(?i)\w+\s+.*?CLASS\s*=.*?>','plugin_htmlOKay'); 80 */ 81 function connectTo($mode) 82 { 83 84 $this->cycle++; 85 86 $this->Lexer->addEntryPattern('<html>(?=.*?</html>)', $mode, 'plugin_htmlOKay'); 87 $this->Lexer->addPattern('.<(?i)IFRAME.*?/IFRAME\s*>', 'plugin_htmlOKay'); 88 $this->Lexer->addPattern('.<(?i)ILAYER.*?/ILAYER\s*>', 'plugin_htmlOKay'); 89 $this->Lexer->addPattern('<(?i)a.*?javascript.*?</a\s*>', 'plugin_htmlOKay'); 90 $this->Lexer->addPattern('<(?i)FORM.*?</FORM\s*>', 'plugin_htmlOKay'); 91 $this->Lexer->addPattern('<(?i)DIV.*?>', 'plugin_htmlOKay'); 92 $this->Lexer->addPattern('<(?i)/DIV.*?>', 'plugin_htmlOKay'); 93 $this->Lexer->addPattern('.<(?i)STYLE.*?/STYLE\s*>', 'plugin_htmlOKay'); 94 $this->Lexer->addPattern('<(?i)SCRIPT.*?/script\s*>', 'plugin_htmlOKay'); 95 $this->Lexer->addPattern('<(?i)TABLE.*?</TABLE\s*>', 'plugin_htmlOKay'); 96 97 $this->Lexer->addPattern('(?i)ID\s*=\s*\W?.*?\W', 'plugin_htmlOKay'); 98 $this->Lexer->addPattern('(?i)class\s*=\s*\W?.*?\W', 'plugin_htmlOKay'); 99 } 100 101 function postConnect() 102 { 103 $this->Lexer->addExitPattern('</html>', 'plugin_htmlOKay'); 104 } 105 106 /** 107 * level 2 permissions: guarded access 108 */ 109 function rewrite_match_medium($match) 110 { 111 if (preg_match('/<FORM(.*?)>/i', $match, $matches)) 112 { 113 if (preg_match('/(action)/i', $matches[1], $action)) 114 { 115 return $this->getError(HTML_OK_NOTSUPPORTED, $match, $action[1]); 116 } elseif (preg_match('/(onsubmit)/i', $matches[1], $onsubmit)) 117 { 118 return $this->getError(HTML_OK_NOTSUPPORTED, $match, $onsubmit[1]); 119 } 120 } 121 elseif (preg_match('/<script/i', $match)) 122 { 123 return $this->script_matches($match, 2); 124 } 125 elseif (preg_match('/<IFRAME/i', $match, $matches)) 126 { 127 return $this->getError(HTML_OK_EXCLUDEDWINDOW, $match, "IFRAME"); 128 } 129 elseif (preg_match('/<ILAYER/i', $match, $matches)) 130 { 131 return $this->getError(HTML_OK_EXCLUDEDWINDOW, $match, "ILAYER"); 132 } 133 elseif (preg_match('/<(DIV)/i', $match, $matches)) 134 { 135 $this->open_div++; 136 return $this->getError(HTML_OK_NOTSUPPORTED, $match, $matches[1], "div"); 137 } 138 elseif (preg_match('/<a.*?javascript.*?<\/a\s*>/i', $match)) 139 { 140 return $this->getError(HTML_OK_NOTSUPPORTED, $match, "javascript urls"); 141 } 142 elseif (preg_match('/<STYLE/i', $match)) 143 { 144 return $this->style_matches($match, 2); 145 } 146 elseif (preg_match('/<TABLE/i', $match, $matches)) 147 { 148 return $this->getError(HTML_OK_NOTSUPPORTED, $match, "TABLE"); 149 } 150 151 $retv = $this->class_id_matches($match); 152 if ($retv !== false) return $retv; 153 154 return $match; 155 } 156 157 /** 158 * treat level 1 permissions: restricted access 159 */ 160 161 function rewrite_match_strict($match) 162 { 163 if (preg_match('/<FORM/i', $match)) 164 { 165 return $this->getError(HTML_OK_NOTSUPPORTED, $match, 'FORM'); 166 167 } elseif (preg_match('/<script/i', $match)) 168 { 169 return $this->getError(HTML_OK_NOTSUPPORTED, $match, 'SCRIPT'); 170 171 } elseif (preg_match('/<IFRAME/i', $match, $matches)) 172 { 173 return $this->getError(HTML_OK_EXCLUDEDWINDOW, $match, "IFRAME"); 174 } 175 elseif (preg_match('/<ILAYER/i', $match, $matches)) 176 { 177 return $this->getError(HTML_OK_EXCLUDEDWINDOW, $match, "ILAYER"); 178 179 } elseif (preg_match('/<(DIV)/i', $match, $matches)) 180 { 181 $this->open_div++; 182 return $this->getError(HTML_OK_NOTSUPPORTED, $match, $matches[1], "div"); 183 184 } elseif (preg_match('/<TABLE/i', $match, $matches)) 185 { 186 return $this->getError(HTML_OK_NOTSUPPORTED, $match, "TABLE"); 187 188 } elseif (preg_match('/<STYLE/i', $match)) 189 { 190 return $this->getError(HTML_OK_NOTSUPPORTED, $match, "STYLE"); 191 192 } elseif (preg_match('/<a.*?javascript.*?<\/a\s*>/i', $match)) 193 { 194 return $this->getError(HTML_OK_NOTSUPPORTED, $match, "javascript urls"); 195 } 196 197 $retv = $this->class_id_matches($match); 198 if ($retv !== false) return $retv; 199 200 return $match; 201 } 202 203 function rewrite_match_lax($match) 204 { 205 $div = false; 206 if (preg_match('/<FORM(.*?)>/i', $match, $matches)) 207 { 208 if (preg_match('/action/i', $matches[1])) 209 { 210 return $this->getError(HTML_OK_NOTSUPPORTED, $match, $matches[1], "form"); 211 } 212 213 } elseif (preg_match('/<STYLE/i', $match)) 214 { 215 $match = $this->style_matches($match, 3); 216 return $match; 217 218 } elseif (preg_match('/<(IFRAME|ILAYER)/i', $match, $matches)) 219 { 220 return $this->getError(HTML_OK_EXCLUDEDWINDOW, $match, $matches[1], ""); 221 222 } elseif (preg_match('/<script/i', $match)) 223 { 224 return $this->script_matches($match, 3); 225 226 } elseif (preg_match('/<(DIV)/i', $match)) 227 { 228 $div = true; 229 $this->open_div++; 230 } 231 232 $retv = $this->class_id_matches($match, $div); 233 if ($retv !== false) return $retv; 234 235 return $match; 236 } 237 238 /** 239 * Super-user access 240 */ 241 function rewrite_match_su($match) 242 { 243 global $conf; 244 if($conf['plugin']['htmlOKay']['su_unrestricted']) 245 return $match; 246 247 $div = false; 248 if (preg_match('/<STYLE/i', $match)) 249 { 250 $match = $this->style_matches($match, 4); 251 return $match; 252 } elseif (preg_match('/<(DIV)/i', $match)) 253 { 254 $div = true; 255 $this->open_div++; 256 } 257 258 $retv = $this->class_id_matches($match, $div); 259 if ($retv !== false) return $retv; 260 261 return $match; 262 } 263 264 function script_matches($match, $level) 265 { 266 if ($level < 3) 267 { 268 if (preg_match('/\blocation\b/', $match)) 269 { 270 return $this->getError(HTML_OK_NOTSUPPORTED, $match, "location"); 271 } 272 273 if (preg_match('/(ActiveX|XMLHttpRequest)/i', $match, $matches)) 274 { 275 return $this->getError(HTML_OK_NOTSUPPORTED, $match, $matches[1]); 276 } 277 278 if (preg_match('/(onsubmit|addEventListener|createEvent|attachEvent|captureEvents)/i', $match, $matches)) 279 { 280 return $this->getError(HTML_OK_NOTSUPPORTED, $match, $matches[1]); 281 } 282 } 283 284 if (preg_match_all('/function\s+(.*?)[\s\n]*\(/i', $match, $matches)) 285 { 286 $err = array(); 287 foreach($matches[1] as $index => $m) 288 { 289 if(!preg_match('/^htmlO_K_/', $m)) { 290 $err[] = $m; 291 } 292 } 293 if (count($err)) 294 { 295 return $this->getError(HTML_OK_BADFNNAME, $match, $err); 296 } 297 } 298 299 return $match; 300 } 301 302 function class_id_matches($match, $div = "") 303 { 304 if (preg_match('/id\s*=\s*\W?(.*)/i', $match, $matches)) 305 { 306 if (isset($matches[1]) && !preg_match('/^htmlO_K_/', $matches[1])) 307 { 308 if ($div) $div = 'div'; 309 $value = rtrim($matches[1], ' ">'); 310 return $this->getError(HTML_OK_INVALIDID, $match, $value, $div); 311 } 312 } 313 314 if (preg_match('/class\s*=\s*\W?(.*)/i', $match, $matches)) 315 { 316 if (isset($matches[1]) && !preg_match('/^htmlO_K_/', $matches[1])) 317 { 318 if ($div) $div = 'div'; 319 $value = rtrim($matches[1], ' "'); 320 return $this->getError(HTML_OK_INVALIDID, $match, $value, $div); 321 } 322 } 323 324 return false; 325 } 326 327 328 /** 329 * 330 * level 2: no use of #id's, check all for class name errors, missing htmlO_K_ 331 */ 332 function style_matches($match, $level) 333 { 334 if (!isset($match)) return ""; 335 // medium: no use of id's 336 if ($level == 2 && preg_match_all('/(#\w+)/', $match, $matches)) 337 { 338 $err = array(); 339 foreach($matches[1] as $index => $m) 340 { 341 $err[] = $m; 342 } 343 if (count($err)) 344 { 345 return $this->getError(HTML_OK_BADSELECTOR, $match, $err); 346 } 347 } 348 349 if (preg_match_all('/\.(\w+)/', $match, $matches)) 350 { 351 $err = array(); 352 foreach($matches[1] as $index => $m) 353 { 354 if (!preg_match('/^htmlO_K_/', $m)) 355 { 356 $err[] = $m; 357 } 358 } 359 if (count($err)) 360 { 361 return $this->getError(HTML_OK_BADCLASSNAME, $match, $err); 362 } 363 } 364 return $match; 365 } 366 367 /** 368 * Handle the match 369 */ 370 function handle($match, $state, $pos, Doku_Handler $handler) 371 { 372 global $conf; 373 $this->pos = $pos; 374 375 $this->helper = plugin_load('helper', 'htmlOKay'); 376 $this->helper->set_permissions(); 377 $this->access_level = $this->helper->get_access(); 378 379 if (!$conf['htmlok']) 380 { 381 $match = preg_replace ('/</', '<', $match) . '<br />'; 382 return array($state, $match); 383 } 384 elseif($this->JS_ErrString) { 385 echo $this->JS_ErrString; 386 $this->JS_ErrString = false; 387 } 388 389 switch ($state) 390 { 391 case DOKU_LEXER_ENTER : 392 return array($state, $match); 393 break; 394 395 case DOKU_LEXER_MATCHED : 396 if (preg_match('/\/DIV\s*>/i', $match)) 397 { 398 $this->closed_div++; 399 } 400 if ($this->access_level == 1) 401 { 402 $match = $this->rewrite_match_strict($match); 403 } elseif ($this->access_level == 2) 404 { 405 $match = $this->rewrite_match_medium($match); 406 } elseif ($this->access_level == 3) 407 { 408 $match = $this->rewrite_match_lax($match); 409 } elseif ($this->access_level == 4) 410 { 411 $match = $this->rewrite_match_su($match); 412 } 413 414 return array($state, $match); 415 break; 416 417 case DOKU_LEXER_UNMATCHED : 418 if (preg_match('/<h(\d)>.*?<\/\\1>/i', $match, $matches)) 419 { 420 $match = preg_replace('/<h\d>/', '<h' . $matches[1] . ' style="border-bottom:0px;">', $match); 421 422 } 423 424 return array($state, $match); 425 break; 426 427 case DOKU_LEXER_EXIT : 428 if ($this->open_div != $this->closed_div) 429 { 430 if (!$this->divs_reported) 431 // echo $this->get_JSErrString("<b>Mismatched Div Elements:</b> Open Divs: {$this->open_div}; Closed Divs: {$this->closed_div}"); 432 $this->divs_reported = true; 433 } 434 435 return array($state, $match); 436 break; 437 438 case DOKU_LEXER_SPECIAL : 439 return array($state, $match); 440 441 break; 442 } 443 return array(); 444 } 445 446 /** 447 * Create output 448 */ 449 450 function render($mode, Doku_Renderer $renderer, $data) 451 { 452 if ($mode == 'xhtml') 453 { 454 list($state, $match) = $data; 455 switch ($state) 456 { 457 case DOKU_LEXER_ENTER : 458 break; 459 case DOKU_LEXER_UNMATCHED : 460 $renderer->doc .= $match; 461 break; 462 case DOKU_LEXER_MATCHED : 463 $renderer->doc .= $match; 464 break; 465 case DOKU_LEXER_EXIT : 466 467 break; 468 } 469 470 return true; 471 } 472 if($mode = 'metadata') 473 { // msg('<pre>'. print_r($renderer->meta,1) . '</pre>' ); 474 $renderer->meta['relation']['htmlokay'] = time(); 475 return true; 476 } 477 478 return false; 479 } 480 481 /* 482 * $htmlOK_ERRORS = array('Invalid ID', 'Element not supported', 'Internal Window Elements Not Supported'); 483 * 484 */ 485 486 function getError($TYPE, $match, $problem_str, $xtra = "") 487 { 488 $error_string = ""; 489 490 if ($xtra) $xtra = "<{$xtra}>"; 491 if ($TYPE == HTML_OK_INVALIDID) 492 { 493 $xtra = ">{$xtra}"; 494 } 495 496 $error_string = "{$xtra}<center><dl style='border-width:0px 0px 0px 0px; border-color: #ffffff; '><DT><DD><TABLE WIDTH='80%' cellpadding='10' border>\n" 497 . "<TR><TD align='center' style='background-color:#eeeeee;font-weight:normal; font-size: 10pt; font-family:sans-serif;'>\n"; 498 499 if (is_string($problem_str)) 500 { 501 $error_string .= compact_string(preg_replace ('/</', '<', $match)) . '<br />'; 502 $problem_str = htmlspecialchars ($problem_str, ENT_QUOTES); 503 } 504 505 switch ($TYPE) 506 { 507 case HTML_OK_INVALIDID: 508 $error_string .= $this->htmlOK_errors[$TYPE] . ": <b>{$problem_str}</b><br />"; 509 $js = $this->htmlOK_errors[$TYPE] . '.' 510 . " htmlO_K_ prefix required for all ID's: <b>htmlO_K_{$problem_str}</b>"; 511 $error_string .= $this->get_JSErrString($js); 512 break; 513 514 case HTML_OK_NOTSUPPORTED: 515 $error_string .= $this->htmlOK_errors[$TYPE] . ": <b>{$problem_str}</b><br />"; 516 $js = $this->htmlOK_errors[$TYPE] . " at current HTML access level: <b>{$problem_str}</b>"; 517 $error_string .= $this->get_JSErrString($js); 518 break; 519 520 case HTML_OK_EXCLUDEDWINDOW: 521 $error_string .= $this->htmlOK_errors[$TYPE] . ": <b>{$problem_str}</b><br />"; 522 $js = $this->htmlOK_errors[$TYPE] . ". External files cannot be included in wiki documents: <b>{$problem_str}</b>. "; 523 $error_string .= $this->get_JSErrString($js); 524 break; 525 526 case HTML_OK_BADCLASSNAME: 527 case HTML_OK_BADSELECTOR: 528 case HTML_OK_BADFNNAME: 529 $error_string .= $this->htmlOK_errors[$TYPE] . ':<BR />'; 530 $name_errs = ""; 531 foreach($problem_str as $p) 532 { 533 $p = htmlspecialchars ($p, ENT_QUOTES); 534 $name_errs .= " {$p}, "; 535 } 536 537 $name_errs = rtrim($name_errs, ' ,'); 538 $name_errs = "<b>$name_errs</b>"; 539 $error_string .= $name_errs; 540 if ($TYPE == HTML_OK_BADCLASSNAME) 541 { 542 $js = $this->htmlOK_errors[$TYPE] . ". htmlO_K_ prefix required for class names: <b>{$name_errs}</b>. "; 543 } elseif ($TYPE == HTML_OK_BADFNNAME) 544 { 545 $js = $this->htmlOK_errors[$TYPE] . ". htmlO_K_ prefix required for function names:<BR /> <b>{$name_errs}</b>. "; 546 } 547 else 548 { 549 $js = $this->htmlOK_errors[$TYPE] . " at current HTML access level: <b>{$name_errs}</b>. "; 550 } 551 $error_string .= $this->get_JSErrString($js); 552 break; 553 554 default: 555 break; 556 } 557 558 return $error_string . '</TR></TD></TABLE></dl></center><br />'; 559 } 560 561 /** 562 * Constructs the Javascript Error String for output in the Errors window 563 */ 564 function get_JSErrString($msg) 565 { 566 global $INFO; 567 $msg = trim($msg); 568 static $msgs_inx = -1; 569 570 if (!isset($msg) || empty($msg)) return ";"; 571 $msgs_inx++; 572 $msg = '<script language="javascript">htmlOK_ERRORS_ARRAY[' . $msgs_inx . ']="' . $msg . '"; </script>' . "\n"; 573 return $msg; 574 } 575} 576 577function compact_string($string_x) 578{ 579 if ($len = strlen($string_x) > 400) 580 { 581 582 $string_a = substr($string_x, 0, 200); 583 $string_b = substr($string_x, -200); 584 $string_x = $string_a . '<br /><b>. . .</b><br />' . $string_b; 585 } 586 587 return $string_x; 588} 589 590?> 591