PHPonTrax
[ class tree: PHPonTrax ] [ index: PHPonTrax ] [ all elements ]

Source for file input_filter.php

Documentation is available at input_filter.php

  1. <?php
  2. /**
  3.  *  File containing the InputFilter class
  4.  *
  5.  *  (PHP 5)
  6.  *
  7.  *  @package PHPonTrax
  8.  *  @version $Id: input_filter.php 245 2006-08-23 06:15:06Z john $
  9.  *  @author Daniel Morris
  10.  *   contributors: Gianpaolo Racca, Ghislain Picard, Marco Wandschneider,
  11.  *                 Chris Tobin and Andrew Eddie.
  12.  *  @copyright Daniel Morris <dan@rootcube.com>
  13.  *  @license http://opensource.org/licenses/gpl-license.php GNU Public License
  14.  */
  15.  
  16. /**
  17.  *  Filter user input to remove potential security threats
  18.  *
  19.  *  InputFilter has three public methods that are useful in protecting
  20.  *  a web site from potential security threats from user input.
  21.  *  <ul>
  22.  *    <li>{@link safeSQL()} protects SQL from the user.</li>
  23.  *    <li>{@link process()} protects HTML tags and attributes from the
  24.  *      user.</li>
  25.  *    <li>{@link process_all()} applies {@link process()} to all
  26.  *      possible sources of user input</li>
  27.  *  </ul>
  28.  *  For usage instructions see
  29.  *  {@tutorial PHPonTrax/InputFilter.cls the class tutorial}.
  30.  *  @todo Check FIXMEs
  31.  */
  32. class InputFilter {
  33.     
  34.     /**
  35.      *  User-provided list of tags to either accept or reject
  36.      *
  37.      *  Whether the tags in this list are accepted or rejected is
  38.      *  determined by the value of {@link $tagsMethod}.
  39.      *  @var string[] 
  40.      */
  41.     protected static $tagsArray array();    // default = empty array
  42.     
  43.     /**
  44.      *  User-provided list of attributes to either accept or reject
  45.      *
  46.      *  Whether the attributes in this list are accepted or rejected is
  47.      *  determined by the value of {@link $attrMethod}.
  48.      *  @var string[] 
  49.      */
  50.     protected static $attrArray array();    // default = empty array
  51.     
  52.     /**
  53.      *  How to apply user-provided tags list
  54.      *
  55.      *  Which method to use when applying the list of tags provided by
  56.      *  the user and stored in {@link $tagsArray}.
  57.      *  @var boolean Tested by {@link filterTags()} to see whether the
  58.      *                user-provide list of tags in {@link $tagsArray}
  59.      *                describes those tags which are forbidden, or
  60.      *                those tags which are permitted.  Default false.
  61.      *   <ul>
  62.      *     <li>true =>  Remove  those tags which are in
  63.      *                  {@link $tagsArray}.</li>
  64.      *     <li>false => Allow only those tags which are listed in
  65.      *                  {@link $tagsArray}.</li>
  66.      *   </ul>
  67.      */
  68.     protected static $tagsMethod true;
  69.     
  70.     /**
  71.      *  How to apply user-provided attribute list
  72.      *
  73.      *  Which method to use when applying the list of attributes
  74.      *  provided by the user and stored in {@link $attrArray}.
  75.      *  @var boolean Tested by {@link filterAttr()} to see whether the
  76.      *                user-provide list of tags in {@link $attrArray}
  77.      *                describes those tags which are forbidden, or
  78.      *                those tags which are permitted.  Default false.
  79.      *   <ul>
  80.      *     <li>true =>  Remove  those tags which are in
  81.      *                  {@link $attrArray}.</li>
  82.      *     <li>false => Allow only those tags which are listed in
  83.      *                  {@link $attrArray}.</li>
  84.      *   </ul>
  85.      */
  86.     protected static $attrMethod true;
  87.  
  88.     
  89.     /**
  90.      *  Whether to remove blacklisted tags and attributes
  91.      *
  92.      *  @var boolean Tested by {@link filterAttr()} and
  93.      *                {@link filterTags()} to see whether to remove
  94.      *                blacklisted tags and attributes.  Default true.
  95.      *   <ul>
  96.      *     <li>true => Remove tags in {@link $tagBlacklist} and
  97.      *                 attributes in {@link $attrBlacklist}, in
  98.      *                 addition to all other potentially suspect tags
  99.      *                 and attributes.</li>
  100.      *     <li>false => Remove potentially suspect tags and attributes
  101.      *       without consulting{@link $tagBlacklist} or
  102.      *       {@link $attrBlacklist}.</li>
  103.      *   </ul>
  104.      */
  105.     protected static $xssAuto true;
  106.  
  107.     /**
  108.      *  Fields to ignore that you want html and other banned stuff in.
  109.      *
  110.      *  @var array 
  111.      */    
  112.     protected static $exception_fields array();
  113.     
  114.     /**
  115.      *  List of tags to be removed
  116.      *
  117.      *  If {@link $xssAuto} is true, remove the tags in this list.
  118.      *  @var string[] 
  119.      */
  120.     protected static $tagBlacklist =
  121.         array('applet''body''bgsound''base''basefont''embed',
  122.               'frame''frameset''head''html''id''iframe',
  123.               'ilayer''layer''link''meta''name''object',
  124.               'script''style''title''xml');
  125.     
  126.     /**
  127.      *  List of attributes to be removed
  128.      *
  129.      *  If {@link $xssAuto} is true, remove the attributes in this list.
  130.      *  @var string[] 
  131.      */
  132.     protected static $attrBlacklist =
  133.         array('action''background''codebase''dynsrc''lowsrc')
  134.         
  135.     /** 
  136.      *  Initializer for InputFilter class.
  137.      *
  138.      *  @param string[] $tagsArray  User-provided list of tags to
  139.      *                               either accept or reject.  Default: none
  140.      *  @param string[] $attrArray  User-provided list of attributes to
  141.      *                               either accept or reject.  Default: none
  142.      *  @param boolean $tagsMethod How to apply the list of tags in $tagsArray:
  143.      *   <ul>
  144.      *     <li>true =>  Remove  those tags which are listed in
  145.      *                  $tagsArray.</li>
  146.      *     <li>false => Allow only those tags which are listed in
  147.      *                  $tagsArray.</li>
  148.      *   </ul>
  149.      *   Default: false
  150.      *  @param boolean $attrMethod How to apply the list of attributess in $attrArray:
  151.      *   <ul>
  152.      *     <li>true =>  Remove  those attributes which are listed in
  153.      *                  $attrArray.</li>
  154.      *     <li>false => Allow only those attributes which are listed in
  155.      *                  $attrArray.</li>
  156.      *   </ul>
  157.      *   Default: false
  158.      *  @param boolean $xssAuto Behavior of {@link filterTags()}:
  159.      *   <ul>
  160.      *     <li>true => Remove tags in {@link $tagBlacklist} and
  161.      *                 attributes in {@link $attrBlacklist}, in
  162.      *                 addition to all other potentially suspect tags
  163.      *                 and attributes.</li>
  164.      *     <li>false => Remove potentially suspect tags and attributes
  165.      *       without consulting{@link $tagBlacklist} or
  166.      *       {@link $attrBlacklist}.</li>
  167.      *   </ul>
  168.      *   Default: true
  169.      *  @uses $attrArray
  170.      *  @uses $attrMethod
  171.      *  @uses $tagsArray
  172.      *  @uses $tagsMethod
  173.      */
  174.     public function init($tagsArray array()$attrArray array(),
  175.                                 $tagsMethod true$attrMethod true,
  176.                                 $xssAuto true
  177.                                     
  178.         // make sure user defined arrays are in lowercase
  179.         for ($i 0$i count($tagsArray)$i++$tagsArray[$istrtolower($tagsArray[$i]);
  180.         for ($i 0$i count($attrArray)$i++$attrArray[$istrtolower($attrArray[$i]);
  181.         // assign to member vars
  182.         self::$tagsArray = (array) $tagsArray;
  183.         self::$attrArray = (array) $attrArray;
  184.         self::$tagsMethod $tagsMethod;
  185.         self::$attrMethod $attrMethod;
  186.         self::$xssAuto $xssAuto;
  187.     }
  188.  
  189.     /**
  190.      *  Adds a field to exclude from filtering
  191.      *
  192.      */    
  193.     public function add_field_exception($field{
  194.         if($field{
  195.             self::$exception_fields[$field;   
  196.         }
  197.     }
  198.  
  199.     /**
  200.      *  Clears all previous field exceptions
  201.      *
  202.      */        
  203.     public function clear_field_exceptions({
  204.         self::$exception_fields array();        
  205.     
  206.  
  207.     /**
  208.      *  Remove forbidden tags and attributes from user input
  209.      *
  210.      *  Construct an InputFilter object.  Then apply the
  211.      *  {@link process()} method to each of the user input arrays
  212.      *  {@link http://www.php.net/reserved.variables#reserved.variables.post $_POST},
  213.      *  {@link http://www.php.net/reserved.variables#reserved.variables.get $_GET} and
  214.      *  {@link http://www.php.net/reserved.variables#reserved.variables.request $_REQUEST}.
  215.      *  <b>FIXME:</b> isn't it partly redundant to do this to $_REQUEST?
  216.      *  Shouldn't we do it to $_COOKIE instead?
  217.      *  @param string[] $tagsArray  User-provided list of tags to
  218.      *                               either accept or reject.  Default: none
  219.      *  @param string[] $attrArray  User-provided list of attributes to
  220.      *                               either accept or reject.  Default: none
  221.      *  @param boolean $tagsMethod How to apply the list of tags in $tagsArray:
  222.      *   <ul>
  223.      *     <li>true =>  Remove  those tags which are listed in
  224.      *                  $tagsArray.</li>
  225.      *     <li>false => Allow only those tags which are listed in
  226.      *                  $tagsArray.</li>
  227.      *   </ul>
  228.      *   Default: false
  229.      *  @param boolean $attrMethod How to apply the list of attributess in $attrArray:
  230.      *   <ul>
  231.      *     <li>true =>  Remove  those attributes which are listed in
  232.      *                  $attrArray.</li>
  233.      *     <li>false => Allow only those attributes which are listed in
  234.      *                  $attrArray.</li>
  235.      *   </ul>
  236.      *   Default: false
  237.      *  @param boolean $xssAuto Behavior of {@link filterTags()}:
  238.      *   <ul>
  239.      *     <li>true => Remove tags in {@link $tagBlacklist} and
  240.      *                 attributes in {@link $attrBlacklist}, in
  241.      *                 addition to all other potentially suspect tags
  242.      *                 and attributes.</li>
  243.      *     <li>false => Remove potentially suspect tags and attributes
  244.      *       without consulting{@link $tagBlacklist} or
  245.      *       {@link $attrBlacklist}.</li>
  246.      *   </ul>
  247.      *   Default: true
  248.      *  @author John Peterson
  249.      *  @uses __construct()
  250.      *  @uses process()
  251.      *  @todo Check out FIXMEs
  252.      */
  253.     public function process_all($tagsArray array()$attrArray array(),
  254.                                 $tagsMethod true$attrMethod true,
  255.                                 $xssAuto true{
  256.         self::init($tagsArray$attrArray$tagsMethod,
  257.                           $attrMethod$xssAuto);
  258.         if(count($_POST)) {
  259.             $_POST self::process($_POST);
  260.         }
  261.         if(count($_GET)) {
  262.             $_GET self::process($_GET);
  263.         }
  264.         if(count($_REQUEST)) {
  265.             $_REQUEST self::process($_REQUEST);
  266.         }
  267.     }
  268.     
  269.     /** 
  270.      *  Remove forbidden tags and attributes from array of strings
  271.      *
  272.      *  Accept a string or array of strings.  For each string in the
  273.      *  source, remove the forbidden tags and attributes from the string.
  274.      *  @param mixed $source - input string/array-of-string to be 'cleaned'
  275.      *  @return mixed 'cleaned' version of input parameter
  276.      *  @uses decode()
  277.      *  @uses remove()
  278.      */
  279.     public function process($source$extra_key null{
  280.         // clean all elements in this array
  281.         if(is_array($source)) {
  282.             foreach($source as $key => $value{
  283.                 //error_log("key:".$extra_key.$key);
  284.                 if(in_array($extra_key.$keyself::$exception_fields)) $source[$key$valuecontinue}
  285.                 // for arrays in arrays
  286.                 if (is_array($value)) $source[$keyself::process($value$key.":");
  287.                 // filter element for XSS and other 'bad' code etc.
  288.                 if (is_string($value)) $source[$keyself::remove(self::decode($value));
  289.             }
  290.             return $source;
  291.         // clean this string
  292.         elseif(is_string($source)) {
  293.             // filter source for XSS and other 'bad' code etc.
  294.             return self::remove(self::decode($source));
  295.         // return parameter as given
  296.         else {
  297.             return $source;    
  298.         }
  299.     }
  300.  
  301.     /** 
  302.      *  Remove forbidden tags and attributes from a string iteratively
  303.      *
  304.      *  Call {@link filterTags()} repeatedly until no change in the
  305.      *  input is produced.
  306.      *  @param string $source Input string to be 'cleaned'
  307.      *  @return string 'cleaned' version of $source
  308.      *  @uses filterTags()
  309.      */
  310.     protected function remove($source{
  311.         // provides nested-tag protection
  312.         while($source != self::filterTags($source)) {
  313.             $source self::filterTags($source);
  314.         }
  315.         return $source;
  316.     }    
  317.     
  318.     /** 
  319.      *  Remove forbidden tags and attributes from a string
  320.      *
  321.      *  Inspect the input for tags "<tagname ...>" and check the tag
  322.      *  name against a list of forbidden tag names.  Delete all tags
  323.      *  with forbidden names.  If {@link $xssAuto} is true, delete all
  324.      *  tags in {@link $tagBlacklist}.  If there is a user-defined tag
  325.      *  list in {@link $tagsArray}, process according to the value of
  326.      *  {@link $tagsMethod}.
  327.      *
  328.      *  If the tag name is OK, then call {@link filterAttr()} to check
  329.      *  all attributes of the tag and delete forbidden attributes.
  330.      *  @param string $source Input string to be 'cleaned'
  331.      *  @return string Cleaned version of input parameter
  332.      *  @uses filterAttr()
  333.      *  @uses $tagBlacklist
  334.      *  @uses $tagsArray
  335.      *  @uses $tagsMethod
  336.      *  @uses $xssAuto
  337.      */
  338.     protected function filterTags($source{
  339.         // filter pass setup
  340.         $preTag null;
  341.         $postTag $source;
  342.         // find initial tag's position
  343.         $tagOpen_start strpos($source'<');
  344.         // interate through string until no tags left
  345.         while($tagOpen_start !== false{
  346.             // process tag interatively
  347.             $preTag .= substr($postTag0$tagOpen_start);
  348.             $postTag substr($postTag$tagOpen_start);
  349.             $fromTagOpen substr($postTag1);
  350.             // end of tag
  351.             $tagOpen_end strpos($fromTagOpen'>');
  352.             if ($tagOpen_end === falsebreak;
  353.             // next start of tag (for nested tag assessment)
  354.             $tagOpen_nested strpos($fromTagOpen'<');
  355.             if (($tagOpen_nested !== false&& ($tagOpen_nested $tagOpen_end)) {
  356.                 $preTag .= substr($postTag0($tagOpen_nested+1));
  357.                 $postTag substr($postTag($tagOpen_nested+1));
  358.                 $tagOpen_start strpos($postTag'<');
  359.                 continue;
  360.             
  361.             $tagOpen_nested (strpos($fromTagOpen'<'$tagOpen_start 1);
  362.             $currentTag substr($fromTagOpen0$tagOpen_end);
  363.             $tagLength strlen($currentTag);
  364.             if (!$tagOpen_end{
  365.                 $preTag .= $postTag;
  366.                 $tagOpen_start strpos($postTag'<');            
  367.             }
  368.             // iterate through tag finding attribute pairs - setup
  369.             $tagLeft $currentTag;
  370.             $attrSet array();
  371.             $currentSpace strpos($tagLeft' ');
  372.             // is end tag
  373.             if (substr($currentTag01== "/"{
  374.                 $isCloseTag true;
  375.                 list($tagNameexplode(' '$currentTag);
  376.                 $tagName substr($tagName1);
  377.             // is start tag
  378.             else {
  379.                 $isCloseTag false;
  380.                 list($tagNameexplode(' '$currentTag);
  381.             }        
  382.             // excludes all "non-regular" tagnames OR no tagname OR remove if xssauto is on and tag is blacklisted
  383.             if ((!preg_match("/^[a-z][a-z0-9]*$/i",$tagName)) || (!$tagName|| ((in_array(strtolower($tagName)self::$tagBlacklist)) && (self::$xssAuto))) {
  384.                 $postTag substr($postTag($tagLength 2));
  385.                 $tagOpen_start strpos($postTag'<');
  386.                 // don't append this tag
  387.                 continue;
  388.             }
  389.             // this while is needed to support attribute values with spaces in!
  390.             while ($currentSpace !== false{
  391.                 $fromSpace substr($tagLeft($currentSpace+1));
  392.                 $nextSpace strpos($fromSpace' ');
  393.                 $openQuotes strpos($fromSpace'"');
  394.                 $closeQuotes strpos(substr($fromSpace($openQuotes+1))'"'$openQuotes 1;
  395.                 // another equals exists
  396.                 if (strpos($fromSpace'='!== false{
  397.                     // opening and closing quotes exists
  398.                     if (($openQuotes !== false&& (strpos(substr($fromSpace($openQuotes+1))'"'!== false))
  399.                         $attr substr($fromSpace0($closeQuotes+1));
  400.                     // one or neither exist
  401.                     else $attr substr($fromSpace0$nextSpace);
  402.                 // no more equals exist
  403.                 else $attr substr($fromSpace0$nextSpace);
  404.                 // last attr pair
  405.                 if (!$attr$attr $fromSpace;
  406.                 // add to attribute pairs array
  407.                 $attrSet[$attr;
  408.                 // next inc
  409.                 $tagLeft substr($fromSpacestrlen($attr));
  410.                 $currentSpace strpos($tagLeft' ');
  411.             }
  412.             // appears in array specified by user
  413.             $tagFound in_array(strtolower($tagName)self::$tagsArray);
  414.             // remove this tag on condition
  415.             if ((!$tagFound && self::$tagsMethod|| ($tagFound && !self::$tagsMethod)) {
  416.                 // reconstruct tag with allowed attributes
  417.                 if (!$isCloseTag{
  418.                     $attrSet self::filterAttr($attrSet);
  419.                     $preTag .= '<' $tagName;
  420.                     for ($i 0$i count($attrSet)$i++)
  421.                         $preTag .= ' ' $attrSet[$i];
  422.                     // reformat single tags to XHTML
  423.                     if (strpos($fromTagOpen"</" $tagName)) $preTag .= '>';
  424.                     else $preTag .= ' />';
  425.                 // just the tagname
  426.                 else $preTag .= '</' $tagName '>';
  427.             }
  428.             // find next tag's start
  429.             $postTag substr($postTag($tagLength 2));
  430.             $tagOpen_start strpos($postTag'<');            
  431.         }
  432.         // append any code after end of tags
  433.         $preTag .= $postTag;
  434.         return $preTag;
  435.     }
  436.  
  437.     /** 
  438.      *  Internal method to strip a tag of certain attributes
  439.      *
  440.      *  Remove potentially dangerous attributes from a set of
  441.      *  "attr=value" strings.  Attributes considered dangerous are:
  442.      *  <ul>
  443.      *    <li>Any attribute name containing any non-alphabetic
  444.      *      character</li>
  445.      *    <li>Any attribute name beginning "on..."</li>
  446.      *    <li>If {@link $xssAuto} is true, any attribute name in
  447.      *      {@link $attrBlacklist}</li>
  448.      *    <li>Any attribute with a value containing the strings
  449.      *      'javascript:', 'behaviour:', 'vbscript:', 'mocha:',
  450.      *      'livescript:'</li>
  451.      *    <li>Any attribute whose name contains 'style' and whose
  452.      *      value contains 'expression'.</li>
  453.      *    <li>If there is a user-provided list of attributes in
  454.      *      {@link $attrArray}, process according to the value of
  455.      *      {@link $attrMethod}.</li>
  456.      *  </ul>
  457.      *  @param string[] $attrSet Array of strings "attr=value" parsed
  458.      *                            from a tag.
  459.      *  @return string[] Input with potentially dangerous attributes
  460.      *                    removed
  461.      *  @uses $attrArray
  462.      *  @uses $attrBlacklist
  463.      *  @uses $attrMethod
  464.      *  @uses $xssAuto
  465.      */
  466.     protected function filterAttr($attrSet{    
  467.         $newSet array();
  468.         // process attributes
  469.         for ($i 0$i <count($attrSet)$i++{
  470.             // skip blank spaces in tag
  471.             if (!$attrSet[$i]continue;
  472.             // split into attr name and value
  473.             $attrSubSet explode('='trim($attrSet[$i]));
  474.             list($attrSubSet[0]explode(' '$attrSubSet[0]);
  475.             // removes all "non-regular" attr names AND also attr blacklisted
  476.             if ((!eregi("^[a-z]*$",$attrSubSet[0])) || ((self::$xssAuto&& ((in_array(strtolower($attrSubSet[0])self::$attrBlacklist)) || (substr($attrSubSet[0]02== 'on'))))
  477.                 continue;
  478.             // xss attr value filtering
  479.             if ($attrSubSet[1|| is_numeric($attrSubSet[1])) {
  480.                 // strips unicode, hex, etc
  481.                 $attrSubSet[1str_replace('&#'''$attrSubSet[1]);
  482.                 // strip normal newline within attr value
  483.                 $attrSubSet[1preg_replace('/\s+/'''$attrSubSet[1]);
  484.                 // strip double quotes
  485.                 $attrSubSet[1str_replace('"'''$attrSubSet[1]);
  486.                 // [requested feature] convert single quotes from either side to doubles (Single quotes shouldn't be used to pad attr value)
  487.                 if ((substr($attrSubSet[1]01== "'"&& (substr($attrSubSet[1](strlen($attrSubSet[1]1)1== "'"))
  488.                     $attrSubSet[1substr($attrSubSet[1]1(strlen($attrSubSet[1]2));
  489.                 // strip slashes
  490.                 $attrSubSet[1stripslashes($attrSubSet[1]);
  491.             }
  492.             // auto strip attr's with "javascript:
  493.             if (((strpos(strtolower($attrSubSet[1])'expression'!== false&& 
  494.                 (strtolower($attrSubSet[0]== 'style')) ||
  495.                 (strpos(strtolower($attrSubSet[1])'javascript:'!== false||
  496.                 (strpos(strtolower($attrSubSet[1])'behaviour:'!== false||
  497.                 (strpos(strtolower($attrSubSet[1])'vbscript:'!== false||
  498.                 (strpos(strtolower($attrSubSet[1])'mocha:'!== false||
  499.                 (strpos(strtolower($attrSubSet[1])'livescript:'!== false
  500.             continue}
  501.  
  502.             // if matches user defined array
  503.             $attrFound in_array(strtolower($attrSubSet[0])self::$attrArray);
  504.             //error_log("attrFound:".($attrFound ? "Yes" : "No"));
  505.             // keep this attr on condition
  506.             if ((!$attrFound && self::$attrMethod|| ($attrFound && !self::$attrMethod)) {
  507.                 //error_log($attrSubSet[0]."=".$attrSubSet[1]);
  508.                 // attr has value
  509.                 if($attrSubSet[1]{
  510.                     $newSet[$attrSubSet[0'="' $attrSubSet[1'"';
  511.                 // attr has decimal zero as value
  512.                 elseif ($attrSubSet[1== "0"
  513.                     $newSet[$attrSubSet[0'="0"';
  514.                 // reformat single attributes to XHTML
  515.                 else {
  516.                     $newSet[$attrSubSet[0'="' $attrSubSet[0'"';
  517.                 }
  518.             }    
  519.         }
  520.         return $newSet;
  521.     }
  522.     
  523.     /** 
  524.      *  Convert HTML entities to characters
  525.      *
  526.      *  Convert input string containing HTML entities to the
  527.      *  corresponding character (&amp; => &).  ISO 8859-1 character
  528.      *  set is assumed.
  529.      *  @param string $source Character string containing HTML entities
  530.      *  @return string Input string, with entities converted to characters
  531.      *  @uses chr()
  532.      *  @uses html_entity_decode()
  533.      *  @uses preg_replace()
  534.      */
  535.     protected function decode($source{
  536.         // url decode
  537.         $source html_entity_decode($sourceENT_QUOTES"ISO-8859-1");
  538.         // convert decimal &#DDD; to character DDD
  539.         $source preg_replace('/&#(\d+);/me',"chr(\\1)"$source);
  540.         // convert hex &#xXXX; to character XXX
  541.         $source preg_replace('/&#x([a-f0-9]+);/mei',"chr(0x\\1)"$source);
  542.         return $source;
  543.     }
  544. }
  545.  
  546. // -- set Emacs parameters --
  547. // Local variables:
  548. // tab-width: 4
  549. // c-basic-offset: 4
  550. // c-hanging-comment-ender-p: nil
  551. // indent-tabs-mode: nil
  552. // End:
  553. ?>

Documentation generated on Mon, 21 May 2007 22:28:41 -0600 by phpDocumentor 1.3.2