PageRenderTime 46ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/libraries/joomla/filter/input.php

https://bitbucket.org/pastor399/newcastleunifc
PHP | 758 lines | 431 code | 88 blank | 239 comment | 74 complexity | f0b606e09750975e31d3be71b20f3b91 MD5 | raw file
  1. <?php
  2. /**
  3. * @package Joomla.Platform
  4. * @subpackage Filter
  5. *
  6. * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
  7. * @license GNU General Public License version 2 or later; see LICENSE
  8. */
  9. defined('JPATH_PLATFORM') or die;
  10. /**
  11. * JFilterInput is a class for filtering input from any data source
  12. *
  13. * Forked from the php input filter library by: Daniel Morris <dan@rootcube.com>
  14. * Original Contributors: Gianpaolo Racca, Ghislain Picard, Marco Wandschneider, Chris Tobin and Andrew Eddie.
  15. *
  16. * @package Joomla.Platform
  17. * @subpackage Filter
  18. * @since 11.1
  19. */
  20. class JFilterInput
  21. {
  22. /**
  23. * @var array A container for JFilterInput instances.
  24. * @since 11.3
  25. */
  26. protected static $instances = array();
  27. /**
  28. * @var array An array of permitted tags.
  29. * @since 11.1
  30. */
  31. public $tagsArray;
  32. /**
  33. * @var array An array of permitted tag attributes.
  34. * @since 11.1
  35. */
  36. public $attrArray;
  37. /**
  38. * @var integer Method for tags: WhiteList method = 0 (default), BlackList method = 1
  39. * @since 11.1
  40. */
  41. public $tagsMethod;
  42. /**
  43. * @var integer Method for attributes: WhiteList method = 0 (default), BlackList method = 1
  44. * @since 11.1
  45. */
  46. public $attrMethod;
  47. /**
  48. * @var integer Only auto clean essentials = 0, Allow clean blacklisted tags/attr = 1
  49. * @since 11.1
  50. */
  51. public $xssAuto;
  52. /**
  53. * @var array A list of the default blacklisted tags.
  54. * @since 11.1
  55. */
  56. public $tagBlacklist = array(
  57. 'applet',
  58. 'body',
  59. 'bgsound',
  60. 'base',
  61. 'basefont',
  62. 'embed',
  63. 'frame',
  64. 'frameset',
  65. 'head',
  66. 'html',
  67. 'id',
  68. 'iframe',
  69. 'ilayer',
  70. 'layer',
  71. 'link',
  72. 'meta',
  73. 'name',
  74. 'object',
  75. 'script',
  76. 'style',
  77. 'title',
  78. 'xml'
  79. );
  80. /**
  81. * @var array A list of the default blacklisted tag attributes. All event handlers implicit.
  82. * @since 11.1
  83. */
  84. public $attrBlacklist = array(
  85. 'action',
  86. 'background',
  87. 'codebase',
  88. 'dynsrc',
  89. 'lowsrc'
  90. );
  91. /**
  92. * Constructor for inputFilter class. Only first parameter is required.
  93. *
  94. * @param array $tagsArray List of user-defined tags
  95. * @param array $attrArray List of user-defined attributes
  96. * @param integer $tagsMethod WhiteList method = 0, BlackList method = 1
  97. * @param integer $attrMethod WhiteList method = 0, BlackList method = 1
  98. * @param integer $xssAuto Only auto clean essentials = 0, Allow clean blacklisted tags/attr = 1
  99. *
  100. * @since 11.1
  101. */
  102. public function __construct($tagsArray = array(), $attrArray = array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1)
  103. {
  104. // Make sure user defined arrays are in lowercase
  105. $tagsArray = array_map('strtolower', (array) $tagsArray);
  106. $attrArray = array_map('strtolower', (array) $attrArray);
  107. // Assign member variables
  108. $this->tagsArray = $tagsArray;
  109. $this->attrArray = $attrArray;
  110. $this->tagsMethod = $tagsMethod;
  111. $this->attrMethod = $attrMethod;
  112. $this->xssAuto = $xssAuto;
  113. }
  114. /**
  115. * Returns an input filter object, only creating it if it doesn't already exist.
  116. *
  117. * @param array $tagsArray List of user-defined tags
  118. * @param array $attrArray List of user-defined attributes
  119. * @param integer $tagsMethod WhiteList method = 0, BlackList method = 1
  120. * @param integer $attrMethod WhiteList method = 0, BlackList method = 1
  121. * @param integer $xssAuto Only auto clean essentials = 0, Allow clean blacklisted tags/attr = 1
  122. *
  123. * @return JFilterInput The JFilterInput object.
  124. *
  125. * @since 11.1
  126. */
  127. public static function &getInstance($tagsArray = array(), $attrArray = array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1)
  128. {
  129. $sig = md5(serialize(array($tagsArray, $attrArray, $tagsMethod, $attrMethod, $xssAuto)));
  130. if (empty(self::$instances[$sig]))
  131. {
  132. self::$instances[$sig] = new JFilterInput($tagsArray, $attrArray, $tagsMethod, $attrMethod, $xssAuto);
  133. }
  134. return self::$instances[$sig];
  135. }
  136. /**
  137. * Method to be called by another php script. Processes for XSS and
  138. * specified bad code.
  139. *
  140. * @param mixed $source Input string/array-of-string to be 'cleaned'
  141. * @param string $type Return type for the variable (INT, UINT, FLOAT, BOOLEAN, WORD, ALNUM, CMD, BASE64, STRING, ARRAY, PATH, NONE)
  142. *
  143. * @return mixed 'Cleaned' version of input parameter
  144. *
  145. * @since 11.1
  146. */
  147. public function clean($source, $type = 'string')
  148. {
  149. // Handle the type constraint
  150. switch (strtoupper($type))
  151. {
  152. case 'INT':
  153. case 'INTEGER':
  154. // Only use the first integer value
  155. preg_match('/-?[0-9]+/', (string) $source, $matches);
  156. $result = @ (int) $matches[0];
  157. break;
  158. case 'UINT':
  159. // Only use the first integer value
  160. preg_match('/-?[0-9]+/', (string) $source, $matches);
  161. $result = @ abs((int) $matches[0]);
  162. break;
  163. case 'FLOAT':
  164. case 'DOUBLE':
  165. // Only use the first floating point value
  166. preg_match('/-?[0-9]+(\.[0-9]+)?/', (string) $source, $matches);
  167. $result = @ (float) $matches[0];
  168. break;
  169. case 'BOOL':
  170. case 'BOOLEAN':
  171. $result = (bool) $source;
  172. break;
  173. case 'WORD':
  174. $result = (string) preg_replace('/[^A-Z_]/i', '', $source);
  175. break;
  176. case 'ALNUM':
  177. $result = (string) preg_replace('/[^A-Z0-9]/i', '', $source);
  178. break;
  179. case 'CMD':
  180. $result = (string) preg_replace('/[^A-Z0-9_\.-]/i', '', $source);
  181. $result = ltrim($result, '.');
  182. break;
  183. case 'BASE64':
  184. $result = (string) preg_replace('/[^A-Z0-9\/+=]/i', '', $source);
  185. break;
  186. case 'STRING':
  187. $result = (string) $this->_remove($this->_decode((string) $source));
  188. break;
  189. case 'HTML':
  190. $result = (string) $this->_remove((string) $source);
  191. break;
  192. case 'ARRAY':
  193. $result = (array) $source;
  194. break;
  195. case 'PATH':
  196. $pattern = '/^[A-Za-z0-9_-]+[A-Za-z0-9_\.-]*([\\\\\/][A-Za-z0-9_-]+[A-Za-z0-9_\.-]*)*$/';
  197. preg_match($pattern, (string) $source, $matches);
  198. $result = @ (string) $matches[0];
  199. break;
  200. case 'USERNAME':
  201. $result = (string) preg_replace('/[\x00-\x1F\x7F<>"\'%&]/', '', $source);
  202. break;
  203. case 'RAW':
  204. $result = $source;
  205. break;
  206. default:
  207. // Are we dealing with an array?
  208. if (is_array($source))
  209. {
  210. foreach ($source as $key => $value)
  211. {
  212. // Filter element for XSS and other 'bad' code etc.
  213. if (is_string($value))
  214. {
  215. $source[$key] = $this->_remove($this->_decode($value));
  216. }
  217. }
  218. $result = $source;
  219. }
  220. else
  221. {
  222. // Or a string?
  223. if (is_string($source) && !empty($source))
  224. {
  225. // Filter source for XSS and other 'bad' code etc.
  226. $result = $this->_remove($this->_decode($source));
  227. }
  228. else
  229. {
  230. // Not an array or string.. return the passed parameter
  231. $result = $source;
  232. }
  233. }
  234. break;
  235. }
  236. return $result;
  237. }
  238. /**
  239. * Function to determine if contents of an attribute are safe
  240. *
  241. * @param array $attrSubSet A 2 element array for attribute's name, value
  242. *
  243. * @return boolean True if bad code is detected
  244. *
  245. * @since 11.1
  246. */
  247. public static function checkAttribute($attrSubSet)
  248. {
  249. $attrSubSet[0] = strtolower($attrSubSet[0]);
  250. $attrSubSet[1] = strtolower($attrSubSet[1]);
  251. return (((strpos($attrSubSet[1], 'expression') !== false) && ($attrSubSet[0]) == 'style') || (strpos($attrSubSet[1], 'javascript:') !== false) ||
  252. (strpos($attrSubSet[1], 'behaviour:') !== false) || (strpos($attrSubSet[1], 'vbscript:') !== false) ||
  253. (strpos($attrSubSet[1], 'mocha:') !== false) || (strpos($attrSubSet[1], 'livescript:') !== false));
  254. }
  255. /**
  256. * Internal method to iteratively remove all unwanted tags and attributes
  257. *
  258. * @param string $source Input string to be 'cleaned'
  259. *
  260. * @return string 'Cleaned' version of input parameter
  261. *
  262. * @since 11.1
  263. */
  264. protected function _remove($source)
  265. {
  266. $loopCounter = 0;
  267. // Iteration provides nested tag protection
  268. while ($source != $this->_cleanTags($source))
  269. {
  270. $source = $this->_cleanTags($source);
  271. $loopCounter++;
  272. }
  273. return $source;
  274. }
  275. /**
  276. * Internal method to strip a string of certain tags
  277. *
  278. * @param string $source Input string to be 'cleaned'
  279. *
  280. * @return string 'Cleaned' version of input parameter
  281. *
  282. * @since 11.1
  283. */
  284. protected function _cleanTags($source)
  285. {
  286. // First, pre-process this for illegal characters inside attribute values
  287. $source = $this->_escapeAttributeValues($source);
  288. // In the beginning we don't really have a tag, so everything is postTag
  289. $preTag = null;
  290. $postTag = $source;
  291. $currentSpace = false;
  292. // Setting to null to deal with undefined variables
  293. $attr = '';
  294. // Is there a tag? If so it will certainly start with a '<'.
  295. $tagOpen_start = strpos($source, '<');
  296. while ($tagOpen_start !== false)
  297. {
  298. // Get some information about the tag we are processing
  299. $preTag .= substr($postTag, 0, $tagOpen_start);
  300. $postTag = substr($postTag, $tagOpen_start);
  301. $fromTagOpen = substr($postTag, 1);
  302. $tagOpen_end = strpos($fromTagOpen, '>');
  303. // Check for mal-formed tag where we have a second '<' before the first '>'
  304. $nextOpenTag = (strlen($postTag) > $tagOpen_start) ? strpos($postTag, '<', $tagOpen_start + 1) : false;
  305. if (($nextOpenTag !== false) && ($nextOpenTag < $tagOpen_end))
  306. {
  307. // At this point we have a mal-formed tag -- remove the offending open
  308. $postTag = substr($postTag, 0, $tagOpen_start) . substr($postTag, $tagOpen_start + 1);
  309. $tagOpen_start = strpos($postTag, '<');
  310. continue;
  311. }
  312. // Let's catch any non-terminated tags and skip over them
  313. if ($tagOpen_end === false)
  314. {
  315. $postTag = substr($postTag, $tagOpen_start + 1);
  316. $tagOpen_start = strpos($postTag, '<');
  317. continue;
  318. }
  319. // Do we have a nested tag?
  320. $tagOpen_nested = strpos($fromTagOpen, '<');
  321. if (($tagOpen_nested !== false) && ($tagOpen_nested < $tagOpen_end))
  322. {
  323. $preTag .= substr($postTag, 0, ($tagOpen_nested + 1));
  324. $postTag = substr($postTag, ($tagOpen_nested + 1));
  325. $tagOpen_start = strpos($postTag, '<');
  326. continue;
  327. }
  328. // Let's get some information about our tag and setup attribute pairs
  329. $tagOpen_nested = (strpos($fromTagOpen, '<') + $tagOpen_start + 1);
  330. $currentTag = substr($fromTagOpen, 0, $tagOpen_end);
  331. $tagLength = strlen($currentTag);
  332. $tagLeft = $currentTag;
  333. $attrSet = array();
  334. $currentSpace = strpos($tagLeft, ' ');
  335. // Are we an open tag or a close tag?
  336. if (substr($currentTag, 0, 1) == '/')
  337. {
  338. // Close Tag
  339. $isCloseTag = true;
  340. list ($tagName) = explode(' ', $currentTag);
  341. $tagName = substr($tagName, 1);
  342. }
  343. else
  344. {
  345. // Open Tag
  346. $isCloseTag = false;
  347. list ($tagName) = explode(' ', $currentTag);
  348. }
  349. /*
  350. * Exclude all "non-regular" tagnames
  351. * OR no tagname
  352. * OR remove if xssauto is on and tag is blacklisted
  353. */
  354. if ((!preg_match("/^[a-z][a-z0-9]*$/i", $tagName)) || (!$tagName) || ((in_array(strtolower($tagName), $this->tagBlacklist)) && ($this->xssAuto)))
  355. {
  356. $postTag = substr($postTag, ($tagLength + 2));
  357. $tagOpen_start = strpos($postTag, '<');
  358. // Strip tag
  359. continue;
  360. }
  361. /*
  362. * Time to grab any attributes from the tag... need this section in
  363. * case attributes have spaces in the values.
  364. */
  365. while ($currentSpace !== false)
  366. {
  367. $attr = '';
  368. $fromSpace = substr($tagLeft, ($currentSpace + 1));
  369. $nextEqual = strpos($fromSpace, '=');
  370. $nextSpace = strpos($fromSpace, ' ');
  371. $openQuotes = strpos($fromSpace, '"');
  372. $closeQuotes = strpos(substr($fromSpace, ($openQuotes + 1)), '"') + $openQuotes + 1;
  373. $startAtt = '';
  374. $startAttPosition = 0;
  375. // Find position of equal and open quotes ignoring
  376. if (preg_match('#\s*=\s*\"#', $fromSpace, $matches, PREG_OFFSET_CAPTURE))
  377. {
  378. $startAtt = $matches[0][0];
  379. $startAttPosition = $matches[0][1];
  380. $closeQuotes = strpos(substr($fromSpace, ($startAttPosition + strlen($startAtt))), '"') + $startAttPosition + strlen($startAtt);
  381. $nextEqual = $startAttPosition + strpos($startAtt, '=');
  382. $openQuotes = $startAttPosition + strpos($startAtt, '"');
  383. $nextSpace = strpos(substr($fromSpace, $closeQuotes), ' ') + $closeQuotes;
  384. }
  385. // Do we have an attribute to process? [check for equal sign]
  386. if ($fromSpace != '/' && (($nextEqual && $nextSpace && $nextSpace < $nextEqual) || !$nextEqual))
  387. {
  388. if (!$nextEqual)
  389. {
  390. $attribEnd = strpos($fromSpace, '/') - 1;
  391. }
  392. else
  393. {
  394. $attribEnd = $nextSpace - 1;
  395. }
  396. // If there is an ending, use this, if not, do not worry.
  397. if ($attribEnd > 0)
  398. {
  399. $fromSpace = substr($fromSpace, $attribEnd + 1);
  400. }
  401. }
  402. if (strpos($fromSpace, '=') !== false)
  403. {
  404. // If the attribute value is wrapped in quotes we need to grab the substring from
  405. // the closing quote, otherwise grab until the next space.
  406. if (($openQuotes !== false) && (strpos(substr($fromSpace, ($openQuotes + 1)), '"') !== false))
  407. {
  408. $attr = substr($fromSpace, 0, ($closeQuotes + 1));
  409. }
  410. else
  411. {
  412. $attr = substr($fromSpace, 0, $nextSpace);
  413. }
  414. }
  415. // No more equal signs so add any extra text in the tag into the attribute array [eg. checked]
  416. else
  417. {
  418. if ($fromSpace != '/')
  419. {
  420. $attr = substr($fromSpace, 0, $nextSpace);
  421. }
  422. }
  423. // Last Attribute Pair
  424. if (!$attr && $fromSpace != '/')
  425. {
  426. $attr = $fromSpace;
  427. }
  428. // Add attribute pair to the attribute array
  429. $attrSet[] = $attr;
  430. // Move search point and continue iteration
  431. $tagLeft = substr($fromSpace, strlen($attr));
  432. $currentSpace = strpos($tagLeft, ' ');
  433. }
  434. // Is our tag in the user input array?
  435. $tagFound = in_array(strtolower($tagName), $this->tagsArray);
  436. // If the tag is allowed let's append it to the output string.
  437. if ((!$tagFound && $this->tagsMethod) || ($tagFound && !$this->tagsMethod))
  438. {
  439. // Reconstruct tag with allowed attributes
  440. if (!$isCloseTag)
  441. {
  442. // Open or single tag
  443. $attrSet = $this->_cleanAttributes($attrSet);
  444. $preTag .= '<' . $tagName;
  445. for ($i = 0, $count = count($attrSet); $i < $count; $i++)
  446. {
  447. $preTag .= ' ' . $attrSet[$i];
  448. }
  449. // Reformat single tags to XHTML
  450. if (strpos($fromTagOpen, '</' . $tagName))
  451. {
  452. $preTag .= '>';
  453. }
  454. else
  455. {
  456. $preTag .= ' />';
  457. }
  458. }
  459. // Closing tag
  460. else
  461. {
  462. $preTag .= '</' . $tagName . '>';
  463. }
  464. }
  465. // Find next tag's start and continue iteration
  466. $postTag = substr($postTag, ($tagLength + 2));
  467. $tagOpen_start = strpos($postTag, '<');
  468. }
  469. // Append any code after the end of tags and return
  470. if ($postTag != '<')
  471. {
  472. $preTag .= $postTag;
  473. }
  474. return $preTag;
  475. }
  476. /**
  477. * Internal method to strip a tag of certain attributes
  478. *
  479. * @param array $attrSet Array of attribute pairs to filter
  480. *
  481. * @return array Filtered array of attribute pairs
  482. *
  483. * @since 11.1
  484. */
  485. protected function _cleanAttributes($attrSet)
  486. {
  487. $newSet = array();
  488. $count = count($attrSet);
  489. // Iterate through attribute pairs
  490. for ($i = 0; $i < $count; $i++)
  491. {
  492. // Skip blank spaces
  493. if (!$attrSet[$i])
  494. {
  495. continue;
  496. }
  497. // Split into name/value pairs
  498. $attrSubSet = explode('=', trim($attrSet[$i]), 2);
  499. // Take the last attribute in case there is an attribute with no value
  500. $attrSubSet[0] = array_pop(explode(' ', trim($attrSubSet[0])));
  501. // Remove all "non-regular" attribute names
  502. // AND blacklisted attributes
  503. if ((!preg_match('/[a-z]*$/i', $attrSubSet[0]))
  504. || (($this->xssAuto) && ((in_array(strtolower($attrSubSet[0]), $this->attrBlacklist))
  505. || (substr($attrSubSet[0], 0, 2) == 'on'))))
  506. {
  507. continue;
  508. }
  509. // XSS attribute value filtering
  510. if (isset($attrSubSet[1]))
  511. {
  512. // Trim leading and trailing spaces
  513. $attrSubSet[1] = trim($attrSubSet[1]);
  514. // Strips unicode, hex, etc
  515. $attrSubSet[1] = str_replace('&#', '', $attrSubSet[1]);
  516. // Strip normal newline within attr value
  517. $attrSubSet[1] = preg_replace('/[\n\r]/', '', $attrSubSet[1]);
  518. // Strip double quotes
  519. $attrSubSet[1] = str_replace('"', '', $attrSubSet[1]);
  520. // Convert single quotes from either side to doubles (Single quotes shouldn't be used to pad attr values)
  521. if ((substr($attrSubSet[1], 0, 1) == "'") && (substr($attrSubSet[1], (strlen($attrSubSet[1]) - 1), 1) == "'"))
  522. {
  523. $attrSubSet[1] = substr($attrSubSet[1], 1, (strlen($attrSubSet[1]) - 2));
  524. }
  525. // Strip slashes
  526. $attrSubSet[1] = stripslashes($attrSubSet[1]);
  527. }
  528. else
  529. {
  530. continue;
  531. }
  532. // Autostrip script tags
  533. if (self::checkAttribute($attrSubSet))
  534. {
  535. continue;
  536. }
  537. // Is our attribute in the user input array?
  538. $attrFound = in_array(strtolower($attrSubSet[0]), $this->attrArray);
  539. // If the tag is allowed lets keep it
  540. if ((!$attrFound && $this->attrMethod) || ($attrFound && !$this->attrMethod))
  541. {
  542. // Does the attribute have a value?
  543. if (empty($attrSubSet[1]) === false)
  544. {
  545. $newSet[] = $attrSubSet[0] . '="' . $attrSubSet[1] . '"';
  546. }
  547. elseif ($attrSubSet[1] === "0")
  548. {
  549. // Special Case
  550. // Is the value 0?
  551. $newSet[] = $attrSubSet[0] . '="0"';
  552. }
  553. else
  554. {
  555. // Leave empty attributes alone
  556. $newSet[] = $attrSubSet[0] . '=""';
  557. }
  558. }
  559. }
  560. return $newSet;
  561. }
  562. /**
  563. * Try to convert to plaintext
  564. *
  565. * @param string $source The source string.
  566. *
  567. * @return string Plaintext string
  568. *
  569. * @since 11.1
  570. */
  571. protected function _decode($source)
  572. {
  573. static $ttr;
  574. if (!is_array($ttr))
  575. {
  576. // Entity decode
  577. $trans_tbl = get_html_translation_table(HTML_ENTITIES);
  578. foreach ($trans_tbl as $k => $v)
  579. {
  580. $ttr[$v] = utf8_encode($k);
  581. }
  582. }
  583. $source = strtr($source, $ttr);
  584. // Convert decimal
  585. $source = preg_replace('/&#(\d+);/me', "utf8_encode(chr(\\1))", $source); // decimal notation
  586. // Convert hex
  587. $source = preg_replace('/&#x([a-f0-9]+);/mei', "utf8_encode(chr(0x\\1))", $source); // hex notation
  588. return $source;
  589. }
  590. /**
  591. * Escape < > and " inside attribute values
  592. *
  593. * @param string $source The source string.
  594. *
  595. * @return string Filtered string
  596. *
  597. * @since 11.1
  598. */
  599. protected function _escapeAttributeValues($source)
  600. {
  601. $alreadyFiltered = '';
  602. $remainder = $source;
  603. $badChars = array('<', '"', '>');
  604. $escapedChars = array('&lt;', '&quot;', '&gt;');
  605. // Process each portion based on presence of =" and "<space>, "/>, or ">
  606. // See if there are any more attributes to process
  607. while (preg_match('#<[^>]*?=\s*?(\"|\')#s', $remainder, $matches, PREG_OFFSET_CAPTURE))
  608. {
  609. // Get the portion before the attribute value
  610. $quotePosition = $matches[0][1];
  611. $nextBefore = $quotePosition + strlen($matches[0][0]);
  612. // Figure out if we have a single or double quote and look for the matching closing quote
  613. // Closing quote should be "/>, ">, "<space>, or " at the end of the string
  614. $quote = substr($matches[0][0], -1);
  615. $pregMatch = ($quote == '"') ? '#(\"\s*/\s*>|\"\s*>|\"\s+|\"$)#' : "#(\'\s*/\s*>|\'\s*>|\'\s+|\'$)#";
  616. // Get the portion after attribute value
  617. if (preg_match($pregMatch, substr($remainder, $nextBefore), $matches, PREG_OFFSET_CAPTURE))
  618. {
  619. // We have a closing quote
  620. $nextAfter = $nextBefore + $matches[0][1];
  621. }
  622. else
  623. {
  624. // No closing quote
  625. $nextAfter = strlen($remainder);
  626. }
  627. // Get the actual attribute value
  628. $attributeValue = substr($remainder, $nextBefore, $nextAfter - $nextBefore);
  629. // Escape bad chars
  630. $attributeValue = str_replace($badChars, $escapedChars, $attributeValue);
  631. $attributeValue = $this->_stripCSSExpressions($attributeValue);
  632. $alreadyFiltered .= substr($remainder, 0, $nextBefore) . $attributeValue . $quote;
  633. $remainder = substr($remainder, $nextAfter + 1);
  634. }
  635. // At this point, we just have to return the $alreadyFiltered and the $remainder
  636. return $alreadyFiltered . $remainder;
  637. }
  638. /**
  639. * Remove CSS Expressions in the form of <property>:expression(...)
  640. *
  641. * @param string $source The source string.
  642. *
  643. * @return string Filtered string
  644. *
  645. * @since 11.1
  646. */
  647. protected function _stripCSSExpressions($source)
  648. {
  649. // Strip any comments out (in the form of /*...*/)
  650. $test = preg_replace('#\/\*.*\*\/#U', '', $source);
  651. // Test for :expression
  652. if (!stripos($test, ':expression'))
  653. {
  654. // Not found, so we are done
  655. $return = $source;
  656. }
  657. else
  658. {
  659. // At this point, we have stripped out the comments and have found :expression
  660. // Test stripped string for :expression followed by a '('
  661. if (preg_match_all('#:expression\s*\(#', $test, $matches))
  662. {
  663. // If found, remove :expression
  664. $test = str_ireplace(':expression', '', $test);
  665. $return = $test;
  666. }
  667. }
  668. return $return;
  669. }
  670. }