Skip to content
This repository
Browse code

Merge branch 'security/escaper-usage' into develop

Forward port from master.

Fixes a number of components that were not using Zend\Escaper to escape HTML,
HTML attributes, and/or URLs.
  • Loading branch information...
commit f4de057409e28f201d441934452217336ca862ad 2 parents cbdd61b + 07d847b
Matthew Weier O'Phinney weierophinney authored

Showing 22 changed files with 579 additions and 275 deletions. Show diff stats Hide diff stats

  1. +32 1 library/Zend/Debug/Debug.php
  2. +6 2 library/Zend/Debug/composer.json
  3. +41 7 library/Zend/Feed/PubSubHubbub/PubSubHubbub.php
  4. +1 0  library/Zend/Feed/composer.json
  5. +38 4 library/Zend/Log/Formatter/Xml.php
  6. +1 0  library/Zend/Log/composer.json
  7. +1 51 library/Zend/Tag/Cloud/Decorator/AbstractCloud.php
  8. +190 0 library/Zend/Tag/Cloud/Decorator/AbstractDecorator.php
  9. +1 51 library/Zend/Tag/Cloud/Decorator/AbstractTag.php
  10. +1 45 library/Zend/Tag/Cloud/Decorator/HtmlCloud.php
  11. +4 47 library/Zend/Tag/Cloud/Decorator/HtmlTag.php
  12. +16 0 library/Zend/Tag/Exception/InvalidAttributeNameException.php
  13. +16 0 library/Zend/Tag/Exception/InvalidElementNameException.php
  14. +3 2 library/Zend/Tag/composer.json
  15. +40 6 library/Zend/Uri/Uri.php
  16. +2 1  library/Zend/Uri/composer.json
  17. +2 1  library/Zend/View/Helper/HeadStyle.php
  18. +2 8 library/Zend/View/Helper/Navigation/Sitemap.php
  19. +40 11 library/Zend/View/Helper/Placeholder/Container/AbstractStandalone.php
  20. +2 2 tests/ZendTest/Tag/Cloud/CloudTest.php
  21. +58 3 tests/ZendTest/Tag/Cloud/Decorator/HtmlCloudTest.php
  22. +82 33 tests/ZendTest/Tag/Cloud/Decorator/HtmlTagTest.php
33 library/Zend/Debug/Debug.php
@@ -10,6 +10,8 @@
10 10
11 11 namespace Zend\Debug;
12 12
  13 +use Zend\Escaper\Escaper;
  14 +
