GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

HTMLPurifier_URI::validate()   F
last analyzed

Complexity

Conditions 20
Paths 1512

Size

Total Lines 106
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 20
eloc 47
c 1
b 0
f 0
nc 1512
nop 2
dl 0
loc 106
rs 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * HTML Purifier's internal representation of a URI.
5
 * @note
6
 *      Internal data-structures are completely escaped. If the data needs
7
 *      to be used in a non-URI context (which is very unlikely), be sure
8
 *      to decode it first. The URI may not necessarily be well-formed until
9
 *      validate() is called.
10
 */
11
class HTMLPurifier_URI
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
12
{
13
    /**
14
     * @type string
15
     */
16
    public $scheme;
17
18
    /**
19
     * @type string
20
     */
21
    public $userinfo;
22
23
    /**
24
     * @type string
25
     */
26
    public $host;
27
28
    /**
29
     * @type int
30
     */
31
    public $port;
32
33
    /**
34
     * @type string
35
     */
36
    public $path;
37
38
    /**
39
     * @type string
40
     */
41
    public $query;
42
43
    /**
44
     * @type string
45
     */
46
    public $fragment;
47
48
    /**
49
     * @param string $scheme
50
     * @param string $userinfo
51
     * @param string $host
52
     * @param int $port
53
     * @param string $path
54
     * @param string $query
55
     * @param string $fragment
56
     * @note Automatically normalizes scheme and port
57
     */
58
    public function __construct($scheme, $userinfo, $host, $port, $path, $query, $fragment)
59
    {
60
        $this->scheme = is_null($scheme) || ctype_lower($scheme) ? $scheme : strtolower($scheme);
61
        $this->userinfo = $userinfo;
62
        $this->host = $host;
63
        $this->port = is_null($port) ? $port : (int)$port;
64
        $this->path = $path;
65
        $this->query = $query;
66
        $this->fragment = $fragment;
67
    }
68
69
    /**
70
     * Retrieves a scheme object corresponding to the URI's scheme/default
71
     * @param HTMLPurifier_Config $config
72
     * @param HTMLPurifier_Context $context
73
     * @return HTMLPurifier_URIScheme Scheme object appropriate for validating this URI
74
     */
75
    public function getSchemeObj($config, $context)
76
    {
77
        $registry = HTMLPurifier_URISchemeRegistry::instance();
78
        if ($this->scheme !== null) {
79
            $scheme_obj = $registry->getScheme($this->scheme, $config, $context);
80
            if (!$scheme_obj) {
81
                return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by HTMLPurifier_URI::getSchemeObj of type HTMLPurifier_URIScheme.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
82
            } // invalid scheme, clean it out
83
        } else {
84
            // no scheme: retrieve the default one
85
            $def = $config->getDefinition('URI');
86
            $scheme_obj = $def->getDefaultScheme($config, $context);
87
            if (!$scheme_obj) {
88
                // something funky happened to the default scheme object
89
                trigger_error(
90
                    'Default scheme object "' . $def->defaultScheme . '" was not readable',
0 ignored issues
show
Bug introduced by
The property defaultScheme does not seem to exist in HTMLPurifier_Definition.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
91
                    E_USER_WARNING
92
                );
93
                return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type documented by HTMLPurifier_URI::getSchemeObj of type HTMLPurifier_URIScheme.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
94
            }
95
        }
96
        return $scheme_obj;
97
    }
98
99
    /**
100
     * Generic validation method applicable for all schemes. May modify
101
     * this URI in order to get it into a compliant form.
102
     * @param HTMLPurifier_Config $config
103
     * @param HTMLPurifier_Context $context
104
     * @return bool True if validation/filtering succeeds, false if failure
105
     */
106
    public function validate($config, $context)
107
    {
108
        // ABNF definitions from RFC 3986
109
        $chars_sub_delims = '!$&\'()*+,;=';
110
        $chars_gen_delims = ':/?#[]@';
0 ignored issues
show
Unused Code introduced by
$chars_gen_delims is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
111
        $chars_pchar = $chars_sub_delims . ':@';
112
113
        // validate host
114
        if (!is_null($this->host)) {
115
            $host_def = new HTMLPurifier_AttrDef_URI_Host();
116
            $this->host = $host_def->validate($this->host, $config, $context);
0 ignored issues
show
Documentation Bug introduced by
It seems like $host_def->validate($thi...ost, $config, $context) can also be of type false. However, the property $host is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
117
            if ($this->host === false) {
118
                $this->host = null;
119
            }
120
        }
121
122
        // validate scheme
123
        // NOTE: It's not appropriate to check whether or not this
124
        // scheme is in our registry, since a URIFilter may convert a
125
        // URI that we don't allow into one we do.  So instead, we just
126
        // check if the scheme can be dropped because there is no host
127
        // and it is our default scheme.
128
        if (!is_null($this->scheme) && is_null($this->host) || $this->host === '') {
129
            // support for relative paths is pretty abysmal when the
130
            // scheme is present, so axe it when possible
131
            $def = $config->getDefinition('URI');
132
            if ($def->defaultScheme === $this->scheme) {
0 ignored issues
show
Bug introduced by
The property defaultScheme does not seem to exist in HTMLPurifier_Definition.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
133
                $this->scheme = null;
134
            }
135
        }
136
137
        // validate username
138
        if (!is_null($this->userinfo)) {
139
            $encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . ':');
0 ignored issues
show
Documentation introduced by
$chars_sub_delims . ':' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
140
            $this->userinfo = $encoder->encode($this->userinfo);
141
        }
142
143
        // validate port
144
        if (!is_null($this->port)) {
145
            if ($this->port < 1 || $this->port > 65535) {
146
                $this->port = null;
147
            }
148
        }
149
150
        // validate path
151
        $segments_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/');
0 ignored issues
show
Documentation introduced by
$chars_pchar . '/' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
152
        if (!is_null($this->host)) { // this catches $this->host === ''
153
            // path-abempty (hier and relative)
154
            // http://www.example.com/my/path
155
            // //www.example.com/my/path (looks odd, but works, and
156
            //                            recognized by most browsers)
157
            // (this set is valid or invalid on a scheme by scheme
158
            // basis, so we'll deal with it later)
159
            // file:///my/path
160
            // ///my/path
161
            $this->path = $segments_encoder->encode($this->path);
162
        } elseif ($this->path !== '') {
163
            if ($this->path[0] === '/') {
164
                // path-absolute (hier and relative)
165
                // http:/my/path
166
                // /my/path
167
                if (strlen($this->path) >= 2 && $this->path[1] === '/') {
168
                    // This could happen if both the host gets stripped
169
                    // out
170
                    // http://my/path
171
                    // //my/path
172
                    $this->path = '';
173
                } else {
174
                    $this->path = $segments_encoder->encode($this->path);
175
                }
176
            } elseif (!is_null($this->scheme)) {
177
                // path-rootless (hier)
178
                // http:my/path
179
                // Short circuit evaluation means we don't need to check nz
180
                $this->path = $segments_encoder->encode($this->path);
181
            } else {
182
                // path-noscheme (relative)
183
                // my/path
184
                // (once again, not checking nz)
185
                $segment_nc_encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . '@');
0 ignored issues
show
Documentation introduced by
$chars_sub_delims . '@' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
186
                $c = strpos($this->path, '/');
187
                if ($c !== false) {
188
                    $this->path =
189
                        $segment_nc_encoder->encode(substr($this->path, 0, $c)) .
190
                        $segments_encoder->encode(substr($this->path, $c));
191
                } else {
192
                    $this->path = $segment_nc_encoder->encode($this->path);
193
                }
194
            }
195
        } else {
196
            // path-empty (hier and relative)
197
            $this->path = ''; // just to be safe
198
        }
