diff --git a/library/Zend/Debug/Debug.php b/library/Zend/Debug/Debug.php index dd1472fba9f..fefae2b400e 100644 --- a/library/Zend/Debug/Debug.php +++ b/library/Zend/Debug/Debug.php @@ -10,6 +10,8 @@ namespace Zend\Debug; +use Zend\Escaper\Escaper; + /** * Concrete class for generating debug dumps related to the output source. * @@ -18,6 +20,10 @@ */ class Debug { + /** + * @var Escaper + */ + protected static $escaper = null; /** * @var string @@ -50,6 +56,31 @@ public static function setSapi($sapi) self::$sapi = $sapi; } + /** + * Set Escaper instance + * + * @param Escaper $escaper + */ + public static function setEscaper(Escaper $escaper) + { + static::$escaper = $escaper; + } + + /** + * Get Escaper instance + * + * Lazy loads an instance if none provided. + * + * @return Escaper + */ + public static function getEscaper() + { + if (null === static::$escaper) { + static::setEscaper(new Escaper()); + } + return static::$escaper; + } + /** * Debug helper function. This is a wrapper for var_dump() that adds * the
 tags, cleans up newlines and indents, and runs
@@ -78,7 +109,7 @@ public static function dump($var, $label=null, $echo=true)
                     . PHP_EOL;
         } else {
             if (!extension_loaded('xdebug')) {
-                $output = htmlspecialchars($output, ENT_QUOTES);
+                $output = static::getEscaper()->escapeHtml($output);
             }
 
             $output = '
'
diff --git a/library/Zend/Debug/composer.json b/library/Zend/Debug/composer.json
index 02b34458c47..de9d6bf8ccd 100644
--- a/library/Zend/Debug/composer.json
+++ b/library/Zend/Debug/composer.json
@@ -13,6 +13,10 @@
     },
     "target-dir": "Zend/Debug",
     "require": {
-        "php": ">=5.3.3"
+        "php": ">=5.3.3",
+        "zendframework/zend-escaper": "self.version"
+    },
+    "suggest": {
+        "ext/xdebug": "XDebug, for better backtrace output"
     }
-}
\ No newline at end of file
+}
diff --git a/library/Zend/Feed/PubSubHubbub/PubSubHubbub.php b/library/Zend/Feed/PubSubHubbub/PubSubHubbub.php
index 114f563b805..3e4b8ba3e8e 100644
--- a/library/Zend/Feed/PubSubHubbub/PubSubHubbub.php
+++ b/library/Zend/Feed/PubSubHubbub/PubSubHubbub.php
@@ -10,6 +10,7 @@
 
 namespace Zend\Feed\PubSubHubbub;
 
+use Zend\Escaper\Escaper;
 use Zend\Feed\Reader;
 use Zend\Http;
 
@@ -32,10 +33,15 @@ class PubSubHubbub
     const SUBSCRIPTION_NOTVERIFIED = 'not_verified';
     const SUBSCRIPTION_TODELETE    = 'to_delete';
 
+    /**
+     * @var Escaper
+     */
+    protected static $escaper;
+
     /**
      * Singleton instance if required of the HTTP client
      *
-     * @var \Zend\Http\Client
+     * @var Http\Client
      */
     protected static $httpClient = null;
 
@@ -67,7 +73,7 @@ public static function detectHubs($source)
      * Allows the external environment to make Zend_Oauth use a specific
      * Client instance.
      *
-     * @param  \Zend\Http\Client $httpClient
+     * @param  Http\Client $httpClient
      * @return void
      */
     public static function setHttpClient(Http\Client $httpClient)
@@ -80,15 +86,15 @@ public static function setHttpClient(Http\Client $httpClient)
      * the instance is reset and cleared of previous parameters GET/POST.
      * Headers are NOT reset but handled by this component if applicable.
      *
-     * @return \Zend\Http\Client
+     * @return Http\Client
      */
     public static function getHttpClient()
     {
-        if (!isset(self::$httpClient)):
+        if (!isset(self::$httpClient)) {
             self::$httpClient = new Http\Client;
-        else:
+        } else {
             self::$httpClient->resetParameters();
-        endif;
+        }
         return self::$httpClient;
     }
 
@@ -103,6 +109,33 @@ public static function clearHttpClient()
         self::$httpClient = null;
     }
 