13 15 /**
14 16 * Concrete class for generating debug dumps related to the output source.
15 17 *
@@ -18,6 +20,10 @@
18 20 */
19 21 class Debug
20 22 {
  23 + /**
  24 + * @var Escaper
  25 + */
  26 + protected static $escaper = null;
21 27
22 28 /**
23 29 * @var string
@@ -51,6 +57,31 @@ public static function setSapi($sapi)
51 57 }
52 58
53 59 /**
  60 + * Set Escaper instance
  61 + *
  62 + * @param Escaper $escaper
  63 + */
  64 + public static function setEscaper(Escaper $escaper)
  65 + {
  66 + static::$escaper = $escaper;
  67 + }
  68 +
  69 + /**
  70 + * Get Escaper instance
  71 + *
  72 + * Lazy loads an instance if none provided.
  73 + *
  74 + * @return Escaper
  75 + */
  76 + public static function getEscaper()
  77 + {
  78 + if (null === static::$escaper) {
  79 + static::setEscaper(new Escaper());
  80 + }
  81 + return static::$escaper;
  82 + }
  83 +
  84 + /**
54 85 * Debug helper function. This is a wrapper for var_dump() that adds
55 86 * the <pre /> tags, cleans up newlines and indents, and runs
56 87 * htmlentities() before output.
@@ -78,7 +109,7 @@ public static function dump($var, $label=null, $echo=true)
78 109 . PHP_EOL;
79 110 } else {
80 111 if (!extension_loaded('xdebug')) {
81   - $output = htmlspecialchars($output, ENT_QUOTES);
  112 + $output = static::getEscaper()->escapeHtml($output);
82 113 }
83 114
84 115 $output = '<pre>'
8 library/Zend/Debug/composer.json
@@ -13,6 +13,10 @@
13 13 },
14 14 "target-dir": "Zend/Debug",
15 15 "require": {
16   - "php": ">=5.3.3"
  16 + "php": ">=5.3.3",
  17 + "zendframework/zend-escaper": "self.version"
  18 + },
  19 + "suggest": {
  20 + "ext/xdebug": "XDebug, for better backtrace output"
17 21 }
18   -}
  22 +}
48 library/Zend/Feed/PubSubHubbub/PubSubHubbub.php
@@ -10,6 +10,7 @@
10 10
11 11 namespace Zend\Feed\PubSubHubbub;
12 12
  13 +use Zend\Escaper\Escaper;
13 14 use Zend\Feed\Reader;
14 15 use Zend\Http;
15 16
@@ -33,9 +34,14 @@ class PubSubHubbub
33 34 const SUBSCRIPTION_TODELETE = 'to_delete';
34 35
35 36 /**
  37 + * @var Escaper
  38 + */
  39 + protected static $escaper;
  40 +
  41 + /**
36 42 * Singleton instance if required of the HTTP client
37 43 *
38   - * @var \Zend\Http\Client
  44 + * @var Http\Client
39 45 */
40 46 protected static $httpClient = null;
41 47
@@ -67,7 +73,7 @@ public static function detectHubs($source)
67 73 * Allows the external environment to make Zend_Oauth use a specific
68 74 * Client instance.
69 75 *
70   - * @param \Zend\Http\Client $httpClient
  76 + * @param Http\Client $httpClient
71 77 * @return void
72 78 */
73 79 public static function setHttpClient(Http\Client $httpClient)
@@ -80,15 +86,15 @@ public static function setHttpClient(Http\Client $httpClient)
80 86 * the instance is reset and cleared of previous parameters GET/POST.
81 87 * Headers are NOT reset but handled by this component if applicable.
82 88 *
83   - * @return \Zend\Http\Client
  89 + * @return Http\Client
84 90 */
85 91 public static function getHttpClient()
86 92 {
87   - if (!isset(self::$httpClient)):
  93 + if (!isset(self::$httpClient)) {
88 94 self::$httpClient = new Http\Client;
89   - else:
  95 + } else {
90 96 self::$httpClient->resetParameters();
91   - endif;
  97 + }
92 98 return self::$httpClient;
93 99 }
94 100
@@ -104,6 +110,33 @@ public static function clearHttpClient()
104 110 }
105 111
106 112 /**
  113 + * Set the Escaper instance
  114 + *
  115 + * If null, resets the instance
  116 + *
  117 + * @param null|Escaper $escaper
  118 + */
  119 + public static function setEscaper(Escaper $escaper = null)
  120 + {
  121 + static::$escaper = $escaper;
  122 + }
  123 +
  124 + /**
  125 + * Get the Escaper instance
  126 + *
  127 + * If none registered, lazy-loads an instance.
  128 + *
  129 + * @return Escaper
  130 + */
  131 + public static function getEscaper()
  132 + {
  133 + if (null === static::$escaper) {
  134 + static::setEscaper(new Escaper());
  135 + }
  136 + return static::$escaper;
  137 + }
  138 +
  139 + /**
107 140 * RFC 3986 safe url encoding method
108 141 *
109 142 * @param string $string
@@ -111,7 +144,8 @@ public static function clearHttpClient()
111 144 */
112 145 public static function urlencode($string)
113 146 {
114   - $rawencoded = rawurlencode($string);
  147 + $escaper = static::getEscaper();
  148 + $rawencoded = $escaper->escapeUrl($string);
115 149 $rfcencoded = str_replace('%7E', '~', $rawencoded);
116 150 return $rfcencoded;
117 151 }
1  library/Zend/Feed/composer.json
@@ -14,6 +14,7 @@
14 14 "target-dir": "Zend/Feed",
15 15 "require": {
16 16 "php": ">=5.3.3",
  17 + "zendframework/zend-escaper": "self.version",
17 18 "zendframework/zend-stdlib": "self.version"
18 19 },
19 20 "suggest": {
42 library/Zend/Log/Formatter/Xml.php
@@ -14,6 +14,7 @@
14 14 use DOMDocument;
15 15 use DOMElement;
16 16 use Traversable;
  17 +use Zend\Escaper\Escaper;
17 18 use Zend\Stdlib\ArrayUtils;
18 19
19 20 /**
@@ -39,6 +40,11 @@ class Xml implements FormatterInterface
39 40 protected $encoding;
40 41
41 42 /**
  43 + * @var Escaper instance
  44 + */
  45 + protected $escaper;
  46 +
  47 + /**
42 48 * Format specifier for DateTime objects in event data (default: ISO 8601)
43 49 *
44 50 * @see http://php.net/manual/en/function.date.php
@@ -122,6 +128,33 @@ public function setEncoding($value)
122 128 }
123 129
124 130 /**
  131 + * Set Escaper instance
  132 + *
  133 + * @param Escaper $escaper
  134 + * @return Xml
  135 + */
  136 + public function setEscaper(Escaper $escaper)
  137 + {
  138 + $this->escaper = $escaper;
  139 + return $this;
  140 + }
  141 +
  142 + /**
  143 + * Get Escaper instance
  144 + *
  145 + * Lazy-loads an instance with the current encoding if none registered.
  146 + *
  147 + * @return Escaper
  148 + */
  149 + public function getEscaper()
  150 + {
  151 + if (null === $this->escaper) {
  152 + $this->setEscaper(new Escaper($this->getEncoding()));
  153 + }
  154 + return $this->escaper;
  155 + }
  156 +
  157 + /**
125 158 * Formats data into a single line to be written by the writer.
126 159 *
127 160 * @param array $event event data
@@ -142,9 +175,10 @@ public function format($event)
142 175 }
143 176 }
144 177
145   - $enc = $this->getEncoding();
146   - $dom = new DOMDocument('1.0', $enc);
147   - $elt = $dom->appendChild(new DOMElement($this->rootElement));
  178 + $enc = $this->getEncoding();
  179 + $escaper = $this->getEscaper();
  180 + $dom = new DOMDocument('1.0', $enc);
  181 + $elt = $dom->appendChild(new DOMElement($this->rootElement));
148 182
149 183 foreach ($dataToInsert as $key => $value) {
150 184 if (empty($value)
@@ -152,7 +186,7 @@ public function format($event)
152 186 || (is_object($value) && method_exists($value,'__toString'))
153 187 ) {
154 188 if ($key == "message") {
155   - $value = htmlspecialchars($value, ENT_COMPAT, $enc);
  189 + $value = $escaper->escapeHtml($value);
156 190 } elseif ($key == "extra" && empty($value)) {
157 191 continue;
158 192 }
1  library/Zend/Log/composer.json
@@ -20,6 +20,7 @@
20 20 "suggest": {
21 21 "ext-mongo": "*",
22 22 "zendframework/zend-db": "Zend\\Db component",
  23 + "zendframework/zend-escaper": "Zend\\Escaper component, for use in the XML formatter",
23 24 "zendframework/zend-mail": "Zend\\Mail component",
24 25 "zendframework/zend-validator": "Zend\\Validator component"
25 26 }
52 library/Zend/Tag/Cloud/Decorator/AbstractCloud.php
@@ -10,62 +10,12 @@
10 10
11 11 namespace Zend\Tag\Cloud\Decorator;
12 12
13   -use Traversable;
14   -use Zend\Stdlib\ArrayUtils;
15   -use Zend\Tag\Cloud\Decorator\DecoratorInterface as Decorator;
16   -
17 13 /**
18 14 * Abstract class for cloud decorators
19 15 *
20 16 * @category Zend
21 17 * @package Zend_Tag
22 18 */
23   -abstract class AbstractCloud implements Decorator
  19 +abstract class AbstractCloud extends AbstractDecorator
24 20 {
25   - /**
26   - * Option keys to skip when calling setOptions()
27   - *
28   - * @var array
29   - */
30   - protected $skipOptions = array(
31   - 'options',
32   - 'config',
33   - );
34   -
35   - /**
36   - * Create a new cloud decorator with options
37   - *
38   - * @param array|Traversable $options
39   - */
40   - public function __construct($options = null)
41   - {
42   - if ($options instanceof Traversable) {
43   - $options = ArrayUtils::iteratorToArray($options);
44   - }
45   - if (is_array($options)) {
46   - $this->setOptions($options);
47   - }
48   - }
49   -
50   - /**
51   - * Set options from array
52   - *
53   - * @param array $options Configuration for the decorator
54   - * @return AbstractCloud
55   - */
56   - public function setOptions(array $options)
57   - {
58   - foreach ($options as $key => $value) {
59   - if (in_array(strtolower($key), $this->skipOptions)) {
60   - continue;
61   - }
62   -
63   - $method = 'set' . $key;
64   - if (method_exists($this, $method)) {
65   - $this->$method($value);
66   - }
67   - }
68   -
69   - return $this;
70   - }
71 21 }
190 library/Zend/Tag/Cloud/Decorator/AbstractDecorator.php
... ... @@ -0,0 +1,190 @@
  1 +<?php
  2 +/**
  3 + * Zend Framework (http://framework.zend.com/)
  4 + *
  5 + * @link http://github.com/zendframework/zf2 for the canonical source repository
  6 + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
  7 + * @license http://framework.zend.com/license/new-bsd New BSD License
  8 + * @package Zend_Tag
  9 + */
  10 +
  11 +namespace Zend\Tag\Cloud\Decorator;
  12 +
  13 +use Traversable;
  14 +use Zend\Escaper\Escaper;
  15 +use Zend\Stdlib\ArrayUtils;
  16 +use Zend\Tag\Cloud\Decorator\DecoratorInterface as Decorator;
  17 +use Zend\Tag\Exception;
  18 +
  19 +/**
  20 + * Abstract class for decorators
  21 + *
  22 + * @category Zend
  23 + * @package Zend_Tag
  24 + */
  25 +abstract class AbstractDecorator implements Decorator
  26 +{
  27 + /**
  28 + * @var string Encoding to use
  29 + */
  30 + protected $encoding = 'UTF-8';
  31 +
  32 + /**
  33 + * @var Escaper
  34 + */
  35 + protected $escaper;
  36 +
  37 + /**
  38 + * Option keys to skip when calling setOptions()
  39 + *
  40 + * @var array
  41 + */
  42 + protected $skipOptions = array(
  43 + 'options',
  44 + 'config',
  45 + );
  46 +
  47 + /**
  48 + * Create a new decorator with options
  49 + *
  50 + * @param array|Traversable $options
  51 + */
  52 + public function __construct($options = null)
  53 + {
  54 + if ($options instanceof Traversable) {
  55 + $options = ArrayUtils::iteratorToArray($options);
  56 + }
  57 + if (is_array($options)) {
  58 + $this->setOptions($options);
  59 + }
  60 + }
  61 +
  62 + /**
  63 + * Set options from array
  64 + *
  65 + * @param array $options Configuration for the decorator
  66 + * @return AbstractTag
  67 + */
  68 + public function setOptions(array $options)
  69 + {
  70 + foreach ($options as $key => $value) {
  71 + if (in_array(strtolower($key), $this->skipOptions)) {
  72 + continue;
  73 + }
  74 +
  75 + $method = 'set' . $key;
  76 + if (method_exists($this, $method)) {
  77 + $this->$method($value);
  78 + }
  79 + }
  80 +
  81 + return $this;
  82 + }
  83 +
  84 + /**
  85 + * Get encoding
  86 + *
  87 + * @return string
  88 + */
  89 + public function getEncoding()
  90 + {
  91 + return $this->encoding;
  92 + }
  93 +
  94 + /**
  95 + * Set encoding
  96 + *
  97 + * @param string
  98 + * @return HTMLCloud
  99 + */
  100 + public function setEncoding($value)
  101 + {
  102 + $this->encoding = (string) $value;
  103 + return $this;
  104 + }
  105 +
  106 + /**
  107 + * Set Escaper instance
  108 + *
  109 + * @param Escaper $escaper
  110 + * @return HtmlCloud
  111 + */
  112 + public function setEscaper($escaper)
  113 + {
  114 + $this->escaper = $escaper;
  115 + return $this;
  116 + }
  117 +
  118 + /**
  119 + * Retrieve Escaper instance
  120 + *
  121 + * If none registered, instantiates and registers one using current encoding.
  122 + *
  123 + * @return Escaper
  124 + */
  125 + public function getEscaper()
  126 + {
  127 + if (null === $this->escaper) {
  128 + $this->setEscaper(new Escaper($this->getEncoding()));
  129 + }
  130 + return $this->escaper;
  131 + }
  132 +
  133 + /**
  134 + * Validate an HTML element name
  135 + *
  136 + * @param string $name
  137 + * @throws Exception\InvalidElementNameException
  138 + */
  139 + protected function validateElementName($name)
  140 + {
  141 + if (!preg_match('/^[a-z0-9]+$/i', $name)) {
  142 + throw new Exception\InvalidElementNameException(sprintf(
  143 + '%s: Invalid element name "%s" provided; please provide valid HTML element names',
  144 + __METHOD__,
  145 + $this->getEscaper()->escapeHtml($name)
  146 + ));
  147 + }
  148 + }
  149 +
  150 + /**
  151 + * Validate an HTML attribute name
  152 + *
  153 + * @param string $name
  154 + * @throws Exception\InvalidAttributeNameException
  155 + */
  156 + protected function validateAttributeName($name)
  157 + {
  158 + if (!preg_match('/^[a-z_:][-a-z0-9_:.]*$/i', $name)) {
  159 + throw new Exception\InvalidAttributeNameException(sprintf(
  160 + '%s: Invalid HTML attribute name "%s" provided; please provide valid HTML attribute names',
  161 + __METHOD__,
  162 + $this->getEscaper()->escapeHtml($name)
  163 + ));
  164 + }
  165 + }
  166 +
  167 + protected function wrapTag($html)
  168 + {
  169 + $escaper = $this->getEscaper();
  170 + foreach ($this->getHTMLTags() as $key => $data) {
  171 + if (is_array($data)) {
  172 + $attributes = '';
  173 + $htmlTag = $key;
  174 + $this->validateElementName($htmlTag);
  175 +
  176 + foreach ($data as $param => $value) {
  177 + $this->validateAttributeName($param);
  178 + $attributes .= ' ' . $param . '="' . $escaper->escapeHtmlAttr($value) . '"';
  179 + }
  180 + } else {
  181 + $attributes = '';
  182 + $htmlTag = $data;
  183 + $this->validateElementName($htmlTag);
  184 + }
  185 +
  186 + $html = sprintf('<%1$s%3$s>%2$s</%1$s>', $htmlTag, $html, $attributes);
  187 + }
  188 + return $html;
  189 + }
  190 +}
52 library/Zend/Tag/Cloud/Decorator/AbstractTag.php
@@ -10,62 +10,12 @@
10 10
11 11 namespace Zend\Tag\Cloud\Decorator;
12 12
13   -use Traversable;
14   -use Zend\Stdlib\ArrayUtils;
15   -use Zend\Tag\Cloud\Decorator\DecoratorInterface as Decorator;
16   -
17 13 /**
18 14 * Abstract class for tag decorators
19 15 *
20 16 * @category Zend
21 17 * @package Zend_Tag
22 18 */
23   -abstract class AbstractTag implements Decorator
  19 +abstract class AbstractTag extends AbstractDecorator
24 20 {
25   - /**
26   - * Option keys to skip when calling setOptions()
27   - *
28   - * @var array
29   - */
30   - protected $skipOptions = array(
31   - 'options',
32   - 'config',
33   - );
34   -
35   - /**
36   - * Create a new cloud decorator with options
37   - *
38   - * @param array|Traversable $options
39   - */
40   - public function __construct($options = null)
41   - {
42   - if ($options instanceof Traversable) {
43   - $options = ArrayUtils::iteratorToArray($options);
44   - }
45   - if (is_array($options)) {
46   - $this->setOptions($options);
47   - }
48   - }
49   -
50   - /**
51   - * Set options from array
52   - *
53   - * @param array $options Configuration for the decorator
54   - * @return AbstractTag
55   - */
56   - public function setOptions(array $options)
57   - {
58   - foreach ($options as $key => $value) {
59   - if (in_array(strtolower($key), $this->skipOptions)) {
60   - continue;
61   - }
62   -
63   - $method = 'set' . $key;
64   - if (method_exists($this, $method)) {
65   - $this->$method($value);
66   - }
67   - }
68   -
69   - return $this;
70   - }
71 21 }
46 library/Zend/Tag/Cloud/Decorator/HtmlCloud.php
@@ -19,11 +19,6 @@
19 19 class HtmlCloud extends AbstractCloud
20 20 {
21 21 /**
22   - * @var string Encoding to use
23   - */
24   - protected $encoding = 'UTF-8';
25   -
26   - /**
27 22 * List of HTML tags
28 23 *
29 24 * @var array
@@ -40,28 +35,6 @@ class HtmlCloud extends AbstractCloud
40 35 protected $separator = ' ';
41 36
42 37 /**
43   - * Get encoding
44   - *
45   - * @return string
46   - */
47   - public function getEncoding()
48   - {
49   - return $this->encoding;
50   - }
51   -
52   - /**
53   - * Set encoding
54   - *
55   - * @param string
56   - * @return HTMLCloud
57   - */
58   - public function setEncoding($value)
59   - {
60   - $this->encoding = (string) $value;
61   - return $this;
62   - }
63   -
64   - /**
65 38 * Set the HTML tags surrounding all tags
66 39 *
67 40 * @param array $htmlTags
@@ -121,24 +94,7 @@ public function render($tags)
121 94 ));
122 95 }
123 96 $cloudHTML = implode($this->getSeparator(), $tags);
124   -
125   - $enc = $this->getEncoding();
126   - foreach ($this->getHTMLTags() as $key => $data) {
127   - if (is_array($data)) {
128   - $htmlTag = $key;
129   - $attributes = '';
130   -
131   - foreach ($data as $param => $value) {
132   - $attributes .= ' ' . $param . '="' . htmlspecialchars($value, ENT_COMPAT, $enc) . '"';
133   - }
134   - } else {
135   - $htmlTag = $data;
136   - $attributes = '';
137   - }
138   -
139   - $cloudHTML = sprintf('<%1$s%3$s>%2$s</%1$s>', $htmlTag, $cloudHTML, $attributes);
140   - }
141   -
  97 + $cloudHTML = $this->wrapTag($cloudHTML);
142 98 return $cloudHTML;
143 99 }
144 100 }
51 library/Zend/Tag/Cloud/Decorator/HtmlTag.php
@@ -30,11 +30,6 @@ class HtmlTag extends AbstractTag
30 30 protected $classList = null;
31 31
32 32 /**
33   - * @var string Encoding to utilize
34   - */
35   - protected $encoding = 'UTF-8';
36   -
37   - /**
38 33 * Unit for the fontsize
39 34 *
40 35 * @var string
@@ -108,28 +103,6 @@ public function getClassList()
108 103 }
109 104
110 105 /**
111   - * Get encoding
112   - *
113   - * @return string
114   - */
115   - public function getEncoding()
116   - {
117   - return $this->encoding;
118   - }
119   -
120   - /**
121   - * Set encoding
122   - *
123   - * @param string $value
124   - * @return HTMLTag
125   - */
126   - public function setEncoding($value)
127   - {
128   - $this->encoding = (string) $value;
129   - return $this;
130   - }
131   -
132   - /**
133 106 * Set the font size unit
134 107 *
135 108 * Possible values are: em, ex, px, in, cm, mm, pt, pc and %
@@ -259,32 +232,16 @@ public function render($tags)
259 232
260 233 $result = array();
261 234
262   - $enc = $this->getEncoding();
  235 + $escaper = $this->getEscaper();
263 236 foreach ($tags as $tag) {
264 237 if (null === ($classList = $this->getClassList())) {
265 238 $attribute = sprintf('style="font-size: %d%s;"', $tag->getParam('weightValue'), $this->getFontSizeUnit());
266 239 } else {
267   - $attribute = sprintf('class="%s"', htmlspecialchars($tag->getParam('weightValue'), ENT_COMPAT, $enc));
268   - }
269   -
270   - $tagHTML = sprintf('<a href="%s" %s>%s</a>', htmlSpecialChars($tag->getParam('url'), ENT_COMPAT, $enc), $attribute, $tag->getTitle());
271   -
272   - foreach ($this->getHTMLTags() as $key => $data) {
273   - if (is_array($data)) {
274   - $htmlTag = $key;
275   - $attributes = '';
276   -
277   - foreach ($data as $param => $value) {
278   - $attributes .= ' ' . $param . '="' . htmlspecialchars($value, ENT_COMPAT, $enc) . '"';
279   - }
280   - } else {
281   - $htmlTag = $data;
282   - $attributes = '';
283   - }
284   -
285   - $tagHTML = sprintf('<%1$s%3$s>%2$s</%1$s>', $htmlTag, $tagHTML, $attributes);
  240 + $attribute = sprintf('class="%s"', $escaper->escapeHtmlAttr($tag->getParam('weightValue')));
286 241 }
287 242
  243 + $tagHTML = sprintf('<a href="%s" %s>%s</a>', $escaper->escapeHtml($tag->getParam('url')), $attribute, $escaper->escapeHtml($tag->getTitle()));
  244 + $tagHTML = $this->wrapTag($tagHTML);
288 245 $result[] = $tagHTML;
289 246 }
290 247
16 library/Zend/Tag/Exception/InvalidAttributeNameException.php
... ... @@ -0,0 +1,16 @@
  1 +<?php
  2 +/**
  3 + * Zend Framework (http://framework.zend.com/)
  4 + *
  5 + * @link http://github.com/zendframework/zf2 for the canonical source repository
  6 + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
  7 + * @license http://framework.zend.com/license/new-bsd New BSD License
  8 + * @package Zend_Tag
  9 + */
  10 +
  11 +namespace Zend\Tag\Exception;
  12 +
  13 +use DomainException;
  14 +
  15 +class InvalidAttributeNameException extends DomainException implements ExceptionInterface
  16 +{}
16 library/Zend/Tag/Exception/InvalidElementNameException.php
... ... @@ -0,0 +1,16 @@
  1 +<?php
  2 +/**
  3 + * Zend Framework (http://framework.zend.com/)
  4 + *
  5 + * @link http://github.com/zendframework/zf2 for the canonical source repository
  6 + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
  7 + * @license http://framework.zend.com/license/new-bsd New BSD License
  8 + * @package Zend_Tag
  9 + */
  10 +
  11 +namespace Zend\Tag\Exception;
  12 +
  13 +use DomainException;
  14 +
  15 +class InvalidElementNameException extends DomainException implements ExceptionInterface
  16 +{}
5 library/Zend/Tag/composer.json
@@ -13,6 +13,7 @@
13 13 },
14 14 "target-dir": "Zend/Tag",
15 15 "require": {
16   - "php": ">=5.3.3"
  16 + "php": ">=5.3.3",
  17 + "zendframework/zend-escaper": "self.version"
17 18 }
18   -}
  19 +}
