b2evolution

Multilingual multiuser multiblog engine

b2evolution Technical Documentation (Version 2.4) [ class tree: evocore ] [ index: evocore ] [ all elements ]

Source for file _xhtml_validator.class.php

Documentation is available at _xhtml_validator.class.php

  1. <?php
  2. /**
  3.  * This file implements the XHTML_Validator class.
  4.  *
  5.  * Checks HTML against a subset of elements to ensure safety and XHTML validation.
  6.  *
  7.  * This file is part of the b2evolution/evocms project - {@link http://b2evolution.net/}.
  8.  * See also {@link http://sourceforge.net/projects/evocms/}.
  9.  *
  10.  * @copyright (c)2003-2008 by Francois PLANQUE - {@link http://fplanque.net/}.
  11.  *  Parts of this file are copyright (c)2003 by Nobuo SAKIYAMA - {@link http://www.sakichan.org/}
  12.  *  Parts of this file are copyright (c)2004-2005 by Daniel HAHLER - {@link http://thequod.de/contact}.
  13.  *
  14.  * @license http://b2evolution.net/about/license.html GNU General Public License (GPL)
  15.  *
  16.  *  {@internal Open Source relicensing agreement:
  17.  *  Daniel HAHLER grants Francois PLANQUE the right to license
  18.  *  Daniel HAHLER's contributions to this file and the b2evolution project
  19.  *  under any OSI approved OSS license (http://www.opensource.org/licenses/).
  20.  *  }}}
  21.  *
  22.  *  {@internal Origin:
  23.  *  This file was inspired by Simon Willison's SafeHtmlChecker released in
  24.  *  the public domain on 23rd Feb 2003.
  25.  *  {@link http://simon.incutio.com/code/php/SafeHtmlChecker.class.php.txt}
  26.  *  }}}
  27.  *
  28.  * @package evocore
  29.  *
  30.  *  {@internal Below is a list of authors who have contributed to design/coding of this file: }}
  31.  * @author blueyed: Daniel HAHLER.
  32.  * @author fplanque: Francois PLANQUE.
  33.  * @author sakichan: Nobuo SAKIYAMA.
  34.  * @author Simon Willison.
  35.  *
  36.  * @version $Id: _xhtml_validator.class.php,v 1.9 2008/01/21 09:35:37 fplanque Exp $
  37.  */
  38. if!defined('EVO_MAIN_INIT') ) die'Please, do not access this page directly.' );
  39.  
  40. /**
  41.  * XHTML_Validator
  42.  *
  43.  * checks HTML against a subset of elements to ensure safety and XHTML validation.
  44.  *
  45.  * @package evocore
  46.  */
  47. {
  48.     var $tags;      // Array showing allowed attributes for tags
  49.     var $tagattrs;  // Array showing URI attributes
  50.     var $uri_attrs;
  51.     var $allowed_uri_scheme;
  52.  
  53.     // Internal variables
  54.     var $parser;
  55.     var $stack = array();
  56.     var $last_checked_pos;
  57.     var $error;
  58.  
  59.     /**
  60.      * Constructor
  61.      *
  62.      * {@internal This gets tested in _libs.misc.simpletest.php}}
  63.      *
  64.      * @param string 
  65.      * @param string Input encoding to use ('ISO-8859-1', 'UTF-8', 'US-ASCII' or '' for auto-detect)
  66.      */
  67.     function XHTML_Validator$context 'posting'$allow_css_tweaks false$allow_iframes false$allow_javascript false$allow_objects false$encoding NULL$msg_type 'error' )
  68.     {
  69.         global $inc_path;
  70.  
  71.         require $inc_path.'xhtml_validator/_xhtml_dtd.inc.php';
  72.  
  73.         $this->context $context;
  74.  
  75.         switch$context )
  76.         {
  77.             case 'posting':
  78.             case 'xmlrpc_posting':
  79.                 $this->tags = $allowed_tags;
  80.                 $this->tagattrs = $allowed_attributes;
  81.                 break;
  82.  
  83.             case 'commenting':
  84.                 $this->tags = $comments_allowed_tags;
  85.                 $this->tagattrs = $comments_allowed_attributes;
  86.                 break;
  87.  
  88.             default:
  89.                 debug_die'unknown context: '.$context );
  90.         }
  91.  
  92.         // Attributes that need to be checked for a valid URI:
  93.         $this->uri_attrs = array
  94.         (
  95.             'xmlns',
  96.             'profile',
  97.             'href',
  98.             'src',
  99.             'cite',
  100.             'classid',
  101.             'codebase',
  102.             'data',
  103.             'archive',
  104.             'usemap',
  105.             'longdesc',
  106.             'action'
  107.         );
  108.  
  109.         $this->allowed_uri_scheme = get_allowed_uri_schemes$context );
  110.  
  111.         $this->msg_type $msg_type;
  112.  
  113.         ifempty($encoding) )
  114.         {
  115.             global $io_charset;
  116.             $encoding $io_charset;
  117.         }
  118.         $encoding strtoupper($encoding)// we might get 'iso-8859-1' for example
  119.         $this->encoding $encoding;
  120.         ifin_array$encodingarray'ISO-8859-1''UTF-8''US-ASCII' ) ) )
  121.         // passed encoding not supported by xml_parser_create()
  122.             $this->xml_parser_encoding ''// auto-detect (in PHP4, in PHP5 anyway)
  123.         }
  124.         else
  125.         {
  126.             $this->xml_parser_encoding $this->encoding;
  127.         }
  128.         $this->parser = xml_parser_create$this->xml_parser_encoding );
  129.  
  130.         $this->last_checked_pos = 0;
  131.         $this->error = false;
  132.  
  133.         // Creates the parser
  134.         xml_set_object$this->parser$this);
  135.  
  136.         // set functions to call when a start or end tag is encountered
  137.         xml_set_element_handler($this->parser'tag_open''tag_close');
  138.         // set function to call for the actual data
  139.         xml_set_character_data_handler($this->parser'cdata');
  140.  
  141.         xml_parser_set_option($this->parserXML_OPTION_CASE_FOLDINGfalse);
  142.     }
  143.  
  144.  
  145.     /**
  146.      * check(-)
  147.      */
  148.     function check($xhtml)
  149.     {
  150.         // Convert encoding:
  151.         // TODO: use convert_encoding()
  152.         ifempty($this->xml_parser_encoding|| $this->encoding != $this->xml_parser_encoding )
  153.         // we need to convert encoding:
  154.             iffunction_exists'mb_convert_encoding' ) )
  155.             // we can convert encoding to UTF-8
  156.                 $this->encoding 'UTF-8';
  157.  
  158.                 // Convert XHTML:
  159.                 $xhtml mb_convert_encoding$xhtml'UTF-8' );
  160.             }
  161.             elseif( ($this->encoding == 'ISO-8859-1' || empty($this->encoding)) && function_exists('utf8_encode') )
  162.             {
  163.                 $this->encoding 'UTF-8';
  164.  
  165.                 $xhtml utf8_encode$xhtml );
  166.             }
  167.         }
  168.  
  169.         // Open comments or '<![CDATA[' are dangerous
  170.         $xhtml str_replace('<!'''$xhtml);
  171.  
  172.         // Convert isolated & chars
  173.         $xhtml preg_replace'#(\s)&(\s)#''\\1&amp;\\2'$xhtml );
  174.  
  175.         $xhtml_head '<?xml version="1.0"';
  176.         ifempty($this->encoding) )
  177.         {
  178.             $xhtml_head .= ' encoding="'.$this->encoding.'"';
  179.         }
  180.  
  181.         $xhtml_head .= '?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"';
  182.  
  183.         // Include entities:
  184.         $xhtml_head .= '[';
  185.         // Include latin1 entities (http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent):
  186.         $xhtml_head .= file_get_contentsdirname(__FILE__).'/_xhtml-lat1.ent' );
  187.         // Include symbol entities (http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent):
  188.         $xhtml_head .= file_get_contentsdirname(__FILE__).'/_xhtml-symbol.ent' );
  189.         // Include special entities (http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent):
  190.         $xhtml_head .= file_get_contentsdirname(__FILE__).'/_xhtml-special.ent' );
  191.         $xhtml_head .= ']>';
  192.  
  193.         $xhtml $xhtml_head.'<body>'.$xhtml.'</body>';
  194.         unset($xhtml_head);
  195.  
  196.         if!xml_parse($this->parser$xhtml) )
  197.         {
  198.             $xml_error_code xml_get_error_code$this->parser );
  199.             $xml_error_string xml_error_string$xml_error_code );
  200.             switch$xml_error_code )
  201.             {
  202.                 case XML_ERROR_TAG_MISMATCH:
  203.                     $xml_error_string .= ': <code>'.$this->stack[count($this->stack)-1].'</code>';
  204.                     break;
  205.             }
  206.             $pos xml_get_current_byte_index($this->parser);
  207.             $xml_error_string .= ' near <code>'.htmlspecialcharssubstr$xhtml$this->last_checked_pos$pos-$this->last_checked_pos+20 ) ).'</code>';
  208.  
  209.             $this->html_errorT_('Parser error: ').$xml_error_string );
  210.         }
  211.  
  212.         return $this->isOK();
  213.     }
  214.  
  215.  
  216.     /**
  217.      * tag_open(-)
  218.      *
  219.      * Called when the parser finds an opening tag
  220.      */
  221.     function tag_open($parser$tag$attrs)
  222.     {
  223.         global $debug;
  224.  
  225.         // echo "processing tag: $tag <br />\n";
  226.         $this->last_checked_pos = xml_get_current_byte_index($this->parser);
  227.  
  228.         if ($tag == 'body')
  229.         {
  230.             ifcount($this->stack)
  231.                 $this->html_errorT_('Tag <code>body</code> can only be used once!') );
  232.             $this->stack[$tag;
  233.             return;
  234.         }
  235.         $previous $this->stack[count($this->stack)-1];
  236.  
  237.         // If previous tag is illegal, no point in running tests
  238.         if (!in_array($previousarray_keys($this->tags))) {
  239.             $this->stack[$tag;
  240.             return;
  241.         }
  242.         // Is tag a legal tag?
  243.         if (!in_array($tagarray_keys($this->tags))) {
  244.             $this->html_errorT_('Illegal tag')": <code>$tag</code>);
  245.             $this->stack[$tag;
  246.             return;
  247.         }
  248.         // Is tag allowed in the current context?
  249.         if (!in_array($tagexplode(' '$this->tags[$previous]))) {
  250.             if ($previous == 'body'{
  251.                 $this->html_error(    sprintfT_('Tag &lt;%s&gt; must occur inside another tag')'<code>'.$tag.'</code>' ) );
  252.             else {
  253.                 $this->html_error(    sprintfT_('Tag &lt;%s&gt; is not allowed within tag &lt;%s&gt;')'<code>'.$tag.'</code>''<code>'.$previous.'</code>') );
  254.             }
  255.         }
  256.         // Are tag attributes valid?
  257.         foreach$attrs as $attr => $value )
  258.         {
  259.             if (!isset($this->tagattrs[$tag]|| !in_array($attrexplode(' '$this->tagattrs[$tag])))
  260.             {
  261.                 $this->html_errorsprintfT_('Tag &lt;%s&gt; may not have attribute %s="..."')'<code>'.$tag.'</code>''<code>'.$attr.'</code>' ) );
  262.             }
  263.  
  264.             if (in_array($attr$this->uri_attrs))
  265.             // This attribute must be checked for URIs
  266.                 $matches array();
  267.                 $value trim($value);
  268.                 if$error validate_url$value$this->contextfalse ) ) //Note: We do not check for spam here, should be done on whole message in check_html_sanity()
  269.                 {
  270.                     $this->html_errorT_('Found invalid URL: ').$error );
  271.                 }
  272.             }
  273.  
  274.         }
  275.         // Set previous, used for checking nesting context rules
  276.         $this->stack[$tag;
  277.     }
  278.  
  279.     /**
  280.      * cdata(-)
  281.      */
  282.     function cdata($parser$cdata)
  283.     {
  284.         $this->last_checked_pos = xml_get_current_byte_index($this->parser);
  285.  
  286.         // Simply check that the 'previous' tag allows CDATA
  287.         $previous $this->stack[count($this->stack)-1];
  288.         // If previous tag is illegal, no point in running test
  289.         if (!in_array($previousarray_keys($this->tags))) {
  290.             return;
  291.         }
  292.         if (trim($cdata!= ''{
  293.             if (!in_array('#PCDATA'explode(' '$this->tags[$previous]))) {
  294.                 $this->html_error(    sprintfT_('Tag &lt;%s&gt; may not contain raw character data')'<code>'.$previous.'</code>' ) );
  295.             }
  296.         }
  297.     }
  298.  
  299.     /**
  300.      * tag_close(-)
  301.      */
  302.     function tag_close($parser$tag)
  303.     {
  304.         $this->last_checked_pos = xml_get_current_byte_index($this->parser);
  305.  
  306.         // Move back one up the stack
  307.         array_pop($this->stack);
  308.     }
  309.  
  310.     function html_error$string )
  311.     {
  312.         global $Messages;
  313.         $this->error = true;
  314.         $Messages->add$string$this->msg_type );
  315.     }
  316.  
  317.     /**
  318.      * isOK(-)
  319.      */
  320.     function isOK()
  321.     {
  322.         return $this->error;
  323.     }
  324.  
  325. }
  326.  
  327.  
  328. /*
  329.  * $Log: _xhtml_validator.class.php,v $
  330.  * Revision 1.9  2008/01/21 09:35:37  fplanque
  331.  * (c) 2008
  332.  *
  333.  * Revision 1.8  2008/01/20 18:20:27  fplanque
  334.  * Antispam per group setting
  335.  *
  336.  * Revision 1.7  2008/01/20 15:31:12  fplanque
  337.  * configurable validation/security rules
  338.  *
  339.  * Revision 1.6  2008/01/19 18:24:25  fplanque
  340.  * antispam checking refactored
  341.  *
  342.  * Revision 1.5  2008/01/19 15:45:28  fplanque
  343.  * refactoring
  344.  *
  345.  * Revision 1.4  2008/01/19 10:57:11  fplanque
  346.  * Splitting XHTML checking by group and interface
  347.  *
  348.  * Revision 1.3  2008/01/18 15:53:42  fplanque
  349.  * Ninja refactoring
  350.  *
  351.  * Revision 1.2  2007/09/13 02:37:22  fplanque
  352.  * special cases
  353.  *
  354.  * Revision 1.1  2007/06/25 11:02:27  fplanque
  355.  * MODULES (refactored MVC)
  356.  *
  357.  * Revision 1.13  2007/04/26 00:11:07  fplanque
  358.  * (c) 2007
  359.  *
  360.  * Revision 1.12  2006/11/27 02:29:53  blueyed
  361.  * Committed test changes by accident. Test added for it as an exercise.
  362.  *
  363.  * Revision 1.11  2006/11/26 02:30:39  fplanque
  364.  * doc / todo
  365.  *
  366.  * Revision 1.10  2006/11/06 22:56:53  blueyed
  367.  * Added full(?) XHTML entities support to the html checker
  368.  *
  369.  * Revision 1.9  2006/11/04 21:44:59  blueyed
  370.  * Include latin1 entities to let xml_parse() not choke on those
  371.  */
  372. ?>

Documentation generated on Sat, 06 Mar 2010 03:42:54 +0100 by phpDocumentor 1.4.2. This site is hosted and maintained by Daniel HAHLER (Contact).