Passed
Pull Request — master (#55)
by Tim
02:07
created

QNameValue::validateValue()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 13
c 1
b 0
f 0
dl 0
loc 22
rs 9.5222
cc 5
nc 5
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XML\Type;
6
7
use DOMElement;
8
use SimpleSAML\XML\Assert\Assert;
9
use SimpleSAML\XML\Exception\SchemaViolationException;
10
use SimpleSAML\XML\Type\{AnyURIValue, NCNameValue};
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, SimpleSAML\XML\Type\NCNameValue. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
Bug introduced by
This use statement conflicts with another class in this namespace, SimpleSAML\XML\Type\AnyURIValue. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
11
12
use function preg_match;
13
14
/**
15
 * @package simplesaml/xml-common
16
 */
17
class QNameValue extends AbstractValueType
18
{
19
    /** @var string */
20
    public const SCHEMA_TYPE = 'xs:QName';
21
22
    /** @var \SimpleSAML\XML\Type\AnyURIValue|null */
23
    protected ?AnyURIValue $namespaceURI;
24
25
    /** @var \SimpleSAML\XML\Type\NCNameValue|null */
26
    protected ?NCNameValue $namespacePrefix;
27
28
    /** @var \SimpleSAML\XML\Type\NCNameValue */
29
    protected NCNameValue $localName;
30
31
    /** @var string */
32
    private static string $qname_regex = '/^
33
        (?:
34
          \{                 # Match a literal {
35
            (\S+)            # Match one or more non-whitespace character
36
          \}                 # Match a literal }
37
          (?:
38
            ([\w_][\w.-]*)   # Match a-z or underscore followed by any word-character, dot or dash
39
            :                # Match a literal :
40
          )?
41
        )?                   # Namespace and prefix are optional
42
        ([\w_][\w.-]*)       # Match a-z or underscore followed by any word-character, dot or dash
43
        $/Dimx';
44
45
46
    /**
47
     * Sanitize the value.
48
     *
49
     * @param string $value  The unsanitized value
50
     * @return string
51
     */
52
    protected function sanitizeValue(string $value): string
53
    {
54
        return static::collapseWhitespace(static::normalizeWhitespace($value));
55
    }
56
57
58
    /**
59
     * Validate the value.
60
     *
61
     * @param string $value
62
     * @throws \SimpleSAML\XML\Exception\SchemaViolationException on failure
63
     * @return void
64
     */
65
    protected function validateValue(string $value): void
66
    {
67
        $qName = $this->sanitizeValue($value);
68
69
        /**
70
         * Split our custom format of {<namespaceURI>}<prefix>:<localName> into individual parts
71
         */
72
        $result = preg_match(
73
            self::$qname_regex,
74
            $qName,
75
            $matches,
76
            PREG_UNMATCHED_AS_NULL,
77
        );
78
79
        if ($result && count($matches) === 4) {
80
            list($qName, $namespaceURI, $namespacePrefix, $localName) = $matches;
81
82
            $this->namespaceURI = ($namespaceURI !== null) ? AnyURIValue::fromString($namespaceURI) : null;
83
            $this->namespacePrefix = ($namespacePrefix !== null) ? NCNameValue::fromString($namespacePrefix) : null;
84
            $this->localName = NCNameValue::fromString($localName);
85
        } else {
86
            throw new SchemaViolationException(sprintf('\'%s\' is not a valid xs:QName.', $qName));
87
        }
88
    }
89
90
91
    /**
92
     * Get the value.
93
     *
94
     * @return string
95
     */
96
    public function getValue(): string
97
    {
98
        return $this->getNamespacePrefix() . ':' . $this->getLocalName();
99
    }
100
101
102
    /**
103
     * Get the namespaceURI for this qualified name.
104
     *
105
     * @return \SimpleSAML\XML\Type\AnyURIValue|null
106
     */
107
    public function getNamespaceURI(): ?AnyURIValue
108
    {
109
        return $this->namespaceURI;
110
    }
111
112
113
    /**
114
     * Get the namespace-prefix for this qualified name.
115
     *
116
     * @return \SimpleSAML\XML\Type\NCNameValue|null
117
     */
118
    public function getNamespacePrefix(): ?NCNameValue
119
    {
120
        return $this->namespacePrefix;
121
    }
122
123
124
    /**
125
     * Get the local name for this qualified name.
126
     *
127
     * @return \SimpleSAML\XML\Type\NCNameValue
128
     */
129
    public function getLocalName(): NCNameValue
130
    {
131
        return $this->localName;
132
    }
133
134
135
    /**
136
     * @param \SimpleSAML\XML\Type\NCNameValue $localName
137
     * @param \SimpleSAML\XML\Type\AnyURIValue|null $namespaceURI
138
     * @param \SimpleSAML\XML\Type\NCNameValue|null $namespacePrefix
139
     * @return static
140
     */
141
    public static function fromParts(
142
        NCNameValue $localName,
143
        ?AnyURIValue $namespaceURI,
144
        ?NCNameValue $namespacePrefix,
145
    ): static {
146
        if ($namespaceURI === null) {
147
            // If we don't have a namespace, we can't have a prefix either
148
            Assert::null($namespacePrefix->getValue(), SchemaViolationException::class);
0 ignored issues
show
Bug introduced by
The method getValue() does not exist on null. ( Ignorable by Annotation )

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

148
            Assert::null($namespacePrefix->/** @scrutinizer ignore-call */ getValue(), SchemaViolationException::class);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
149
            return new static($localName->getValue());
150
        }
151
152
        return new static(
153
            '{' . $namespaceURI->getValue() . '}'
154
            . ($namespacePrefix ? ($namespacePrefix->getValue() . ':') : '')
155
            . $localName,
156
        );
157
    }
158
159
160
    /**
161
     * @param string $qName
162
     */
163
    public static function fromDocument(
164
        string $qName,
165
        DOMElement $element,
166
    ): static {
167
        $namespacePrefix = null;
168
        if (str_contains($qName, ':')) {
169
            list($namespacePrefix, $localName) = explode(':', $qName, 2);
170
        } else {
171
            // No prefix
172
            $localName = $qName;
173
        }
174
175
        // Will return the default namespace (if any) when prefix is NULL
176
        $namespaceURI = $element->lookupNamespaceUri($namespacePrefix);
177
178
        return new static('{' . $namespaceURI . '}' . $namespacePrefix . ':' . $localName);
179
    }
180
}
181