199
200
        // qf = query and fragment
201
        $qf_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/?');
0 ignored issues
show
Documentation introduced by
$chars_pchar . '/?' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
202
203
        if (!is_null($this->query)) {
204
            $this->query = $qf_encoder->encode($this->query);
205
        }
206
207
        if (!is_null($this->fragment)) {
208
            $this->fragment = $qf_encoder->encode($this->fragment);
209
        }
210
        return true;
211
    }
212
213
    /**
214
     * Convert URI back to string
215
     * @return string URI appropriate for output
216
     */
217
    public function toString()
218
    {
219
        // reconstruct authority
220
        $authority = null;
221
        // there is a rendering difference between a null authority
222
        // (http:foo-bar) and an empty string authority
223
        // (http:///foo-bar).
224
        if (!is_null($this->host)) {
225
            $authority = '';
226
            if (!is_null($this->userinfo)) {
227
                $authority .= $this->userinfo . '@';
228
            }
229
            $authority .= $this->host;
230
            if (!is_null($this->port)) {
231
                $authority .= ':' . $this->port;
232
            }
233
        }
234
235
        // Reconstruct the result
236
        // One might wonder about parsing quirks from browsers after
237
        // this reconstruction.  Unfortunately, parsing behavior depends
238
        // on what *scheme* was employed (file:///foo is handled *very*
239
        // differently than http:///foo), so unfortunately we have to
240
        // defer to the schemes to do the right thing.
241
        $result = '';
242
        if (!is_null($this->scheme)) {
243
            $result .= $this->scheme . ':';
244
        }
245
        if (!is_null($authority)) {
246
            $result .= '//' . $authority;
247
        }
248
        $result .= $this->path;
249
        if (!is_null($this->query)) {
250
            $result .= '?' . $this->query;
251
        }
252
        if (!is_null($this->fragment)) {
253
            $result .= '#' . $this->fragment;
254
        }
255
256
        return $result;
257
    }