+    /**
+     * Set the Escaper instance
+     *
+     * If null, resets the instance
+     * 
+     * @param  null|Escaper $escaper 
+     */
+    public static function setEscaper(Escaper $escaper = null)
+    {
+        static::$escaper = $escaper;
+    }
+
+    /**
+     * Get the Escaper instance
+     *
+     * If none registered, lazy-loads an instance.
+     * 
+     * @return Escaper
+     */
+    public static function getEscaper()
+    {
+        if (null === static::$escaper) {
+            static::setEscaper(new Escaper());
+        }
+        return static::$escaper;
+    }
+
     /**
      * RFC 3986 safe url encoding method
      *
@@ -111,7 +144,8 @@ public static function clearHttpClient()
      */
     public static function urlencode($string)
     {
-        $rawencoded = rawurlencode($string);
+        $escaper    = static::getEscaper();
+        $rawencoded = $escaper->escapeUrl($string);
         $rfcencoded = str_replace('%7E', '~', $rawencoded);
         return $rfcencoded;
     }
diff --git a/library/Zend/Feed/composer.json b/library/Zend/Feed/composer.json
index 61e61a53bf3..d98958ee8e9 100644
--- a/library/Zend/Feed/composer.json
+++ b/library/Zend/Feed/composer.json
@@ -14,6 +14,7 @@
     "target-dir": "Zend/Feed",
     "require": {
         "php": ">=5.3.3",
+        "zendframework/zend-escaper": "self.version",
         "zendframework/zend-stdlib": "self.version"
     },
     "suggest": {
diff --git a/library/Zend/Log/Formatter/Xml.php b/library/Zend/Log/Formatter/Xml.php
index 64bb6da200a..cbde6074c20 100644
--- a/library/Zend/Log/Formatter/Xml.php
+++ b/library/Zend/Log/Formatter/Xml.php
@@ -14,6 +14,7 @@
 use DOMDocument;
 use DOMElement;
 use Traversable;
+use Zend\Escaper\Escaper;
 use Zend\Stdlib\ArrayUtils;
 
 /**
@@ -38,6 +39,11 @@ class Xml implements FormatterInterface
      */
     protected $encoding;
 
+    /**
+     * @var Escaper instance
+     */
+    protected $escaper;
+
     /**
      * Format specifier for DateTime objects in event data (default: ISO 8601)
      *
@@ -121,6 +127,33 @@ public function setEncoding($value)
         return $this;
     }
 
+    /**
+     * Set Escaper instance
+     *
+     * @param  Escaper $escaper
+     * @return Xml
+     */
+    public function setEscaper(Escaper $escaper)
+    {
+        $this->escaper = $escaper;
+        return $this;
+    }
+    
+    /**
+     * Get Escaper instance
+     *
+     * Lazy-loads an instance with the current encoding if none registered.
+     *
+     * @return Escaper
+     */
+    public function getEscaper()
+    {
+        if (null === $this->escaper) {
+            $this->setEscaper(new Escaper($this->getEncoding()));
+        }
+        return $this->escaper;
+    }
+
     /**
      * Formats data into a single line to be written by the writer.
      *
@@ -142,9 +175,10 @@ public function format($event)
             }
         }
 
-        $enc = $this->getEncoding();
-        $dom = new DOMDocument('1.0', $enc);
-        $elt = $dom->appendChild(new DOMElement($this->rootElement));
+        $enc     = $this->getEncoding();
+        $escaper = $this->getEscaper();
+        $dom     = new DOMDocument('1.0', $enc);
+        $elt     = $dom->appendChild(new DOMElement($this->rootElement));
 
         foreach ($dataToInsert as $key => $value) {
             if (empty($value)
@@ -152,7 +186,7 @@ public function format($event)
                 || (is_object($value) && method_exists($value,'__toString'))
             ) {
                 if ($key == "message") {
-                    $value = htmlspecialchars($value, ENT_COMPAT, $enc);
+                    $value = $escaper->escapeHtml($value);
                 } elseif ($key == "extra" && empty($value)) {
                     continue;
                 }
diff --git a/library/Zend/Log/composer.json b/library/Zend/Log/composer.json
index 34c1fabc674..004d24beaf3 100644
--- a/library/Zend/Log/composer.json
+++ b/library/Zend/Log/composer.json
@@ -20,6 +20,7 @@
     "suggest": {
         "ext-mongo": "*",
         "zendframework/zend-db": "Zend\\Db component",
+        "zendframework/zend-escaper": "Zend\\Escaper component, for use in the XML formatter",
         "zendframework/zend-mail": "Zend\\Mail component",
         "zendframework/zend-validator": "Zend\\Validator component"
     }
diff --git a/library/Zend/Tag/Cloud/Decorator/AbstractCloud.php b/library/Zend/Tag/Cloud/Decorator/AbstractCloud.php
index 956d2f07686..3c02ca1ad1a 100644
--- a/library/Zend/Tag/Cloud/Decorator/AbstractCloud.php
+++ b/library/Zend/Tag/Cloud/Decorator/AbstractCloud.php
@@ -10,62 +10,12 @@
 
 namespace Zend\Tag\Cloud\Decorator;
 
-use Traversable;
-use Zend\Stdlib\ArrayUtils;
-use Zend\Tag\Cloud\Decorator\DecoratorInterface as Decorator;
-
 /**
  * Abstract class for cloud decorators
  *
  * @category  Zend
  * @package   Zend_Tag
  */
-abstract class AbstractCloud implements Decorator
+abstract class AbstractCloud extends AbstractDecorator
 {
-    /**
-     * Option keys to skip when calling setOptions()
-     *
-     * @var array
-     */
-    protected $skipOptions = array(
-        'options',
-        'config',
-    );
-
-    /**
-     * Create a new cloud decorator with options
-     *
-     * @param  array|Traversable $options
-     */
-    public function __construct($options = null)
-    {
-        if ($options instanceof Traversable) {
-            $options = ArrayUtils::iteratorToArray($options);
-        }
-        if (is_array($options)) {
-            $this->setOptions($options);
-        }
-    }
-
-    /**
-     * Set options from array
-     *
-     * @param  array $options Configuration for the decorator
-     * @return AbstractCloud
-     */
-    public function setOptions(array $options)
-    {
-        foreach ($options as $key => $value) {
-            if (in_array(strtolower($key), $this->skipOptions)) {
-                continue;
-            }
-
-            $method = 'set' . $key;
-            if (method_exists($this, $method)) {
-                $this->$method($value);
-            }
-        }
-
-        return $this;
-    }
 }
diff --git a/library/Zend/Tag/Cloud/Decorator/AbstractDecorator.php b/library/Zend/Tag/Cloud/Decorator/AbstractDecorator.php
new file mode 100644
index 00000000000..4d6c9541268
--- /dev/null
+++ b/library/Zend/Tag/Cloud/Decorator/AbstractDecorator.php
@@ -0,0 +1,190 @@
+setOptions($options);
+        }
+    }
+
+    /**
+     * Set options from array
+     *
+     * @param  array $options Configuration for the decorator
+     * @return AbstractTag
+     */
+    public function setOptions(array $options)
+    {
+        foreach ($options as $key => $value) {
+            if (in_array(strtolower($key), $this->skipOptions)) {
+                continue;
+            }
+
+            $method = 'set' . $key;
+            if (method_exists($this, $method)) {
+                $this->$method($value);
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * Get encoding
+     *
+     * @return string
+     */
+    public function getEncoding()
+    {
+        return $this->encoding;
+    }
+
+    /**
+     * Set encoding
+     *
+     * @param string
+     * @return HTMLCloud
+     */
+    public function setEncoding($value)
+    {
+        $this->encoding = (string) $value;
+        return $this;
+    }
+
+    /**
+     * Set Escaper instance
+     *
+     * @param  Escaper $escaper
+     * @return HtmlCloud
+     */
+    public function setEscaper($escaper)
+    {
+        $this->escaper = $escaper;
+        return $this;
+    }
+    
+    /**
+     * Retrieve Escaper instance
+     *
+     * If none registered, instantiates and registers one using current encoding.
+     *
+     * @return Escaper
+     */
+    public function getEscaper()
+    {
+        if (null === $this->escaper) {
+            $this->setEscaper(new Escaper($this->getEncoding()));
+        }
+        return $this->escaper;
+    }
+
+    /**
+     * Validate an HTML element name
+     * 
+     * @param  string $name 
+     * @throws Exception\InvalidElementNameException
+     */
+    protected function validateElementName($name)
+    {
+        if (!preg_match('/^[a-z0-9]+$/i', $name)) {
+            throw new Exception\InvalidElementNameException(sprintf(
+                '%s: Invalid element name "%s" provided; please provide valid HTML element names',
+                __METHOD__,
+                $this->getEscaper()->escapeHtml($name)
+            ));
+        }
+    }
+
+    /**
+     * Validate an HTML attribute name
+     * 
+     * @param  string $name 
+     * @throws Exception\InvalidAttributeNameException
+     */
+    protected function validateAttributeName($name)
+    {
+        if (!preg_match('/^[a-z_:][-a-z0-9_:.]*$/i', $name)) {
+            throw new Exception\InvalidAttributeNameException(sprintf(
+                '%s: Invalid HTML attribute name "%s" provided; please provide valid HTML attribute names',
+                __METHOD__,
+                $this->getEscaper()->escapeHtml($name)
+            ));
+        }
+    }
+
+    protected function wrapTag($html)
+    {
+        $escaper = $this->getEscaper();
+        foreach ($this->getHTMLTags() as $key => $data) {
+            if (is_array($data)) {
+                $attributes = '';
+                $htmlTag    = $key;
+                $this->validateElementName($htmlTag);
+
+                foreach ($data as $param => $value) {
+                    $this->validateAttributeName($param);
+                    $attributes .= ' ' . $param . '="' . $escaper->escapeHtmlAttr($value) . '"';
+                }
+            } else {
+                $attributes = '';
+                $htmlTag    = $data;
+                $this->validateElementName($htmlTag);
+            }
+
+            $html = sprintf('<%1$s%3$s>%2$s', $htmlTag, $html, $attributes);
+        }
+        return $html;
+    }
+}
diff --git a/library/Zend/Tag/Cloud/Decorator/AbstractTag.php b/library/Zend/Tag/Cloud/Decorator/AbstractTag.php
index ffd410b0e09..1db779289c0 100644
--- a/library/Zend/Tag/Cloud/Decorator/AbstractTag.php
+++ b/library/Zend/Tag/Cloud/Decorator/AbstractTag.php
@@ -10,62 +10,12 @@
 
 namespace Zend\Tag\Cloud\Decorator;
 
-use Traversable;
-use Zend\Stdlib\ArrayUtils;
-use Zend\Tag\Cloud\Decorator\DecoratorInterface as Decorator;
-
 /**
  * Abstract class for tag decorators
  *
  * @category  Zend
  * @package   Zend_Tag
  */
-abstract class AbstractTag implements Decorator
+abstract class AbstractTag extends AbstractDecorator
 {
-    /**
-     * Option keys to skip when calling setOptions()
-     *
-     * @var array
-     */
-    protected $skipOptions = array(
-        'options',
-        'config',
-    );
-
-    /**
-     * Create a new cloud decorator with options
-     *
-     * @param  array|Traversable $options
-     */
-    public function __construct($options = null)
-    {
-        if ($options instanceof Traversable) {
-            $options = ArrayUtils::iteratorToArray($options);
-        }
-        if (is_array($options)) {
-            $this->setOptions($options);
-        }
-    }
-
-    /**
-     * Set options from array
-     *
-     * @param  array $options Configuration for the decorator
-     * @return AbstractTag
-     */
-    public function setOptions(array $options)
-    {
-        foreach ($options as $key => $value) {
-            if (in_array(strtolower($key), $this->skipOptions)) {
-                continue;
-            }
-
-            $method = 'set' . $key;
-            if (method_exists($this, $method)) {
-                $this->$method($value);
-            }
-        }
-
-        return $this;
-    }
 }
diff --git a/library/Zend/Tag/Cloud/Decorator/HtmlCloud.php b/library/Zend/Tag/Cloud/Decorator/HtmlCloud.php
index b0429469563..f352033dee9 100644
--- a/library/Zend/Tag/Cloud/Decorator/HtmlCloud.php
+++ b/library/Zend/Tag/Cloud/Decorator/HtmlCloud.php
@@ -18,11 +18,6 @@
  */
 class HtmlCloud extends AbstractCloud
 {
-    /**
-     * @var string Encoding to use
-     */
-    protected $encoding = 'UTF-8';
-
     /**
      * List of HTML tags
      *
@@ -39,28 +34,6 @@ class HtmlCloud extends AbstractCloud
      */
     protected $separator = ' ';
 
-    /**
-     * Get encoding
-     *
-     * @return string
-     */
-    public function getEncoding()
-    {
-        return $this->encoding;
-    }
-
-    /**
-     * Set encoding
-     *
-     * @param string
-     * @return HTMLCloud
-     */
-    public function setEncoding($value)
-    {
-        $this->encoding = (string) $value;
-        return $this;
-    }
-
     /**
      * Set the HTML tags surrounding all tags
      *
@@ -121,24 +94,7 @@ public function render($tags)
             ));
         }
         $cloudHTML = implode($this->getSeparator(), $tags);
-
-        $enc = $this->getEncoding();
-        foreach ($this->getHTMLTags() as $key => $data) {
-            if (is_array($data)) {
-                $htmlTag    = $key;
-                $attributes = '';
-
-                foreach ($data as $param => $value) {
-                    $attributes .= ' ' . $param . '="' . htmlspecialchars($value, ENT_COMPAT, $enc) . '"';
-                }
-            } else {
-                $htmlTag    = $data;
-                $attributes = '';
-            }
-
-            $cloudHTML = sprintf('<%1$s%3$s>%2$s', $htmlTag, $cloudHTML, $attributes);
-        }
-
+        $cloudHTML = $this->wrapTag($cloudHTML);
         return $cloudHTML;
     }
 }
diff --git a/library/Zend/Tag/Cloud/Decorator/HtmlTag.php b/library/Zend/Tag/Cloud/Decorator/HtmlTag.php
index de8b9a2e4d2..70327af0c95 100644
--- a/library/Zend/Tag/Cloud/Decorator/HtmlTag.php
+++ b/library/Zend/Tag/Cloud/Decorator/HtmlTag.php
@@ -29,11 +29,6 @@ class HtmlTag extends AbstractTag
      */
     protected $classList = null;
 
-    /**
-     * @var string Encoding to utilize
-     */
-    protected $encoding = 'UTF-8';
-
     /**
      * Unit for the fontsize
      *
@@ -107,28 +102,6 @@ public function getClassList()
         return $this->classList;
     }
 
-    /**
-     * Get encoding
-     *
-     * @return string
-     */
-    public function getEncoding()
-    {
-         return $this->encoding;
-    }
-
-    /**
-     * Set encoding
-     *
-     * @param  string $value
-     * @return HTMLTag
-     */
-    public function setEncoding($value)
-    {
-        $this->encoding = (string) $value;
-        return $this;
-    }
-
     /**
      * Set the font size unit
      *
@@ -259,32 +232,16 @@ public function render($tags)
 
         $result = array();
 
-        $enc = $this->getEncoding();
+        $escaper = $this->getEscaper();
         foreach ($tags as $tag) {
             if (null === ($classList = $this->getClassList())) {
                 $attribute = sprintf('style="font-size: %d%s;"', $tag->getParam('weightValue'), $this->getFontSizeUnit());
             } else {
-                $attribute = sprintf('class="%s"', htmlspecialchars($tag->getParam('weightValue'), ENT_COMPAT, $enc));
-            }
-
-            $tagHTML = sprintf('%s', htmlSpecialChars($tag->getParam('url'), ENT_COMPAT, $enc), $attribute, $tag->getTitle());
-
-            foreach ($this->getHTMLTags() as $key => $data) {
-                if (is_array($data)) {
-                    $htmlTag    = $key;
-                    $attributes = '';
-
-                    foreach ($data as $param => $value) {
-                        $attributes .= ' ' . $param . '="' . htmlspecialchars($value, ENT_COMPAT, $enc) . '"';
-                    }
-                } else {
-                    $htmlTag    = $data;
-                    $attributes = '';
-                }
-
-                $tagHTML = sprintf('<%1$s%3$s>%2$s', $htmlTag, $tagHTML, $attributes);
+                $attribute = sprintf('class="%s"', $escaper->escapeHtmlAttr($tag->getParam('weightValue')));
             }
 
+            $tagHTML  = sprintf('%s', $escaper->escapeHtml($tag->getParam('url')), $attribute, $escaper->escapeHtml($tag->getTitle()));
+            $tagHTML  = $this->wrapTag($tagHTML);
             $result[] = $tagHTML;
         }
 
diff --git a/library/Zend/Tag/Exception/InvalidAttributeNameException.php b/library/Zend/Tag/Exception/InvalidAttributeNameException.php
new file mode 100644
index 00000000000..41288292adc
--- /dev/null
+++ b/library/Zend/Tag/Exception/InvalidAttributeNameException.php
@@ -0,0 +1,16 @@
+=5.3.3"
+        "php": ">=5.3.3",
+        "zendframework/zend-escaper": "self.version"
     }
-}
\ No newline at end of file
+}
diff --git a/library/Zend/Uri/Uri.php b/library/Zend/Uri/Uri.php
index 6e700c8fc2a..4dfa5224f5e 100644
--- a/library/Zend/Uri/Uri.php
+++ b/library/Zend/Uri/Uri.php
@@ -10,6 +10,7 @@
 
 namespace Zend\Uri;
 
+use Zend\Escaper\Escaper;
 use Zend\Validator;
 
 /**
@@ -125,6 +126,11 @@ class Uri implements UriInterface
      */
     protected static $defaultPorts = array();
 
+    /**
+     * @var Escaper
+     */
+    protected static $escaper;
+
     /**
      * Create a new URI object
      *
@@ -152,6 +158,31 @@ public function __construct($uri = null)
         }
     }
 
+    /**
+     * Set Escaper instance
+     * 
+     * @param  Escaper $escaper 
+     */
+    public static function setEscaper(Escaper $escaper)
+    {
+        static::$escaper = $escaper;
+    }
+
+    /**
+     * Retrieve Escaper instance
+     *
+     * Lazy-loads one if none provided
+     * 
+     * @return Escaper
+     */
+    public static function getEscaper()
+    {
+        if (null === static::$escaper) {
+            static::setEscaper(new Escaper());
+        }
+        return static::$escaper;
+    }
+
     /**
      * Check if the URI is valid
      *
@@ -935,8 +966,9 @@ public static function encodeUserInfo($userInfo)
         }
 
         $regex   = '/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:]|%(?![A-Fa-f0-9]{2}))/';
-        $replace = function($match) {
-            return rawurlencode($match[0]);
+        $escaper = static::getEscaper();
+        $replace = function ($match) use ($escaper) {
+            return $escaper->escapeUrl($match[0]);
         };
 
         return preg_replace_callback($regex, $replace, $userInfo);
@@ -962,8 +994,9 @@ public static function encodePath($path)
         }
 
         $regex   = '/(?:[^' . self::CHAR_UNRESERVED . ':@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/';
-        $replace = function($match) {
-            return rawurlencode($match[0]);
+        $escaper = static::getEscaper();
+        $replace = function ($match) use ($escaper) {
+            return $escaper->escapeUrl($match[0]);
         };
 
         return preg_replace_callback($regex, $replace, $path);
@@ -990,8 +1023,9 @@ public static function encodeQueryFragment($input)
         }
 
         $regex   = '/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/';
-        $replace = function($match) {
-            return rawurlencode($match[0]);
+        $escaper = static::getEscaper();
+        $replace = function ($match) use ($escaper) {
+            return $escaper->escapeUrl($match[0]);
         };
 
         return preg_replace_callback($regex, $replace, $input);
diff --git a/library/Zend/Uri/composer.json b/library/Zend/Uri/composer.json
index 52fd0a34967..ae5e2cbcf3c 100644
--- a/library/Zend/Uri/composer.json
+++ b/library/Zend/Uri/composer.json
@@ -14,6 +14,7 @@
     "target-dir": "Zend/Uri",
     "require": {
         "php": ">=5.3.3",
+        "zendframework/zend-escaper": "self.version",
         "zendframework/zend-validator": "self.version"
     }
-}
\ No newline at end of file
+}
diff --git a/library/Zend/View/Helper/HeadStyle.php b/library/Zend/View/Helper/HeadStyle.php
index 5a400b76e87..369dd99fc7a 100644
--- a/library/Zend/View/Helper/HeadStyle.php
+++ b/library/Zend/View/Helper/HeadStyle.php
@@ -311,6 +311,7 @@ public function itemToString(stdClass $item, $indent)
             ) {
                 $enc = $this->view->getEncoding();
             }
+            $escaper = $this->getEscaper($enc);
             foreach ($item->attributes as $key => $value) {
                 if (!in_array($key, $this->optionalAttributes)) {
                     continue;
@@ -333,7 +334,7 @@ public function itemToString(stdClass $item, $indent)
                         $value = substr($value, 0, -1);
                     }
                 }
-                $attrString .= sprintf(' %s="%s"', $key, htmlspecialchars($value, ENT_COMPAT, $enc));
+                $attrString .= sprintf(' %s="%s"', $key, $escaper->escapeHtmlAttr($value));
             }
         }
 
diff --git a/library/Zend/View/Helper/Navigation/Sitemap.php b/library/Zend/View/Helper/Navigation/Sitemap.php
index 37da30f51f5..f55d4d5ccad 100644
--- a/library/Zend/View/Helper/Navigation/Sitemap.php
+++ b/library/Zend/View/Helper/Navigation/Sitemap.php
@@ -242,14 +242,8 @@ public function getServerUrl()
      */
     protected function xmlEscape($string)
     {
-        $enc = 'UTF-8';
-        if ($this->view instanceof View\Renderer\RendererInterface
-            && method_exists($this->view, 'getEncoding')
-        ) {
-            $enc = $this->view->getEncoding();
-        }
-
-        return htmlspecialchars($string, ENT_QUOTES, $enc, false);
+        $escaper = $this->view->plugin('escapeHtml');
+        return $escaper($string);
     }
 
     // Public methods:
diff --git a/library/Zend/View/Helper/Placeholder/Container/AbstractStandalone.php b/library/Zend/View/Helper/Placeholder/Container/AbstractStandalone.php
index 8017f516113..61ba616f2a2 100644
--- a/library/Zend/View/Helper/Placeholder/Container/AbstractStandalone.php
+++ b/library/Zend/View/Helper/Placeholder/Container/AbstractStandalone.php
@@ -10,8 +10,10 @@
 
 namespace Zend\View\Helper\Placeholder\Container;
 
+use Zend\Escaper\Escaper;
 use Zend\View\Exception;
 use Zend\View\Helper\Placeholder\Registry;
+use Zend\View\Renderer\RendererInterface;
 
 /**
  * Base class for targeted placeholder helpers
@@ -28,6 +30,11 @@ abstract class AbstractStandalone
      */
     protected $container;
 
+    /**
+     * @var Escaper[]
+     */
+    protected $escapers = array();
+
     /**
      * @var \Zend\View\Helper\Placeholder\Registry
      */
@@ -78,6 +85,35 @@ public function setRegistry(Registry $registry)
         return $this;
     }
 
+    /**
+     * Set Escaper instance
+     *
+     * @param  Escaper $escaper
+     * @return AbstractStandalone
+     */
+    public function setEscaper(Escaper $escaper)
+    {
+        $encoding = $escaper->getEncoding();
+        $this->escapers[$encoding] = $escaper;
+        return $this;
+    }
+    
+    /**
+     * Get Escaper instance
+     *
+     * Lazy-loads one if none available
+     *
+     * @return mixed
+     */
+    public function getEscaper($enc = 'UTF-8')
+    {
+        $enc = strtolower($enc);
+        if (!isset($this->escapers[$enc])) {
+            $this->setEscaper(new Escaper($enc));
+        }
+        return $this->escapers[$enc];
+    }
+
     /**
      * Set whether or not auto escaping should be used
      *
@@ -108,23 +144,16 @@ public function getAutoEscape()
      */
     protected function escape($string)
     {
-        $enc = 'UTF-8';
-        if ($this->view instanceof \Zend\View\Renderer\RendererInterface
+        if ($this->view instanceof RendererInterface
             && method_exists($this->view, 'getEncoding')
         ) {
-            $enc = $this->view->getEncoding();
+            $enc     = $this->view->getEncoding();
             $escaper = $this->view->plugin('escapeHtml');
             return $escaper((string) $string);
         }
-        /**
-         * bump this out to a protected method to kill the instance penalty!
-         */
-        $escaper = new \Zend\Escaper\Escaper($enc);
+
+        $escaper = $this->getEscaper();
         return $escaper->escapeHtml((string) $string);
-        /**
-         * Replaced to ensure consistent escaping
-         */
-        //return htmlspecialchars((string) $string, ENT_COMPAT, $enc);
     }
 
     /**
diff --git a/tests/ZendTest/Tag/Cloud/CloudTest.php b/tests/ZendTest/Tag/Cloud/CloudTest.php
index b22690bb6a9..0b8b07438bc 100644
--- a/tests/ZendTest/Tag/Cloud/CloudTest.php
+++ b/tests/ZendTest/Tag/Cloud/CloudTest.php
@@ -208,7 +208,7 @@ public function testSkipOptions()
     public function testRender()
     {
         $cloud    = $this->_getCloud(array('tags' => array(array('title' => 'foo', 'weight' => 1), array('title' => 'bar', 'weight' => 3))));
-        $expected = '