DisallowUnsafeDynamicURL   A
last analyzed

Complexity

Total Complexity 15

Size/Duplication

Total Lines 96
Duplicated Lines 0 %

Test Coverage

Coverage 96%

Importance

Changes 0
Metric Value
wmc 15
eloc 20
c 0
b 0
f 0
dl 0
loc 96
ccs 24
cts 25
cp 0.96
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A isSafeUrl() 0 3 1
A chooseHasSafeUrl() 0 17 4
A getNodes() 0 3 1
A isSafe() 0 3 1
A checkElementNode() 0 5 2
A checkAttributeNode() 0 5 2
A elementHasSafeUrl() 0 8 4
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 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);
0 ignored issues
show
Bug introduced by
It seems like $choose->ownerDocument can also be of type null; however, parameter $document of DOMXPath::__construct() does only seem to accept DOMDocument, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

76
		$xpath        = new DOMXPath(/** @scrutinizer ignore-type */ $choose->ownerDocument);
Loading history...
77 2
		$hasOtherwise = false;
78 2
		foreach ($xpath->query('xsl:when | xsl:otherwise', $choose) as $branch)
79
		{
80 2
			if (!$this->elementHasSafeUrl($branch))
81
			{
82 1
				return false;
83
			}
84 2
			if ($branch->nodeName === 'xsl:otherwise')
85
			{
86
				$hasOtherwise = true;
87
			}
88
		}
89
90 1
		return $hasOtherwise;
91
	}
92
93
	/**
94
	* Test whether given element contains a known-safe URL
95
	*
96
	* @param  DOMElement $element
97
	* @return bool
98
	*/
99 8
	protected function elementHasSafeUrl(DOMElement $element)
100
	{
101 8
		if ($element->firstChild instanceof DOMElement && $element->firstChild->nodeName === 'xsl:choose')
102
		{
103 2
			return $this->chooseHasSafeUrl($element->firstChild);
104
		}
105
106 8
		return $element->firstChild instanceof DOMText && $this->isSafeUrl($element->firstChild->textContent);
107
	}
108
109
	/**
110
	* Test whether given URL is known to be safe
111
	*
112
	* @param  string $url
113
	* @return bool
114
	*/
115 11
	protected function isSafeUrl($url)
116
	{
117 11
		return (bool) preg_match($this->safeUrlRegexp, $url);
118
	}
119
}