258
259
    /**
260
     * Returns true if this URL might be considered a 'local' URL given
261
     * the current context.  This is true when the host is null, or
262
     * when it matches the host supplied to the configuration.
263
     *
264
     * Note that this does not do any scheme checking, so it is mostly
265
     * only appropriate for metadata that doesn't care about protocol
266
     * security.  isBenign is probably what you actually want.
267
     * @param HTMLPurifier_Config $config
268
     * @param HTMLPurifier_Context $context
269
     * @return bool
270
     */
271
    public function isLocal($config, $context)
0 ignored issues
show
Unused Code introduced by
The parameter $context is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
272
    {
273
        if ($this->host === null) {
274
            return true;
275
        }
276
        $uri_def = $config->getDefinition('URI');
277
        if ($uri_def->host === $this->host) {
0 ignored issues
show
Bug introduced by
The property host does not seem to exist in HTMLPurifier_Definition.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
278
            return true;
279
        }
280
        return false;
281
    }
282
283
    /**
284
     * Returns true if this URL should be considered a 'benign' URL,
285
     * that is:
286
     *
287
     *      - It is a local URL (isLocal), and
288
     *      - It has a equal or better level of security
289
     * @param HTMLPurifier_Config $config
290
     * @param HTMLPurifier_Context $context
291
     * @return bool
292
     */
293
    public function isBenign($config, $context)
294
    {
295
        if (!$this->isLocal($config, $context)) {
296
            return false;
297
        }
298
299
        $scheme_obj = $this->getSchemeObj($config, $context);
300
        if (!$scheme_obj) {
301
            return false;
302
        } // conservative approach
303
304
        $current_scheme_obj = $config->getDefinition('URI')->getDefaultScheme($config, $context);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class HTMLPurifier_Definition as the method getDefaultScheme() does only exist in the following sub-classes of HTMLPurifier_Definition: HTMLPurifier_URIDefinition, HTMLPurifier_URIDefinition. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
305
        if ($current_scheme_obj->secure) {
306
            if (!$scheme_obj->secure) {
307
                return false;
308
            }
309
        }
310
        return true;
311
    }
312
}
313
314
// vim: et sw=4 sts=4
315