[ Index ]
 

Code source de PHPonTrax 2.6.6-svn

Accédez au Source d'autres logiciels libresSoutenez Angelica Josefina !

title

Body

[fermer]

/vendor/trax/ -> input_filter.php (source)

   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[$i] = strtolower($tagsArray[$i]);
 180          for ($i = 0; $i < count($attrArray); $i++) $attrArray[$i] = strtolower($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.$key, self::$exception_fields)) { $source[$key] = $value; continue; }
 285                  // for arrays in arrays
 286                  if (is_array($value)) $source[$key] = self::process($value, $key.":");
 287                  // filter element for XSS and other 'bad' code etc.
 288                  if (is_string($value)) $source[$key] = self::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($postTag, 0, $tagOpen_start);
 348              $postTag = substr($postTag, $tagOpen_start);
 349              $fromTagOpen = substr($postTag, 1);
 350              // end of tag
 351              $tagOpen_end = strpos($fromTagOpen, '>');
 352              if ($tagOpen_end === false) break;
 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($postTag, 0, ($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($fromTagOpen, 0, $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($currentTag, 0, 1) == "/") {
 374                  $isCloseTag = true;
 375                  list($tagName) = explode(' ', $currentTag);
 376                  $tagName = substr($tagName, 1);
 377              // is start tag
 378              } else {
 379                  $isCloseTag = false;
 380                  list($tagName) = explode(' ', $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($fromSpace, 0, ($closeQuotes+1));
 400                      // one or neither exist
 401                      else $attr = substr($fromSpace, 0, $nextSpace);
 402                  // no more equals exist
 403                  } else $attr = substr($fromSpace, 0, $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($fromSpace, strlen($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], 0, 2) == 'on'))))
 477                  continue;
 478              // xss attr value filtering
 479              if ($attrSubSet[1] || is_numeric($attrSubSet[1])) {
 480                  // strips unicode, hex, etc
 481                  $attrSubSet[1] = str_replace('&#', '', $attrSubSet[1]);
 482                  // strip normal newline within attr value
 483                  $attrSubSet[1] = preg_replace('/\s+/', '', $attrSubSet[1]);
 484                  // strip double quotes
 485                  $attrSubSet[1] = str_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], 0, 1) == "'") && (substr($attrSubSet[1], (strlen($attrSubSet[1]) - 1), 1) == "'"))
 488                      $attrSubSet[1] = substr($attrSubSet[1], 1, (strlen($attrSubSet[1]) - 2));
 489                  // strip slashes
 490                  $attrSubSet[1] = stripslashes($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($source, ENT_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  ?>


Généré le : Sun Feb 25 20:04:38 2007 par Balluche grâce à PHPXref 0.7