AbstractFlashRestriction   A
last analyzed

Complexity

Total Complexity 35

Size/Duplication

Total Lines 265
Duplicated Lines 0 %

Test Coverage

Coverage 97.22%

Importance

Changes 0
Metric Value
wmc 35
eloc 68
dl 0
loc 265
ccs 70
cts 72
cp 0.9722
rs 9.6
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A check() 0 5 1
A checkDynamicParams() 0 10 4
A getElements() 0 12 4
A checkEmbeds() 0 9 2
A __construct() 0 4 1
A checkSetting() 0 19 4
A checkObjects() 0 16 4
A checkAttributes() 0 16 4
A getObjectParams() 0 14 4
A isDynamic() 0 21 4
A checkDynamicAttributes() 0 9 3
1
<?php
2
3
/**
4
* @package   s9e\TextFormatter
5
* @copyright Copyright (c) The s9e authors
6
* @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7
*/
8
namespace s9e\TextFormatter\Configurator\TemplateChecks;
9
10
use DOMElement;
11
use DOMNode;
12
use DOMXPath;
13
use s9e\TextFormatter\Configurator\Exceptions\UnsafeTemplateException;
14
use s9e\TextFormatter\Configurator\Items\Tag;
15
use s9e\TextFormatter\Configurator\TemplateCheck;
16
17
/**
18
* NOTE: when this check is enabled, DisallowObjectParamsWithGeneratedName should be enabled too.
19
*       Otherwise, <param/> elements with a dynamic 'name' attribute could be used to bypass this
20
*       restriction. For the same reason, DisallowCopy, DisallowDisableOutputEscaping,
21
*       DisallowDynamicAttributeNames, DisallowDynamicElementNames and DisallowUnsafeCopyOf should
22
*       all be enabled too
23
*/
24
abstract class AbstractFlashRestriction extends TemplateCheck
25
{
26
	/**
27
	* @var string Name of the default setting
28
	*/
29
	public $defaultSetting;
30
31
	/**
32
	* @var string Name of the highest setting allowed
33
	*/
34
	public $maxSetting;
35
36
	/**
37
	* @var bool Whether this restriction applies only to elements using any kind of dynamic markup:
38
	*           XSL elements or attribute value templates
39
	*/
40
	public $onlyIfDynamic;
41
42
	/**
43
	* @var string Name of the restricted setting
44
	*/
45
	protected $settingName;
46
47
	/**
48
	* @var array Valid settings
49
	*/
50
	protected $settings;
51
52
	/**
53
	* @var DOMElement <xsl:template/> node
54
	*/
55
	protected $template;
56
57
	/**
58
	* Constructor
59
	*
60
	* @param  string $maxSetting    Max setting allowed
61
	* @param  bool   $onlyIfDynamic Whether this restriction applies only to elements using any kind
62
	*                               of dynamic markup: XSL elements or attribute value templates
63
	*/
64 56
	public function __construct($maxSetting, $onlyIfDynamic = false)
65
	{
66 56
		$this->maxSetting    = $maxSetting;
67 56
		$this->onlyIfDynamic = $onlyIfDynamic;
68
	}
69
70
	/**
71
	* Test for the set Flash restriction
72
	*
73
	* @param  DOMElement $template <xsl:template/> node
74
	* @param  Tag        $tag      Tag this template belongs to
75
	* @return void
76
	*/
77 56
	public function check(DOMElement $template, Tag $tag)
78
	{
79 56
		$this->template = $template;
80 56
		$this->checkEmbeds();
81 25
		$this->checkObjects();
82
	}
83
84
	/**
85
	* Test given element's attributes
86
	*
87
	* @param  DOMElement $embed Context element
88
	* @return void
89
	*/
90 28
	protected function checkAttributes(DOMElement $embed)
91
	{
92 28
		$settingName = strtolower($this->settingName);
93 28
		$useDefault  = true;
94 28
		foreach ($embed->attributes as $attribute)
95
		{
96 23
			$attrName = strtolower($attribute->name);
97 23
			if ($attrName === $settingName)
98
			{
99 19
				$this->checkSetting($attribute, $attribute->value);
100
				$useDefault = false;
101
			}
102
		}
103 9
		if ($useDefault)
104
		{
105 9
			$this->checkSetting($embed, $this->defaultSetting);
106
		}
107
	}
108
109
	/**
110
	* Test whether given element has dynamic attributes that match the setting's name
111
	*
112
	* @param  DOMElement $embed Context element
113
	* @return void
114
	*/
115 31
	protected function checkDynamicAttributes(DOMElement $embed)
116
	{
117 31
		$settingName = strtolower($this->settingName);
118 31
		foreach ($embed->getElementsByTagNameNS(self::XMLNS_XSL, 'attribute') as $attribute)
119
		{
120 3
			$attrName = strtolower($attribute->getAttribute('name'));
121 3
			if ($attrName === $settingName)
122
			{
123 3
				throw new UnsafeTemplateException('Cannot assess the safety of dynamic attributes', $attribute);
124
			}
125
		}
126
	}
127
128
	/**
129
	* Test the presence of dynamic params in given object
130
	*
131
	* @param  DOMElement $object Context element
132
	* @return void
133
	*/
134 25
	protected function checkDynamicParams(DOMElement $object)
135
	{
136 25
		foreach ($this->getObjectParams($object) as $param)
137
		{
138 18
			foreach ($param->getElementsByTagNameNS(self::XMLNS_XSL, 'attribute') as $attribute)
139
			{
140
				// Test for a dynamic "value" attribute
141 3
				if (strtolower($attribute->getAttribute('name')) === 'value')
142
				{
143 3
					throw new UnsafeTemplateException('Cannot assess the safety of dynamic attributes', $attribute);
144
				}
145
			}
146
		}
147
	}
148
149
	/**
150
	* Check embed elements in given template
151
	*
152
	* @return void
153
	*/
154 56
	protected function checkEmbeds()
155
	{
156 56
		foreach ($this->getElements('embed') as $embed)
157
		{
158
			// Test <xsl:attribute/> descendants
159 31
			$this->checkDynamicAttributes($embed);
160
161
			// Test the element's attributes
162 28
			$this->checkAttributes($embed);
163
		}
164
	}
165
166
	/**
167
	* Check object elements in given template
168
	*
169
	* @return void
170
	*/
171 25
	protected function checkObjects()
172
	{
173 25
		foreach ($this->getElements('object') as $object)
174
		{
175
			// Make sure this object doesn't have dynamic params
176 25
			$this->checkDynamicParams($object);
177
178
			// Test the element's <param/> descendants
179 22
			$params = $this->getObjectParams($object);
180 22
			foreach ($params as $param)
181
			{
182 15
				$this->checkSetting($param, $param->getAttribute('value'));
183
			}
184 7
			if (empty($params))
185
			{
186 7
				$this->checkSetting($object, $this->defaultSetting);
187
			}
188
		}
189
	}
190
191
	/**
192
	* Test whether given setting is allowed
193
	*
194
	* @param  DOMNode $node    Target node
195
	* @param  string  $setting Setting
196
	* @return void
197
	*/
198 50
	protected function checkSetting(DOMNode $node, $setting)
199
	{
200 50
		if (!isset($this->settings[strtolower($setting)]))
201
		{
202
			// Test whether the value contains an odd number of {
203 14
			if (preg_match('/(?<!\\{)\\{(?:\\{\\{)*(?!\\{)/', $setting))
204
			{
205 6
				throw new UnsafeTemplateException('Cannot assess ' . $this->settingName . " setting '" . $setting . "'", $node);
206
			}
207
208 8
			throw new UnsafeTemplateException('Unknown ' . $this->settingName . " value '" . $setting . "'", $node);
209
		}
210
211 36
		$value    = $this->settings[strtolower($setting)];
212 36
		$maxValue = $this->settings[strtolower($this->maxSetting)];
213
214 36
		if ($value > $maxValue)
215
		{
216 36
			throw new UnsafeTemplateException($this->settingName . " setting '" . $setting . "' exceeds restricted value '" . $this->maxSetting . "'", $node);
217
		}
218
	}
219
220
	/**
221
	* Test whether given node contains dynamic content (XSL elements or attribute value template)
222
	*
223
	* @param  DOMElement $node Node
224
	* @return bool
225
	*/
226 12
	protected function isDynamic(DOMElement $node)
227
	{
228 12
		if ($node->getElementsByTagNameNS(self::XMLNS_XSL, '*')->length)
229
		{
230 6
			return true;
231
		}
232
233
		// Look for any attributes containing "{" in this element or its descendants
234 6
		$xpath = new DOMXPath($node->ownerDocument);
235 6
		$query = './/@*[contains(., "{")]';
236
237 6
		foreach ($xpath->query($query, $node) as $attribute)
238
		{
239
			// Test whether the value contains an odd number of {
240 6
			if (preg_match('/(?<!\\{)\\{(?:\\{\\{)*(?!\\{)/', $attribute->value))
241
			{
242 6
				return true;
243
			}
244
		}
245
246
		return false;
247
	}
248
249
	/**
250
	* Get all elements the restriction applies to
251
	*
252
	* @param  string       $tagName Element's name
253
	* @return DOMElement[]
254
	*/
255 56
	protected function getElements($tagName)
256
	{
257 56
		$nodes = [];
258 56
		foreach ($this->template->ownerDocument->getElementsByTagName($tagName) as $node)
259
		{
260 56
			if (!$this->onlyIfDynamic || $this->isDynamic($node))
261
			{
262 56
				$nodes[] = $node;
263
			}
264
		}
265
266 56
		return $nodes;
267
	}
268
269
	/**
270
	* Get all param elements attached to given object
271
	*
272
	* @param  DOMElement   $object Context element
273
	* @return DOMElement[]
274
	*/
275 25
	protected function getObjectParams(DOMElement $object)
276
	{
277 25
		$params      = [];
278 25
		$settingName = strtolower($this->settingName);
279 25
		foreach ($object->getElementsByTagName('param') as $param)
280
		{
281 24
			$paramName = strtolower($param->getAttribute('name'));
282 24
			if ($paramName === $settingName && $param->parentNode->isSameNode($object))
283
			{
284 18
				$params[] = $param;
285
			}
286
		}
287
288 25
		return $params;
289
	}
290
}