46 library/Zend/Uri/Uri.php
@@ -10,6 +10,7 @@
10 10
11 11 namespace Zend\Uri;
12 12
  13 +use Zend\Escaper\Escaper;
13 14 use Zend\Validator;
14 15
15 16 /**
@@ -126,6 +127,11 @@ class Uri implements UriInterface
126 127 protected static $defaultPorts = array();
127 128
128 129 /**
  130 + * @var Escaper
  131 + */
  132 + protected static $escaper;
  133 +
  134 + /**
129 135 * Create a new URI object
130 136 *
131 137 * @param Uri|string|null $uri
@@ -153,6 +159,31 @@ public function __construct($uri = null)
153 159 }
154 160
155 161 /**
  162 + * Set Escaper instance
  163 + *
  164 + * @param Escaper $escaper
  165 + */
  166 + public static function setEscaper(Escaper $escaper)
  167 + {
  168 + static::$escaper = $escaper;
  169 + }
  170 +
  171 + /**
  172 + * Retrieve Escaper instance
  173 + *
  174 + * Lazy-loads one if none provided
  175 + *
  176 + * @return Escaper
  177 + */
  178 + public static function getEscaper()
  179 + {
  180 + if (null === static::$escaper) {
  181 + static::setEscaper(new Escaper());
  182 + }
  183 + return static::$escaper;
  184 + }
  185 +
  186 + /**
156 187 * Check if the URI is valid
157 188 *
158 189 * Note that a relative URI may still be valid
@@ -935,8 +966,9 @@ public static function encodeUserInfo($userInfo)
935 966 }
936 967
937 968 $regex = '/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:]|%(?![A-Fa-f0-9]{2}))/';
938   - $replace = function($match) {
939   - return rawurlencode($match[0]);
  969 + $escaper = static::getEscaper();
  970 + $replace = function ($match) use ($escaper) {
  971 + return $escaper->escapeUrl($match[0]);
940 972 };
941 973
942 974 return preg_replace_callback($regex, $replace, $userInfo);
@@ -962,8 +994,9 @@ public static function encodePath($path)
962 994 }
963 995
964 996 $regex = '/(?:[^' . self::CHAR_UNRESERVED . ':@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/';
965   - $replace = function($match) {
966   - return rawurlencode($match[0]);
  997 + $escaper = static::getEscaper();
  998 + $replace = function ($match) use ($escaper) {
  999 + return $escaper->escapeUrl($match[0]);
967 1000 };
968 1001
969 1002 return preg_replace_callback($regex, $replace, $path);
@@ -990,8 +1023,9 @@ public static function encodeQueryFragment($input)
990 1023 }
991 1024
992 1025 $regex = '/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/';
993   - $replace = function($match) {
994   - return rawurlencode($match[0]);
  1026 + $escaper = static::getEscaper();
  1027 + $replace = function ($match) use ($escaper) {
  1028 + return $escaper->escapeUrl($match[0]);
995 1029 };
996 1030
997 1031 return preg_replace_callback($regex, $replace, $input);
3  library/Zend/Uri/composer.json
@@ -14,6 +14,7 @@
14 14 "target-dir": "Zend/Uri",
15 15 "require": {
16 16 "php": ">=5.3.3",
  17 + "zendframework/zend-escaper": "self.version",
17 18 "zendframework/zend-validator": "self.version"
18 19 }
19   -}
  20 +}
3  library/Zend/View/Helper/HeadStyle.php
@@ -311,6 +311,7 @@ public function itemToString(stdClass $item, $indent)
311 311 ) {
312 312 $enc = $this->view->getEncoding();
313 313 }
  314 + $escaper = $this->getEscaper($enc);
314 315 foreach ($item->attributes as $key => $value) {
315 316 if (!in_array($key, $this->optionalAttributes)) {
316 317 continue;
@@ -333,7 +334,7 @@ public function itemToString(stdClass $item, $indent)
333 334 $value = substr($value, 0, -1);
334 335 }
335 336 }
336   - $attrString .= sprintf(' %s="%s"', $key, htmlspecialchars($value, ENT_COMPAT, $enc));
  337 + $attrString .= sprintf(' %s="%s"', $key, $escaper->escapeHtmlAttr($value));
337 338 }
338 339 }
339 340
10 library/Zend/View/Helper/Navigation/Sitemap.php
@@ -242,14 +242,8 @@ public function getServerUrl()
242 242 */
243 243 protected function xmlEscape($string)
244 244 {
245   - $enc = 'UTF-8';
246   - if ($this->view instanceof View\Renderer\RendererInterface
247   - && method_exists($this->view, 'getEncoding')
248   - ) {
249   - $enc = $this->view->getEncoding();
250   - }
251   -
252   - return htmlspecialchars($string, ENT_QUOTES, $enc, false);
  245 + $escaper = $this->view->plugin('escapeHtml');
  246 + return $escaper($string);
253 247 }
254 248
255 249 // Public methods:
51 library/Zend/View/Helper/Placeholder/Container/AbstractStandalone.php
@@ -10,8 +10,10 @@
10 10
11 11 namespace Zend\View\Helper\Placeholder\Container;
12 12
  13 +use Zend\Escaper\Escaper;
