Merge branch 'bugfix/zero-width-space-characters-may-destroy-links-in-plugin-output-11796'

fixes #11796
fxies #11737
This commit is contained in:
Eric Lippmann 2016-06-02 17:52:34 +02:00
commit 6cf8c2ec93
2 changed files with 135 additions and 7 deletions

View File

@ -1,10 +1,23 @@
<?php <?php
/* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */ /* Icinga Web 2 | (c) 2013 Icinga Development Team | GPLv2+ */
/**
* Plugin output renderer
*/
class Zend_View_Helper_PluginOutput extends Zend_View_Helper_Abstract class Zend_View_Helper_PluginOutput extends Zend_View_Helper_Abstract
{ {
/**
* The return value of getPurifier()
*
* @var HTMLPurifier
*/
protected static $purifier; protected static $purifier;
/**
* Patterns to be replaced in plain text plugin output
*
* @var array
*/
protected static $txtPatterns = array( protected static $txtPatterns = array(
'~\\\n~', '~\\\n~',
'~\\\t~', '~\\\t~',
@ -16,6 +29,11 @@ class Zend_View_Helper_PluginOutput extends Zend_View_Helper_Abstract
'~\@{6,}~' '~\@{6,}~'
); );
/**
* Replacements for $txtPatterns
*
* @var array
*/
protected static $txtReplacements = array( protected static $txtReplacements = array(
"\n", "\n",
"\t", "\t",
@ -27,6 +45,38 @@ class Zend_View_Helper_PluginOutput extends Zend_View_Helper_Abstract
'@@@@@@', '@@@@@@',
); );
/**
* The character &#8203;
*
* @var string
*/
protected $zeroWidthSpace;
/**
* The encoded character &#8203;
*
* @var string
*/
protected $zeroWidthSpaceEnt = '&#8203;';
/**
* Create a new Zend_View_Helper_PluginOutput
*/
public function __construct()
{
// This is actually not required as the value is constant,
// but as its (visual) length is 0, it's likely to be mixed up with the empty string.
$this->zeroWidthSpace = html_entity_decode($this->zeroWidthSpaceEnt, ENT_NOQUOTES, 'UTF-8');
}
/**
* Render plugin output
*
* @param string $output
* @param bool $raw
*
* @return string
*/
public function pluginOutput($output, $raw = false) public function pluginOutput($output, $raw = false)
{ {
if (empty($output)) { if (empty($output)) {
@ -41,24 +91,26 @@ class Zend_View_Helper_PluginOutput extends Zend_View_Helper_Abstract
$this->getPurifier()->purify($output) $this->getPurifier()->purify($output)
); );
$isHtml = true; $isHtml = true;
$useDom = true;
} else { } else {
// Plaintext // Plaintext
$count = 0;
$output = preg_replace( $output = preg_replace(
self::$txtPatterns, self::$txtPatterns,
self::$txtReplacements, self::$txtReplacements,
$this->view->escape($output) $this->view->escape($output),
-1,
$count
); );
$isHtml = false; $isHtml = false;
$useDom = (bool) $count;
} }
$output = $this->fixLinks($output);
// Help browsers to break words in plugin output // Help browsers to break words in plugin output
$output = trim($output); $output = trim($output);
// Add space after comma where missing // Add space after comma where missing
$output = preg_replace('/,(?=[^\s])/', ', ', $output); $output = preg_replace('/,(?=[^\s])/', ', ', $output);
// Add zero width space after ')', ']', ':', '.', '_' and '-' if not surrounded by whitespaces $output = $useDom ? $this->fixLinksAndWrapping($output) : $this->fixWrapping($output, $this->zeroWidthSpaceEnt);
$output = preg_replace('/([^\s])([\\)\\]:._-])([^\s])/', '$1$2&#8203;$3', $output);
// Add zero width space before '(' and '[' if not surrounded by whitespaces
$output = preg_replace('/([^\s])([([])([^\s])/', '$1&#8203;$2$3', $output);
if (! $raw) { if (! $raw) {
if ($isHtml) { if ($isHtml) {
@ -70,13 +122,22 @@ class Zend_View_Helper_PluginOutput extends Zend_View_Helper_Abstract
return $output; return $output;
} }
protected function fixLinks($html) /**
* Replace classic Icinga CGI links with Icinga Web 2 links and
* add zero width space to make wrapping easier for the user agent
*
* @param string $html
*
* @return string
*/
protected function fixLinksAndWrapping($html)
{ {
$ret = array(); $ret = array();
$dom = new DOMDocument; $dom = new DOMDocument;
$dom->loadXML('<div>' . $html . '</div>', LIBXML_NOERROR | LIBXML_NOWARNING); $dom->loadXML('<div>' . $html . '</div>', LIBXML_NOERROR | LIBXML_NOWARNING);
$dom->preserveWhiteSpace = false; $dom->preserveWhiteSpace = false;
$links = $dom->getElementsByTagName('a'); $links = $dom->getElementsByTagName('a');
foreach ($links as $tag) foreach ($links as $tag)
{ {
@ -93,9 +154,17 @@ class Zend_View_Helper_PluginOutput extends Zend_View_Helper_Abstract
} }
//$ret[$tag->getAttribute('href')] = $tag->childNodes->item(0)->nodeValue; //$ret[$tag->getAttribute('href')] = $tag->childNodes->item(0)->nodeValue;
} }
$this->fixWrappingRecursive($dom);
return substr($dom->saveHTML(), 5, -7); return substr($dom->saveHTML(), 5, -7);
} }
/**
* Initialize and return self::$purifier
*
* @return HTMLPurifier
*/
protected function getPurifier() protected function getPurifier()
{ {
if (self::$purifier === null) { if (self::$purifier === null) {
@ -119,4 +188,36 @@ class Zend_View_Helper_PluginOutput extends Zend_View_Helper_Abstract
} }
return self::$purifier; return self::$purifier;
} }
/**
* Add zero width space to all text in the DOM to make wrapping easier for the user agent
*
* @param DOMNode $node
*/
protected function fixWrappingRecursive(DOMNode $node)
{
if ($node instanceof DOMText) {
$node->data = $this->fixWrapping($node->data, $this->zeroWidthSpace);
} elseif ($node->childNodes !== null) {
foreach ($node->childNodes as $childNode) {
$this->fixWrappingRecursive($childNode);
}
}
}
/**
* Add zero width space to make wrapping easier for the user agent
*
* @param string $output
* @param string $zeroWidthSpace
*
* @return string
*/
protected function fixWrapping($output, $zeroWidthSpace)
{
// Add zero width space after ')', ']', ':', '.', '_' and '-' if not surrounded by whitespaces
$output = preg_replace('/([^\s])([\\)\\]:._-])([^\s])/', '$1$2' . $zeroWidthSpace . '$3', $output);
// Add zero width space before '(' and '[' if not surrounded by whitespaces
return preg_replace('/([^\s])([([])([^\s])/', '$1' . $zeroWidthSpace . '$2$3', $output);
}
} }

View File

@ -0,0 +1,27 @@
<?php
/* Icinga Web 2 | (c) 2016 Icinga Development Team | GPLv2+ */
namespace Tests\Icinga\Module\Monitoring\Regression;
require_once __DIR__ . '/../../../application/views/helpers/PluginOutput.php';
use Icinga\Test\BaseTestCase;
use Zend_View_Helper_PluginOutput;
/**
* Regression-Test for bug #11796
*
* Plugin output renderer must not destroy links by adding zero width space characters.
*
* @see https://dev.icinga.org/issues/11796
*/
class Bug11796Test extends BaseTestCase
{
public function testWhetherZeroWidthSpaceDoesntDestroyLinksInPluginOutput()
{
$helper = new Zend_View_Helper_PluginOutput();
$this->assertTrue(
strpos($helper->pluginOutput('<a href="http://example.com">EXAMPLE.COM', true), 'example.com') !== false
);
}
}