b2evolution

Multilingual multiuser multiblog engine

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

Source for file _htmlchecker.class.php

Documentation is available at _htmlchecker.class.php

  1. <?php
  2. /**
  3.  * This file implements the SafeHtmlChecker 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-2006 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: _htmlchecker.class.php,v 1.8.2.4 2007/01/21 20:17:17 fplanque Exp $
  37.  */
  38. if!defined('EVO_MAIN_INIT') ) die'Please, do not access this page directly.' );
  39.  
  40. /**
  41.  * SafeHtmlChecker
  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 array 
  65.      * @param array 
  66.      * @param array 
  67.      * @param array 
  68.      * @param string Input encoding to use ('ISO-8859-1', 'UTF-8', 'US-ASCII' or '' for auto-detect)
  69.      */
  70.     function SafeHtmlChecker$allowed_tags$allowed_attributes$uri_attrs$allowed_uri_scheme$encoding '' )
  71.     {
  72.         $this->tags = $allowed_tags;
  73.         $this->tagattrs = $allowed_attributes;
  74.         $this->uri_attrs = $uri_attrs;
  75.         $this->allowed_uri_scheme = $allowed_uri_scheme;
  76.  
  77.  
  78.         $encoding strtoupper($encoding)// we might get 'iso-8859-1' for example
  79.         $this->encoding $encoding;
  80.         ifin_array$encodingarray'ISO-8859-1''UTF-8''US-ASCII' ) ) )
  81.         // passed encoding not supported by xml_parser_create()
  82.             $this->xml_parser_encoding ''// auto-detect (in PHP4, in PHP5 anyway)
  83.         }
  84.         else
  85.         {
  86.             $this->xml_parser_encoding $this->encoding;
  87.         }
  88.         $this->parser = xml_parser_create$this->xml_parser_encoding );
  89.  
  90.         $this->last_checked_pos = 0;
  91.         $this->error = false;
  92.  
  93.         // Creates the parser
  94.         xml_set_object$this->parser$this);
  95.  
  96.         // set functions to call when a start or end tag is encountered
  97.         xml_set_element_handler($this->parser'tag_open''tag_close');
  98.         // set function to call for the actual data
  99.         xml_set_character_data_handler($this->parser'cdata');
  100.  
  101.         xml_set_default_handler($this->parser'default_handler');
  102.         xml_set_external_entity_ref_handler($this->parser'external_entity');
  103.         xml_set_unparsed_entity_decl_handler($this->parser'unparsed_entity');
  104.  
  105.         xml_parser_set_option($this->parserXML_OPTION_CASE_FOLDINGfalse);
  106.     }
  107.  
  108.     function default_handler$parser$data)
  109.     {
  110.         // echo 'default handler: '.$data.'<br />';
  111.     }
  112.  
  113.     function external_entity$parser$open_entity_names$base$system_id$public_id)
  114.     {
  115.         // echo 'external_entity<br />';
  116.     }
  117.  
  118.  
  119.     function unparsed_entity$parser$entity_name$base$system_id$public_id$notation_name)
  120.     {
  121.         // echo 'unparsed_entity<br />';
  122.     }
  123.  
  124.  
  125.     /**
  126.      * check(-)
  127.      */
  128.     function check($xhtml)
  129.     {
  130.         // Convert encoding:
  131.         ifempty($this->xml_parser_encoding|| $this->encoding != $this->xml_parser_encoding )
  132.         // we need to convert encoding:
  133.             iffunction_exists'mb_convert_encoding' ) )
  134.             // we can convert encoding to UTF-8
  135.                 $this->encoding 'UTF-8';
  136.  
  137.                 // Convert XHTML:
  138.                 $xhtml mb_convert_encoding$xhtml'UTF-8' );
  139.             }
  140.             elseif( ($this->encoding == 'ISO-8859-1' || empty($this->encoding)) && function_exists('utf8_encode') )
  141.             {
  142.                 $this->encoding 'UTF-8';
  143.  
  144.                 $xhtml utf8_encode$xhtml );
  145.             }
  146.         }
  147.  
  148.         // Open comments or '<![CDATA[' are dangerous
  149.         $xhtml str_replace('<!'''$xhtml);
  150.  
  151.         // Convert isolated & chars
  152.         $xhtml preg_replace'#(\s)&(\s)#''\\1&amp;\\2'$xhtml );
  153.  
  154.         $xhtml_head '<?xml version="1.0"';
  155.         ifempty($this->encoding) )
  156.         {
  157.             $xhtml_head .= ' encoding="'.$this->encoding.'"';
  158.         }
  159.         $xhtml_head .= '?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">';
  160.  
  161.         $xhtml $xhtml_head.'<body>'.$xhtml.'</body>';
  162.  
  163.         if!xml_parse($this->parser$xhtml) )
  164.         {
  165.             $xml_error_code xml_get_error_code$this->parser );
  166.             $xml_error_string xml_error_string$xml_error_code );
  167.             switch$xml_error_code )
  168.             {
  169.                 case XML_ERROR_TAG_MISMATCH:
  170.                     $xml_error_string .= ': <code>'.$this->stack[count($this->stack)-1].'</code>';
  171.                     break;
  172.             }
  173.             $pos xml_get_current_byte_index($this->parser);
  174.             $xml_error_string .= ' near <code>'.htmlspecialcharssubstr$xhtml$this->last_checked_pos$pos-$this->last_checked_pos+20 ) ).'</code>';
  175.  
  176.             $this->html_errorT_('Parser error: ').$xml_error_string );
  177.         }
  178.     }
  179.  
  180.     /**
  181.      * tag_open(-)
  182.      *
  183.      * Called when the parser finds an opening tag
  184.      */
  185.     function tag_open($parser$tag$attrs)
  186.     {
  187.         // echo "processing tag: $tag <br />\n";
  188.         $this->last_checked_pos = xml_get_current_byte_index($this->parser);
  189.  
  190.         if ($tag == 'body')
  191.         {
  192.             ifcount($this->stack)
  193.                 $this->html_errorT_('Tag <code>body</code> can only be used once!') );
  194.             $this->stack[$tag;
  195.             return;
  196.         }
  197.         $previous $this->stack[count($this->stack)-1];
  198.  
  199.         // If previous tag is illegal, no point in running tests
  200.         if (!in_array($previousarray_keys($this->tags))) {
  201.             $this->stack[$tag;
  202.             return;
  203.         }
  204.         // Is tag a legal tag?
  205.         if (!in_array($tagarray_keys($this->tags))) {
  206.             $this->html_errorT_('Illegal tag')": <code>$tag</code>);
  207.             $this->stack[$tag;
  208.             return;
  209.         }
  210.         // Is tag allowed in the current context?
  211.         if (!in_array($tagexplode(' '$this->tags[$previous]))) {
  212.             if ($previous == 'body'{
  213.                 $this->html_error(    sprintfT_('Tag &lt;%s&gt; must occur inside another tag')'<code>'.$tag.'</code>' ) );
  214.             else {
  215.                 $this->html_error(    sprintfT_('Tag &lt;%s&gt; is not allowed within tag &lt;%s&gt;')'<code>'.$tag.'</code>''<code>'.$previous.'</code>') );
  216.             }
  217.         }
  218.         // Are tag attributes valid?
  219.         foreach$attrs as $attr => $value )
  220.         {
  221.             if (!isset($this->tagattrs[$tag]|| !in_array($attrexplode(' '$this->tagattrs[$tag])))
  222.             {
  223.                 $this->html_errorsprintfT_('Tag &lt;%s&gt; may not have attribute %s')'<code>'.$tag.'</code>''<code>'.$attr.'</code>' ) );
  224.             }
  225.             if (in_array($attr$this->uri_attrs))
  226.             // Must this attribute be checked for URIs
  227.                 $matches array();
  228.                 $value trim($value);
  229.                 if$error validate_url$value$this->allowed_uri_scheme ) )
  230.                 {
  231.                     $this->html_errorT_('Found invalid URL: ').$error );
  232.                 }
  233.             }
  234.         }
  235.         // Set previous, used for checking nesting context rules
  236.         $this->stack[$tag;
  237.     }
  238.  
  239.     /**
  240.      * cdata(-)
  241.      */
  242.     function cdata($parser$cdata)
  243.     {
  244.         $this->last_checked_pos = xml_get_current_byte_index($this->parser);
  245.  
  246.         // Simply check that the 'previous' tag allows CDATA
  247.         $previous $this->stack[count($this->stack)-1];
  248.         // If previous tag is illegal, no point in running test
  249.         if (!in_array($previousarray_keys($this->tags))) {
  250.             return;
  251.         }
  252.         if (trim($cdata!= ''{
  253.             if (!in_array('#PCDATA'explode(' '$this->tags[$previous]))) {
  254.                 $this->html_error(    sprintfT_('Tag &lt;%s&gt; may not contain raw character data')'<code>'.$previous.'</code>' ) );
  255.             }
  256.         }
  257.     }
  258.  
  259.     /**
  260.      * tag_close(-)
  261.      */
  262.     function tag_close($parser$tag)
  263.     {
  264.         $this->last_checked_pos = xml_get_current_byte_index($this->parser);
  265.  
  266.         // Move back one up the stack
  267.         array_pop($this->stack);
  268.     }
  269.  
  270.     function html_error$string )
  271.     {
  272.         global $Messages;
  273.         $this->error = true;
  274.         $Messages->add$string'error' );
  275.     }
  276.  
  277.     /**
  278.      * isOK(-)
  279.      */
  280.     function isOK()
  281.     {
  282.         return $this->error;
  283.     }
  284.  
  285. }
  286.  
  287.  
  288. /*
  289.  * $Log: _htmlchecker.class.php,v $
  290.  * Revision 1.8.2.4  2007/01/21 20:17:17  fplanque
  291.  * rollback. too complex change for 1.9. move to 1.10.
  292.  *
  293.  * Revision 1.8.2.2  2006/11/04 19:55:04  fplanque
  294.  * Reinjected old Log blocks. Removing them from CVS was a bad idea -- especially since Daniel has decided branch 1.9 was his HEAD...
  295.  *
  296.  * Revision 1.8  2006/07/04 17:32:30  fplanque
  297.  * no message
  298.  *
  299.  * Revision 1.7  2006/05/17 09:56:56  blueyed
  300.  * Handle default (empty) "encoding" better
  301.  *
  302.  * Revision 1.6  2006/05/02 22:19:27  blueyed
  303.  * whitespace
  304.  *
  305.  * Revision 1.5  2006/04/28 18:07:20  blueyed
  306.  * Simplified, removed PHP5 dependency
  307.  *
  308.  * Revision 1.4  2006/04/28 16:04:27  blueyed
  309.  * Fixed encoding for SafeHtmlChecker; added tests
  310.  *
  311.  * Revision 1.3  2006/03/20 00:25:45  blueyed
  312.  * fix
  313.  *
  314.  * Revision 1.2  2006/03/12 23:09:01  fplanque
  315.  * doc cleanup
  316.  *
  317.  * Revision 1.1  2006/02/23 21:12:18  fplanque
  318.  * File reorganization to MVC (Model View Controller) architecture.
  319.  * See index.hml files in folders.
  320.  * (Sorry for all the remaining bugs induced by the reorg... :/)
  321.  *
  322.  * Revision 1.9  2006/01/16 00:35:12  blueyed
  323.  * Fallback to UTF-8 encoding for not-supported encodings.
  324.  *
  325.  * Revision 1.8  2005/12/12 19:21:22  fplanque
  326.  * big merge; lots of small mods; hope I didn't make to many mistakes :]
  327.  *
  328.  * Revision 1.7  2005/10/09 19:31:15  blueyed
  329.  * Spelling (*allowed_attribues => *allowed_attributes)
  330.  *
  331.  * Revision 1.6  2005/09/06 17:13:55  fplanque
  332.  * stop processing early if referer spam has been detected
  333.  *
  334.  * Revision 1.5  2005/06/03 15:12:33  fplanque
  335.  * error/info message cleanup
  336.  *
  337.  * Revision 1.4  2005/02/28 09:06:33  blueyed
  338.  * removed constants for DB config (allows to override it from _config_TEST.php), introduced EVO_CONFIG_LOADED
  339.  *
  340.  * Revision 1.3  2004/11/15 18:57:05  fplanque
  341.  * cosmetics
  342.  *
  343.  * Revision 1.2  2004/10/14 18:31:25  blueyed
  344.  * granting copyright
  345.  *
  346.  * Revision 1.1  2004/10/13 22:46:32  fplanque
  347.  * renamed [b2]evocore/*
  348.  *
  349.  * Revision 1.13  2004/10/12 16:12:17  fplanque
  350.  * Edited code documentation.
  351.  *
  352.  */
  353. ?>

Documentation generated on Tue, 18 Dec 2007 19:18:09 +0100 by phpDocumentor 1.4.0