13 14 use Zend\View\Exception;
14 15 use Zend\View\Helper\Placeholder\Registry;
  16 +use Zend\View\Renderer\RendererInterface;
15 17
16 18 /**
17 19 * Base class for targeted placeholder helpers
@@ -29,6 +31,11 @@
29 31 protected $container;
30 32
31 33 /**
  34 + * @var Escaper[]
  35 + */
  36 + protected $escapers = array();
  37 +
  38 + /**
32 39 * @var \Zend\View\Helper\Placeholder\Registry
33 40 */
34 41 protected $registry;
@@ -79,6 +86,35 @@ public function setRegistry(Registry $registry)
79 86 }
80 87
81 88 /**
  89 + * Set Escaper instance
  90 + *
  91 + * @param Escaper $escaper
  92 + * @return AbstractStandalone
  93 + */
  94 + public function setEscaper(Escaper $escaper)
  95 + {
  96 + $encoding = $escaper->getEncoding();
  97 + $this->escapers[$encoding] = $escaper;
  98 + return $this;
  99 + }
  100 +
  101 + /**
  102 + * Get Escaper instance
  103 + *
  104 + * Lazy-loads one if none available
  105 + *
  106 + * @return mixed
  107 + */
  108 + public function getEscaper($enc = 'UTF-8')
  109 + {
  110 + $enc = strtolower($enc);
  111 + if (!isset($this->escapers[$enc])) {
  112 + $this->setEscaper(new Escaper($enc));
  113 + }
  114 + return $this->escapers[$enc];
  115 + }
  116 +
  117 + /**
82 118 * Set whether or not auto escaping should be used
83 119 *
84 120 * @param bool $autoEscape whether or not to auto escape output
@@ -108,23 +144,16 @@ public function getAutoEscape()
108 144 */
109 145 protected function escape($string)
110 146 {
111   - $enc = 'UTF-8';
112   - if ($this->view instanceof \Zend\View\Renderer\RendererInterface
  147 + if ($this->view instanceof RendererInterface
113 148 && method_exists($this->view, 'getEncoding')
114 149 ) {
115   - $enc = $this->view->getEncoding();
  150 + $enc = $this->view->getEncoding();
116 151 $escaper = $this->view->plugin('escapeHtml');
117 152 return $escaper((string) $string);
118 153 }
119   - /**
120   - * bump this out to a protected method to kill the instance penalty!
121   - */
122   - $escaper = new \Zend\Escaper\Escaper($enc);
  154 +
  155 + $escaper = $this->getEscaper();
123 156 return $escaper->escapeHtml((string) $string);
124   - /**
125   - * Replaced to ensure consistent escaping
126   - */
127   - //return htmlspecialchars((string) $string, ENT_COMPAT, $enc);
128 157 }
129 158
130 159 /**
4 tests/ZendTest/Tag/Cloud/CloudTest.php
@@ -208,7 +208,7 @@ public function testSkipOptions()
208 208 public function testRender()
209 209 {
210 210 $cloud = $this->_getCloud(array('tags' => array(array('title' => 'foo', 'weight' => 1), array('title' => 'bar', 'weight' => 3))));
211   - $expected = '<ul class="Zend\Tag\Cloud">'
  211 + $expected = '<ul class="Zend&#x5C;Tag&#x5C;Cloud">'
212 212 . '<li><a href="" style="font-size: 10px;">foo</a></li> '
213 213 . '<li><a href="" style="font-size: 20px;">bar</a></li>'
214 214 . '</ul>';
@@ -224,7 +224,7 @@ public function testRenderEmptyCloud()
224 224 public function testRenderViaToString()
225 225 {
226 226 $cloud = $this->_getCloud(array('tags' => array(array('title' => 'foo', 'weight' => 1), array('title' => 'bar', 'weight' => 3))));
227   - $expected = '<ul class="Zend\Tag\Cloud">'
  227 + $expected = '<ul class="Zend&#x5C;Tag&#x5C;Cloud">'
228 228 . '<li><a href="" style="font-size: 10px;">foo</a></li> '
229 229 . '<li><a href="" style="font-size: 20px;">bar</a></li>'
230 230 . '</ul>';
61 tests/ZendTest/Tag/Cloud/Decorator/HtmlCloudTest.php
@@ -25,7 +25,7 @@ public function testDefaultOutput()
25 25 {
26 26 $decorator = new Decorator\HtmlCloud();
27 27
28   - $this->assertEquals('<ul class="Zend\Tag\Cloud">foo bar</ul>', $decorator->render(array('foo', 'bar')));
  28 + $this->assertEquals('<ul class="Zend&#x5C;Tag&#x5C;Cloud">foo bar</ul>', $decorator->render(array('foo', 'bar')));
29 29 }
30 30
31 31 public function testNestedTags()
@@ -41,7 +41,7 @@ public function testSeparator()
41 41 $decorator = new Decorator\HtmlCloud();
42 42 $decorator->setSeparator('-');
43 43
44   - $this->assertEquals('<ul class="Zend\Tag\Cloud">foo-bar</ul>', $decorator->render(array('foo', 'bar')));
  44 + $this->assertEquals('<ul class="Zend&#x5C;Tag&#x5C;Cloud">foo-bar</ul>', $decorator->render(array('foo', 'bar')));
45 45 }
46 46
47 47 public function testConstructorWithArray()
@@ -71,5 +71,60 @@ public function testSkipOptions()
71 71 $decorator = new Decorator\HtmlCloud(array('options' => 'foobar'));
72 72 // In case would fail due to an error
73 73 }
74   -}
75 74
  75 + public function invalidHtmlTagProvider()
  76 + {
  77 + return array(
  78 + array(array('_foo')),
  79 + array(array('&foo')),
  80 + array(array(' foo')),
  81 + array(array(' foo')),
  82 + array(array(
  83 + '_foo' => array(),
  84 + )),
  85 + );
  86 + }
  87 +
  88 + /**
  89 + * @dataProvider invalidHtmlTagProvider
  90 + */
  91 + public function testInvalidHtmlTagsRaiseAnException($tags)
  92 + {
  93 + $decorator = new Decorator\HtmlCloud();
  94 + $decorator->setHTMLTags($tags);
  95 + $this->setExpectedException('Zend\Tag\Exception\InvalidElementNameException');
  96 + $decorator->render(array());
  97 + }
  98 +
  99 + public function invalidAttributeProvider()
  100 + {
  101 + return array(
  102 + array(array(
  103 + 'foo' => array(
  104 + '&bar' => 'baz',
  105 + ),
  106 + )),
  107 + array(array(
  108 + 'foo' => array(
  109 + ':bar&baz' => 'bat',
  110 + ),
  111 + )),
  112 + array(array(
  113 + 'foo' => array(
  114 + 'bar/baz' => 'bat',
  115 + ),