Merge branch 'bugfix/zero-width-space-characters-may-destroy-links-in-plugin-output-11796'
fixes #11796 fxies #11737
This commit is contained in:
commit
6cf8c2ec93
|
@ -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 ​
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $zeroWidthSpace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The encoded character ​
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $zeroWidthSpaceEnt = '​';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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​$3', $output);
|
|
||||||
// Add zero width space before '(' and '[' if not surrounded by whitespaces
|
|
||||||
$output = preg_replace('/([^\s])([([])([^\s])/', '$1​$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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue