Completed
Push — master ( 4757ad...c2a1a0 )
by Josh
03:45
created

DisallowUnsafeDynamicURL   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 97
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 95.83%

Importance

Changes 0
Metric Value
wmc 15
lcom 1
cbo 3
dl 0
loc 97
ccs 23
cts 24
cp 0.9583
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getNodes() 0 4 1
A isSafe() 0 4 1
A checkAttributeNode() 0 7 2
A checkElementNode() 0 7 2
A chooseHasSafeUrl() 0 17 4
A elementHasSafeUrl() 0 9 4
A isSafeUrl() 0 4 1
1
<?php
2
3
/**
4
* @package   s9e\TextFormatter
5
* @copyright Copyright (c) 2010-2019 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 DOMAttr;
11
use DOMElement;
12
use DOMText;
13
use DOMXPath;
14
use s9e\TextFormatter\Configurator\Helpers\NodeLocator;
15
use s9e\TextFormatter\Configurator\Items\Attribute;
16
use s9e\TextFormatter\Configurator\Items\Tag;
17
18
/**
19
* This primary use of this check is to ensure that dynamic content cannot be used to create
20
* javascript: links
21
*/
22
class DisallowUnsafeDynamicURL extends AbstractDynamicContentCheck
23
{
24
	/**
25
	* @var string Regexp used to exclude nodes that start with a hardcoded scheme part, a hardcoded
26
	*             local part, or a fragment
27
	*/
28
	protected $safeUrlRegexp = '(^(?:(?!data|\\w*script)\\w+:|[^:]*/|#))i';
29
30
	/**
31
	* {@inheritdoc}
32
	*/
33 18
	protected function getNodes(DOMElement $template)
34
	{
35 18
		return NodeLocator::getURLNodes($template->ownerDocument);
36
	}
37
38
	/**
39
	* {@inheritdoc}
40
	*/
41 8
	protected function isSafe(Attribute $attribute)
42
	{
43 8
		return $attribute->isSafeAsURL();
44
	}
45
46
	/**
47
	* {@inheritdoc}
48
	*/
49 8
	protected function checkAttributeNode(DOMAttr $attribute, Tag $tag)
50
	{
51 8
		if (!$this->isSafeUrl($attribute->value))
52
		{
53 8
			parent::checkAttributeNode($attribute, $tag);
54
		}
55
	}
56
57
	/**
58
	* {@inheritdoc}
59
	*/
60 8
	protected function checkElementNode(DOMElement $element, Tag $tag)
61
	{
62 8
		if (!$this->elementHasSafeUrl($element))
63
		{
64 8
			parent::checkElementNode($element, $tag);
65
		}
66
	}
67
68
	/**
69
	* Test whether every branch of a given xsl:choose element contains a known-safe URL
70
	*
71
	* @param  DOMElement $choose
72
	* @return bool
73
	*/
74 2
	protected function chooseHasSafeUrl(DOMElement $choose)
75
	{
76 2
		$xpath = new DOMXPath($choose->ownerDocument);
77 2
		foreach ($xpath->query('xsl:when | xsl:otherwise', $choose) as $branch)
78
		{
79 2
			if (!$this->elementHasSafeUrl($branch))
80
			{
81 1
				return false;
82
			}
83 2
			if ($branch->nodeName === 'xsl:otherwise')
84
			{
85
				return true;
86
			}
87
		}
88
89 1
		return false;
90
	}
91
92
	/**
93
	* Test whether given element contains a known-safe URL
94
	*
95
	* @param  DOMElement $element
96
	* @return bool
97
	*/
98 8
	protected function elementHasSafeUrl(DOMElement $element)
99
	{
100 8
		if ($element->firstChild instanceof DOMElement && $element->firstChild->nodeName === 'xsl:choose')
101
		{
102 2
			return $this->chooseHasSafeUrl($element->firstChild);
103
		}
104
105 8
		return $element->firstChild instanceof DOMText && $this->isSafeUrl($element->firstChild->textContent);
106
	}
107
108
	/**
109
	* Test whether given URL is known to be safe
110
	*
111
	* @param  string $url
112
	* @return bool
113
	*/
114 11
	protected function isSafeUrl($url)
115
	{
116 11
		return (bool) preg_match($this->safeUrlRegexp, $url);
117
	}
118
}