[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/vendor/simplepie/simplepie/src/ -> Sanitize.php (source)

   1  <?php
   2  
   3  declare(strict_types=1);
   4  /**
   5   * SimplePie
   6   *
   7   * A PHP-Based RSS and Atom Feed Framework.
   8   * Takes the hard work out of managing a complete RSS/Atom solution.
   9   *
  10   * Copyright (c) 2004-2022, Ryan Parman, Sam Sneddon, Ryan McCue, and contributors
  11   * All rights reserved.
  12   *
  13   * Redistribution and use in source and binary forms, with or without modification, are
  14   * permitted provided that the following conditions are met:
  15   *
  16   *     * Redistributions of source code must retain the above copyright notice, this list of
  17   *       conditions and the following disclaimer.
  18   *
  19   *     * Redistributions in binary form must reproduce the above copyright notice, this list
  20   *       of conditions and the following disclaimer in the documentation and/or other materials
  21   *       provided with the distribution.
  22   *
  23   *     * Neither the name of the SimplePie Team nor the names of its contributors may be used
  24   *       to endorse or promote products derived from this software without specific prior
  25   *       written permission.
  26   *
  27   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
  28   * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
  29   * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
  30   * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  31   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  32   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  33   * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
  34   * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  35   * POSSIBILITY OF SUCH DAMAGE.
  36   *
  37   * @package SimplePie
  38   * @copyright 2004-2016 Ryan Parman, Sam Sneddon, Ryan McCue
  39   * @author Ryan Parman
  40   * @author Sam Sneddon
  41   * @author Ryan McCue
  42   * @link http://simplepie.org/ SimplePie
  43   * @license http://www.opensource.org/licenses/bsd-license.php BSD License
  44   */
  45  
  46  namespace SimplePie;
  47  
  48  use InvalidArgumentException;
  49  use SimplePie\Cache\Base;
  50  use SimplePie\Cache\BaseDataCache;
  51  use SimplePie\Cache\CallableNameFilter;
  52  use SimplePie\Cache\DataCache;
  53  use SimplePie\Cache\NameFilter;
  54  
  55  /**
  56   * Used for data cleanup and post-processing
  57   *
  58   *
  59   * This class can be overloaded with {@see \SimplePie\SimplePie::set_sanitize_class()}
  60   *
  61   * @package SimplePie
  62   * @todo Move to using an actual HTML parser (this will allow tags to be properly stripped, and to switch between HTML and XHTML), this will also make it easier to shorten a string while preserving HTML tags
  63   */
  64  class Sanitize implements RegistryAware
  65  {
  66      // Private vars
  67      public $base;
  68  
  69      // Options
  70      public $remove_div = true;
  71      public $image_handler = '';
  72      public $strip_htmltags = ['base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style'];
  73      public $encode_instead_of_strip = false;
  74      public $strip_attributes = ['bgsound', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc'];
  75      public $rename_attributes = [];
  76      public $add_attributes = ['audio' => ['preload' => 'none'], 'iframe' => ['sandbox' => 'allow-scripts allow-same-origin'], 'video' => ['preload' => 'none']];
  77      public $strip_comments = false;
  78      public $output_encoding = 'UTF-8';
  79      public $enable_cache = true;
  80      public $cache_location = './cache';
  81      public $cache_name_function = 'md5';
  82  
  83      /**
  84       * @var NameFilter
  85       */
  86      private $cache_namefilter;
  87      public $timeout = 10;
  88      public $useragent = '';
  89      public $force_fsockopen = false;
  90      public $replace_url_attributes = null;
  91      public $registry;
  92  
  93      /**
  94       * @var DataCache|null
  95       */
  96      private $cache = null;
  97  
  98      /**
  99       * @var int Cache duration (in seconds)
 100       */
 101      private $cache_duration = 3600;
 102  
 103      /**
 104       * List of domains for which to force HTTPS.
 105       * @see \SimplePie\Sanitize::set_https_domains()
 106       * Array is a tree split at DNS levels. Example:
 107       * array('biz' => true, 'com' => array('example' => true), 'net' => array('example' => array('www' => true)))
 108       */
 109      public $https_domains = [];
 110  
 111      public function __construct()
 112      {
 113          // Set defaults
 114          $this->set_url_replacements(null);
 115      }
 116  
 117      public function remove_div($enable = true)
 118      {
 119          $this->remove_div = (bool) $enable;
 120      }
 121  
 122      public function set_image_handler($page = false)
 123      {
 124          if ($page) {
 125              $this->image_handler = (string) $page;
 126          } else {
 127              $this->image_handler = false;
 128          }
 129      }
 130  
 131      public function set_registry(\SimplePie\Registry $registry)/* : void */
 132      {
 133          $this->registry = $registry;
 134      }
 135  
 136      public function pass_cache_data($enable_cache = true, $cache_location = './cache', $cache_name_function = 'md5', $cache_class = 'SimplePie\Cache', DataCache $cache = null)
 137      {
 138          if (isset($enable_cache)) {
 139              $this->enable_cache = (bool) $enable_cache;
 140          }
 141  
 142          if ($cache_location) {
 143              $this->cache_location = (string) $cache_location;
 144          }
 145  
 146          if (! is_string($cache_name_function) && ! is_object($cache_name_function) && ! $cache_name_function instanceof NameFilter) {
 147              throw new InvalidArgumentException(sprintf(
 148                  '%s(): Argument #3 ($cache_name_function) must be of type %s',
 149                  __METHOD__,
 150                  NameFilter::class
 151              ), 1);
 152          }
 153  
 154          // BC: $cache_name_function could be a callable as string
 155          if (is_string($cache_name_function)) {
 156              // trigger_error(sprintf('Providing $cache_name_function as string in "%s()" is deprecated since SimplePie 1.8.0, provide as "%s" instead.', __METHOD__, NameFilter::class), \E_USER_DEPRECATED);
 157              $this->cache_name_function = (string) $cache_name_function;
 158  
 159              $cache_name_function = new CallableNameFilter($cache_name_function);
 160          }
 161  
 162          $this->cache_namefilter = $cache_name_function;
 163  
 164          if ($cache !== null) {
 165              $this->cache = $cache;
 166          }
 167      }
 168  
 169      public function pass_file_data($file_class = 'SimplePie\File', $timeout = 10, $useragent = '', $force_fsockopen = false)
 170      {
 171          if ($timeout) {
 172              $this->timeout = (string) $timeout;
 173          }
 174  
 175          if ($useragent) {
 176              $this->useragent = (string) $useragent;
 177          }
 178  
 179          if ($force_fsockopen) {
 180              $this->force_fsockopen = (string) $force_fsockopen;
 181          }
 182      }
 183  
 184      public function strip_htmltags($tags = ['base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style'])
 185      {
 186          if ($tags) {
 187              if (is_array($tags)) {
 188                  $this->strip_htmltags = $tags;
 189              } else {
 190                  $this->strip_htmltags = explode(',', $tags);
 191              }
 192          } else {
 193              $this->strip_htmltags = false;
 194          }
 195      }
 196  
 197      public function encode_instead_of_strip($encode = false)
 198      {
 199          $this->encode_instead_of_strip = (bool) $encode;
 200      }
 201  
 202      public function rename_attributes($attribs = [])
 203      {
 204          if ($attribs) {
 205              if (is_array($attribs)) {
 206                  $this->rename_attributes = $attribs;
 207              } else {
 208                  $this->rename_attributes = explode(',', $attribs);
 209              }
 210          } else {
 211              $this->rename_attributes = false;
 212          }
 213      }
 214  
 215      public function strip_attributes($attribs = ['bgsound', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc'])
 216      {
 217          if ($attribs) {
 218              if (is_array($attribs)) {
 219                  $this->strip_attributes = $attribs;
 220              } else {
 221                  $this->strip_attributes = explode(',', $attribs);
 222              }
 223          } else {
 224              $this->strip_attributes = false;
 225          }
 226      }
 227  
 228      public function add_attributes($attribs = ['audio' => ['preload' => 'none'], 'iframe' => ['sandbox' => 'allow-scripts allow-same-origin'], 'video' => ['preload' => 'none']])
 229      {
 230          if ($attribs) {
 231              if (is_array($attribs)) {
 232                  $this->add_attributes = $attribs;
 233              } else {
 234                  $this->add_attributes = explode(',', $attribs);
 235              }
 236          } else {
 237              $this->add_attributes = false;
 238          }
 239      }
 240  
 241      public function strip_comments($strip = false)
 242      {
 243          $this->strip_comments = (bool) $strip;
 244      }
 245  
 246      public function set_output_encoding($encoding = 'UTF-8')
 247      {
 248          $this->output_encoding = (string) $encoding;
 249      }
 250  
 251      /**
 252       * Set element/attribute key/value pairs of HTML attributes
 253       * containing URLs that need to be resolved relative to the feed
 254       *
 255       * Defaults to |a|@href, |area|@href, |audio|@src, |blockquote|@cite,
 256       * |del|@cite, |form|@action, |img|@longdesc, |img|@src, |input|@src,
 257       * |ins|@cite, |q|@cite, |source|@src, |video|@src
 258       *
 259       * @since 1.0
 260       * @param array|null $element_attribute Element/attribute key/value pairs, null for default
 261       */
 262      public function set_url_replacements($element_attribute = null)
 263      {
 264          if ($element_attribute === null) {
 265              $element_attribute = [
 266                  'a' => 'href',
 267                  'area' => 'href',
 268                  'audio' => 'src',
 269                  'blockquote' => 'cite',
 270                  'del' => 'cite',
 271                  'form' => 'action',
 272                  'img' => [
 273                      'longdesc',
 274                      'src'
 275                  ],
 276                  'input' => 'src',
 277                  'ins' => 'cite',
 278                  'q' => 'cite',
 279                  'source' => 'src',
 280                  'video' => [
 281                      'poster',
 282                      'src'
 283                  ]
 284              ];
 285          }
 286          $this->replace_url_attributes = (array) $element_attribute;
 287      }
 288  
 289      /**
 290       * Set the list of domains for which to force HTTPS.
 291       * @see \SimplePie\Misc::https_url()
 292       * Example array('biz', 'example.com', 'example.org', 'www.example.net');
 293       */
 294      public function set_https_domains($domains)
 295      {
 296          $this->https_domains = [];
 297          foreach ($domains as $domain) {
 298              $domain = trim($domain, ". \t\n\r\0\x0B");
 299              $segments = array_reverse(explode('.', $domain));
 300              $node =& $this->https_domains;
 301              foreach ($segments as $segment) {//Build a tree
 302                  if ($node === true) {
 303                      break;
 304                  }
 305                  if (!isset($node[$segment])) {
 306                      $node[$segment] = [];
 307                  }
 308                  $node =& $node[$segment];
 309              }
 310              $node = true;
 311          }
 312      }
 313  
 314      /**
 315       * Check if the domain is in the list of forced HTTPS.
 316       */
 317      protected function is_https_domain($domain)
 318      {
 319          $domain = trim($domain, '. ');
 320          $segments = array_reverse(explode('.', $domain));
 321          $node =& $this->https_domains;
 322          foreach ($segments as $segment) {//Explore the tree
 323              if (isset($node[$segment])) {
 324                  $node =& $node[$segment];
 325              } else {
 326                  break;
 327              }
 328          }
 329          return $node === true;
 330      }
 331  
 332      /**
 333       * Force HTTPS for selected Web sites.
 334       */
 335      public function https_url($url)
 336      {
 337          return (strtolower(substr($url, 0, 7)) === 'http://') &&
 338              $this->is_https_domain(parse_url($url, PHP_URL_HOST)) ?
 339              substr_replace($url, 's', 4, 0) : //Add the 's' to HTTPS
 340              $url;
 341      }
 342  
 343      public function sanitize($data, $type, $base = '')
 344      {
 345          $data = trim($data);
 346          if ($data !== '' || $type & \SimplePie\SimplePie::CONSTRUCT_IRI) {
 347              if ($type & \SimplePie\SimplePie::CONSTRUCT_MAYBE_HTML) {
 348                  if (preg_match('/(&(#(x[0-9a-fA-F]+|[0-9]+)|[a-zA-Z0-9]+)|<\/[A-Za-z][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E]*' . \SimplePie\SimplePie::PCRE_HTML_ATTRIBUTE . '>)/', $data)) {
 349                      $type |= \SimplePie\SimplePie::CONSTRUCT_HTML;
 350                  } else {
 351                      $type |= \SimplePie\SimplePie::CONSTRUCT_TEXT;
 352                  }
 353              }
 354  
 355              if ($type & \SimplePie\SimplePie::CONSTRUCT_BASE64) {
 356                  $data = base64_decode($data);
 357              }
 358  
 359              if ($type & (\SimplePie\SimplePie::CONSTRUCT_HTML | \SimplePie\SimplePie::CONSTRUCT_XHTML)) {
 360                  if (!class_exists('DOMDocument')) {
 361                      throw new \SimplePie\Exception('DOMDocument not found, unable to use sanitizer');
 362                  }
 363                  $document = new \DOMDocument();
 364                  $document->encoding = 'UTF-8';
 365  
 366                  $data = $this->preprocess($data, $type);
 367  
 368                  set_error_handler(['SimplePie\Misc', 'silence_errors']);
 369                  $document->loadHTML($data);
 370                  restore_error_handler();
 371  
 372                  $xpath = new \DOMXPath($document);
 373  
 374                  // Strip comments
 375                  if ($this->strip_comments) {
 376                      $comments = $xpath->query('//comment()');
 377  
 378                      foreach ($comments as $comment) {
 379                          $comment->parentNode->removeChild($comment);
 380                      }
 381                  }
 382  
 383                  // Strip out HTML tags and attributes that might cause various security problems.
 384                  // Based on recommendations by Mark Pilgrim at:
 385                  // http://diveintomark.org/archives/2003/06/12/how_to_consume_rss_safely
 386                  if ($this->strip_htmltags) {
 387                      foreach ($this->strip_htmltags as $tag) {
 388                          $this->strip_tag($tag, $document, $xpath, $type);
 389                      }
 390                  }
 391  
 392                  if ($this->rename_attributes) {
 393                      foreach ($this->rename_attributes as $attrib) {
 394                          $this->rename_attr($attrib, $xpath);
 395                      }
 396                  }
 397  
 398                  if ($this->strip_attributes) {
 399                      foreach ($this->strip_attributes as $attrib) {
 400                          $this->strip_attr($attrib, $xpath);
 401                      }
 402                  }
 403  
 404                  if ($this->add_attributes) {
 405                      foreach ($this->add_attributes as $tag => $valuePairs) {
 406                          $this->add_attr($tag, $valuePairs, $document);
 407                      }
 408                  }
 409  
 410                  // Replace relative URLs
 411                  $this->base = $base;
 412                  foreach ($this->replace_url_attributes as $element => $attributes) {
 413                      $this->replace_urls($document, $element, $attributes);
 414                  }
 415  
 416                  // If image handling (caching, etc.) is enabled, cache and rewrite all the image tags.
 417                  if (isset($this->image_handler) && ((string) $this->image_handler) !== '' && $this->enable_cache) {
 418                      $images = $document->getElementsByTagName('img');
 419  
 420                      foreach ($images as $img) {
 421                          if ($img->hasAttribute('src')) {
 422                              $image_url = $this->cache_namefilter->filter($img->getAttribute('src'));
 423                              $cache = $this->get_cache($image_url);
 424  
 425                              if ($cache->get_data($image_url, false)) {
 426                                  $img->setAttribute('src', $this->image_handler . $image_url);
 427                              } else {
 428                                  $file = $this->registry->create(File::class, [$img->getAttribute('src'), $this->timeout, 5, ['X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']], $this->useragent, $this->force_fsockopen]);
 429                                  $headers = $file->headers;
 430  
 431                                  if ($file->success && ($file->method & \SimplePie\SimplePie::FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300))) {
 432                                      if ($cache->set_data($image_url, ['headers' => $file->headers, 'body' => $file->body], $this->cache_duration)) {
 433                                          $img->setAttribute('src', $this->image_handler . $image_url);
 434                                      } else {
 435                                          trigger_error("$this->cache_location is not writable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING);
 436                                      }
 437                                  }
 438                              }
 439                          }
 440                      }
 441                  }
 442  
 443                  // Get content node
 444                  $div = $document->getElementsByTagName('body')->item(0)->firstChild;
 445                  // Finally, convert to a HTML string
 446                  $data = trim($document->saveHTML($div));
 447  
 448                  if ($this->remove_div) {
 449                      $data = preg_replace('/^<div' . \SimplePie\SimplePie::PCRE_XML_ATTRIBUTE . '>/', '', $data);
 450                      $data = preg_replace('/<\/div>$/', '', $data);
 451                  } else {
 452                      $data = preg_replace('/^<div' . \SimplePie\SimplePie::PCRE_XML_ATTRIBUTE . '>/', '<div>', $data);
 453                  }
 454  
 455                  $data = str_replace('</source>', '', $data);
 456              }
 457  
 458              if ($type & \SimplePie\SimplePie::CONSTRUCT_IRI) {
 459                  $absolute = $this->registry->call(Misc::class, 'absolutize_url', [$data, $base]);
 460                  if ($absolute !== false) {
 461                      $data = $absolute;
 462                  }
 463              }
 464  
 465              if ($type & (\SimplePie\SimplePie::CONSTRUCT_TEXT | \SimplePie\SimplePie::CONSTRUCT_IRI)) {
 466                  $data = htmlspecialchars($data, ENT_COMPAT, 'UTF-8');
 467              }
 468  
 469              if ($this->output_encoding !== 'UTF-8') {
 470                  $data = $this->registry->call(Misc::class, 'change_encoding', [$data, 'UTF-8', $this->output_encoding]);
 471              }
 472          }
 473          return $data;
 474      }
 475  
 476      protected function preprocess($html, $type)
 477      {
 478          $ret = '';
 479          $html = preg_replace('%</?(?:html|body)[^>]*?'.'>%is', '', $html);
 480          if ($type & ~\SimplePie\SimplePie::CONSTRUCT_XHTML) {
 481              // Atom XHTML constructs are wrapped with a div by default
 482              // Note: No protection if $html contains a stray </div>!
 483              $html = '<div>' . $html . '</div>';
 484              $ret .= '<!DOCTYPE html>';
 485              $content_type = 'text/html';
 486          } else {
 487              $ret .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
 488              $content_type = 'application/xhtml+xml';
 489          }
 490  
 491          $ret .= '<html><head>';
 492          $ret .= '<meta http-equiv="Content-Type" content="' . $content_type . '; charset=utf-8" />';
 493          $ret .= '</head><body>' . $html . '</body></html>';
 494          return $ret;
 495      }
 496  
 497      public function replace_urls($document, $tag, $attributes)
 498      {
 499          if (!is_array($attributes)) {
 500              $attributes = [$attributes];
 501          }
 502  
 503          if (!is_array($this->strip_htmltags) || !in_array($tag, $this->strip_htmltags)) {
 504              $elements = $document->getElementsByTagName($tag);
 505              foreach ($elements as $element) {
 506                  foreach ($attributes as $attribute) {
 507                      if ($element->hasAttribute($attribute)) {
 508                          $value = $this->registry->call(Misc::class, 'absolutize_url', [$element->getAttribute($attribute), $this->base]);
 509                          if ($value !== false) {
 510                              $value = $this->https_url($value);
 511                              $element->setAttribute($attribute, $value);
 512                          }
 513                      }
 514                  }
 515              }
 516          }
 517      }
 518  
 519      public function do_strip_htmltags($match)
 520      {
 521          if ($this->encode_instead_of_strip) {
 522              if (isset($match[4]) && !in_array(strtolower($match[1]), ['script', 'style'])) {
 523                  $match[1] = htmlspecialchars($match[1], ENT_COMPAT, 'UTF-8');
 524                  $match[2] = htmlspecialchars($match[2], ENT_COMPAT, 'UTF-8');
 525                  return "&lt;$match[1]$match[2]&gt;$match[3]&lt;/$match[1]&gt;";
 526              } else {
 527                  return htmlspecialchars($match[0], ENT_COMPAT, 'UTF-8');
 528              }
 529          } elseif (isset($match[4]) && !in_array(strtolower($match[1]), ['script', 'style'])) {
 530              return $match[4];
 531          } else {
 532              return '';
 533          }
 534      }
 535  
 536      protected function strip_tag($tag, $document, $xpath, $type)
 537      {
 538          $elements = $xpath->query('body//' . $tag);
 539          if ($this->encode_instead_of_strip) {
 540              foreach ($elements as $element) {
 541                  $fragment = $document->createDocumentFragment();
 542  
 543                  // For elements which aren't script or style, include the tag itself
 544                  if (!in_array($tag, ['script', 'style'])) {
 545                      $text = '<' . $tag;
 546                      if ($element->hasAttributes()) {
 547                          $attrs = [];
 548                          foreach ($element->attributes as $name => $attr) {
 549                              $value = $attr->value;
 550  
 551                              // In XHTML, empty values should never exist, so we repeat the value
 552                              if (empty($value) && ($type & \SimplePie\SimplePie::CONSTRUCT_XHTML)) {
 553                                  $value = $name;
 554                              }
 555                              // For HTML, empty is fine
 556                              elseif (empty($value) && ($type & \SimplePie\SimplePie::CONSTRUCT_HTML)) {
 557                                  $attrs[] = $name;
 558                                  continue;
 559                              }
 560  
 561                              // Standard attribute text
 562                              $attrs[] = $name . '="' . $attr->value . '"';
 563                          }
 564                          $text .= ' ' . implode(' ', $attrs);
 565                      }
 566                      $text .= '>';
 567                      $fragment->appendChild(new \DOMText($text));
 568                  }
 569  
 570                  $number = $element->childNodes->length;
 571                  for ($i = $number; $i > 0; $i--) {
 572                      $child = $element->childNodes->item(0);
 573                      $fragment->appendChild($child);
 574                  }
 575  
 576                  if (!in_array($tag, ['script', 'style'])) {
 577                      $fragment->appendChild(new \DOMText('</' . $tag . '>'));
 578                  }
 579  
 580                  $element->parentNode->replaceChild($fragment, $element);
 581              }
 582  
 583              return;
 584          } elseif (in_array($tag, ['script', 'style'])) {
 585              foreach ($elements as $element) {
 586                  $element->parentNode->removeChild($element);
 587              }
 588  
 589              return;
 590          } else {
 591              foreach ($elements as $element) {
 592                  $fragment = $document->createDocumentFragment();
 593                  $number = $element->childNodes->length;
 594                  for ($i = $number; $i > 0; $i--) {
 595                      $child = $element->childNodes->item(0);
 596                      $fragment->appendChild($child);
 597                  }
 598  
 599                  $element->parentNode->replaceChild($fragment, $element);
 600              }
 601          }
 602      }
 603  
 604      protected function strip_attr($attrib, $xpath)
 605      {
 606          $elements = $xpath->query('//*[@' . $attrib . ']');
 607  
 608          foreach ($elements as $element) {
 609              $element->removeAttribute($attrib);
 610          }
 611      }
 612  
 613      protected function rename_attr($attrib, $xpath)
 614      {
 615          $elements = $xpath->query('//*[@' . $attrib . ']');
 616  
 617          foreach ($elements as $element) {
 618              $element->setAttribute('data-sanitized-' . $attrib, $element->getAttribute($attrib));
 619              $element->removeAttribute($attrib);
 620          }
 621      }
 622  
 623      protected function add_attr($tag, $valuePairs, $document)
 624      {
 625          $elements = $document->getElementsByTagName($tag);
 626          foreach ($elements as $element) {
 627              foreach ($valuePairs as $attrib => $value) {
 628                  $element->setAttribute($attrib, $value);
 629              }
 630          }
 631      }
 632  
 633      /**
 634       * Get a DataCache
 635       *
 636       * @param string $image_url Only needed for BC, can be removed in SimplePie 2.0.0
 637       *
 638       * @return DataCache
 639       */
 640      private function get_cache($image_url = '')
 641      {
 642          if ($this->cache === null) {
 643              // @trigger_error(sprintf('Not providing as PSR-16 cache implementation is deprecated since SimplePie 1.8.0, please use "SimplePie\SimplePie::set_cache()".'), \E_USER_DEPRECATED);
 644              $cache = $this->registry->call(Cache::class, 'get_handler', [
 645                  $this->cache_location,
 646                  $image_url,
 647                  Base::TYPE_IMAGE
 648              ]);
 649  
 650              return new BaseDataCache($cache);
 651          }
 652  
 653          return $this->cache;
 654      }
 655  }
 656  
 657  class_alias('SimplePie\Sanitize', 'SimplePie_Sanitize');