Issues (88)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Tokens/Element.php (5 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Groundskeeper\Tokens;
4
5
use Groundskeeper\Configuration;
6
use Psr\Log\LoggerInterface;
7
8
class Element extends AbstractToken implements Cleanable, ContainsChildren, Removable
9
{
10
    /** @var array */
11
    protected $attributes;
12
13
    /** @var Token[] */
14
    protected $children;
15
16
    /** @var string */
17
    private $name;
18
19
    /**
20
     * Constructor
21
     */
22 185
    public function __construct(Configuration $configuration,
23
                                int $line,
24
                                int $position,
25
                                string $name,
26
                                array $attributes = array())
27
    {
28 185
        parent::__construct($configuration, $line, $position);
29
30 185
        $this->attributes = array();
31 185
        foreach ($attributes as $key => $value) {
32 99
            $this->addAttribute($key, $value);
33
        }
34
35 185
        $this->children = array();
36
37 185
        if (!is_string($name)) {
38
            throw new \InvalidArgumentException('Element name must be string type.');
39
        }
40
41 185
        $this->name = trim(strtolower($name));
42 185
    }
43
44
    /**
45
     * Getter for 'attributes'.
46
     */
47 2
    public function getAttributes() : array
48
    {
49 2
        $attributeArray = array();
50 2
        foreach ($this->attributes as $attribute) {
51 1
            $attributeArray[$attribute->getName()] = $attribute->getValue();
52
        }
53
54 2
        return $attributeArray;
55
    }
56
57 4
    public function getAttribute(string $key)
58
    {
59 4
        if (!$this->hasAttribute($key)) {
60 1
            throw new \InvalidArgumentException('Invalid attribute key: ' . $key);
61
        }
62
63 3
        $attributeObject = $this->attributes[$key];
64
65 3
        return $attributeObject->getValue();
66
    }
67
68
    /**
69
     * Hasser for 'attributes'.
70
     *
71
     * @param string $key
72
     *
73
     * @return bool True if the attribute is present.
74
     */
75 45
    public function hasAttribute(string $key) : bool
76
    {
77 45
        return array_key_exists($key, $this->attributes);
78
    }
79
80 102
    public function addAttribute(string $key, $value)
81
    {
82 102
        $key = trim(strtolower($key));
83 102
        if ($key === '') {
84 1
            throw new \InvalidArgumentException('Invalid empty attribute key.');
85
        }
86
87 101
        $attributeParameters = $this->getAttributeParameters($key);
88 101
        $isStandard = true;
89 101 View Code Duplication
        if (empty($attributeParameters)) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
90
            $attributeParameters = array(
91 11
                'name' => $key,
92 11
                'regex' => '/\S*/i',
93
                'valueType' => Attribute::CS_STRING
94
            );
95 11
            $isStandard = false;
96
        }
97
98 101
        $this->attributes[$key] = new Attribute(
99 101
            $key,
100 101
            $value,
101 101
            $attributeParameters['valueType'],
0 ignored issues
show
The call to Attribute::__construct() has too many arguments starting with $attributeParameters['valueType'].

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
102 101
            $isStandard
103
        );
104
105 101
        return $this;
106
    }
107
108 2
    public function removeAttribute(string $key)
109
    {
110 2
        $key = trim(strtolower($key));
111 2
        if (isset($this->attributes[$key])) {
112 2
            unset($this->attributes[$key]);
113
        }
114 2
    }
115
116
    /**
117
     * Required by ContainsChildren interface.
118
     */
119 3
    public function getChildren() : array
120
    {
121 3
        return $this->children;
122
    }
123
124
    /**
125
     * Required by ContainsChildren interface.
126
     */
127 1
    public function hasChild(Token $token) : bool
128
    {
129 1
        return in_array($token, $this->children, true);
130
    }
131
132
    /**
133
     * Required by ContainsChildren interface.
134
     */
135 140
    public function appendChild(Token $token)
136
    {
137 140
        $token->setParent($this);
138 140
        $this->children[] = $token;
139 140
    }
140
141
    /**
142
     * Required by ContainsChildren interface.
143
     */
144 5
    public function prependChild(Token $token)
145
    {
146 5
        $token->setParent($this);
147 5
        array_unshift($this->children, $token);
148 5
    }
149
150
    /**
151
     * Required by the ContainsChildren interface.
152
     */
153 39 View Code Duplication
    public function removeChild(Token $token)
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
154
    {
155 39
        $key = array_search($token, $this->children, true);
156 39
        if ($key !== false) {
157 39
            unset($this->children[$key]);
158
        }
159 39
    }
160
161
    /**
162
     * Getter for 'name'.
163
     */
164 137
    public function getName() : string
165
    {
166 137
        return $this->name;
167
    }
168
169
    /**
170
     * Required by the Cleanable interface.
171
     */
172 140
    public function clean(LoggerInterface $logger) : bool
173
    {
174 140
        if ($this->configuration->get('clean-strategy') === Configuration::CLEAN_STRATEGY_NONE) {
175 1
            return true;
176
        }
177
178
        // Assign attributes to the attributes. (Soooo meta ....)
179 139
        foreach ($this->attributes as $attribute) {
180 92
            $attributeParameters = $this->getAttributeParameters(
181 92
                $attribute->getName()
182
            );
183 92
            $isStandard = true;
184 92 View Code Duplication
            if (empty($attributeParameters)) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
185
                $attributeParameters = array(
186 9
                    'name' => $attribute->getName(),
187 9
                    'regex' => '/\S*/i',
188
                    'valueType' => Attribute::UNKNOWN
189
                );
190 9
                $isStandard = false;
191
            }
192
193 92
            $attribute->setType($attributeParameters['valueType']);
194 92
            $attribute->setIsStandard($isStandard);
195
        }
196
197
        // Clean attributes.
198 139
        foreach ($this->attributes as $attribute) {
199 92
            $attributeCleanResult = $attribute->clean(
200 92
                $this->configuration,
201 92
                $this,
202 92
                $logger
203
            );
204 92
            if (!$attributeCleanResult && $this->configuration->get('clean-strategy') !== Configuration::CLEAN_STRATEGY_LENIENT) {
205 92
                unset($this->attributes[$attribute->getName()]);
206
            }
207
        }
208
209
        // Fix self (if possible)
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
210 139
        $this->fixSelf($logger);
211
212
        // Remove self or children?
213 139
        if ($this->configuration->get('clean-strategy') !== Configuration::CLEAN_STRATEGY_LENIENT) {
214
            // Remove self?
215 139
            if ($this->removeInvalidSelf($logger)) {
216 32
                return false;
217
            }
218
219
            // Remove children?
220 138
            $this->removeInvalidChildren($logger);
221
        }
222
223
        // Clean children.
224 139
        return AbstractToken::cleanChildTokens(
225 139
            $this->configuration,
226 139
            $this->children,
227 139
            $logger
228
        );
229
    }
230
231 132
    protected function fixSelf(LoggerInterface $logger)
232
    {
233 132
    }
234
235 118
    protected function removeInvalidChildren(LoggerInterface $logger)
236
    {
237 118
    }
238
239 129
    protected function removeInvalidSelf(LoggerInterface $logger) : bool
240
    {
241 129
        return false;
242
    }
243
244 101
    protected function getAllowedAttributes()
245
    {
246
        return array(
247
            // Global Attributes
248 101
            '/^accesskey$/i' => Attribute::CS_STRING,
249
            '/^class$/i' => Attribute::CS_STRING,
250
            '/^contenteditable$/i' => Attribute::CS_STRING,
251
            '/^contextmenu$/i' => Attribute::CS_STRING,
252
            '/^data-\S/i' => Attribute::CS_STRING,
253
            '/^dir$/i' => Attribute::CI_ENUM . '("ltr","rtl"|"ltr")',
254
            '/^draggable$/i' => Attribute::CS_STRING,
255
            '/^dropzone$/i' => Attribute::CS_STRING,
256
            '/^hidden$/i' => Attribute::CS_STRING,
257
            '/^id$/i' => Attribute::CS_STRING,
258
            '/^is$/i' => Attribute::CS_STRING,
259
            '/^itemid$/i' => Attribute::CS_STRING,
260
            '/^itemprop$/i' => Attribute::CS_STRING,
261
            '/^itemref$/i' => Attribute::CS_STRING,
262
            '/^itemscope$/i' => Attribute::CS_STRING,
263
            '/^itemtype$/i' => Attribute::CS_STRING,
264
            '/^lang$/i' => Attribute::CI_STRING,
265
            '/^slot$/i' => Attribute::CS_STRING,
266
            '/^spellcheck$/i' => Attribute::CS_STRING,
267
            '/^style$/i' => Attribute::CS_STRING,
268
            '/^tabindex$/i' => Attribute::CS_STRING,
269
            '/^title$/i' => Attribute::CS_STRING,
270
            '/^translate$/i' => Attribute::CI_ENUM . '("yes","no",""|"yes")',
271
272
            // Event Handler Content Attributes
273
            // https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-content-attributes
274
            '/^onabort$/i' => Attribute::JS,
275
            '/^onautocomplete$/i' => Attribute::JS,
276
            '/^onautocompleteerror$/i' => Attribute::JS,
277
            '/^onblur$/i' => Attribute::JS,
278
            '/^oncancel$/i' => Attribute::JS,
279
            '/^oncanplay$/i' => Attribute::JS,
280
            '/^oncanplaythrough$/i' => Attribute::JS,
281
            '/^onchange$/i' => Attribute::JS,
282
            '/^onclick$/i' => Attribute::JS,
283
            '/^onclose$/i' => Attribute::JS,
284
            '/^oncontextmenu$/i' => Attribute::JS,
285
            '/^oncuechange$/i' => Attribute::JS,
286
            '/^ondblclick$/i' => Attribute::JS,
287
            '/^ondrag$/i' => Attribute::JS,
288
            '/^ondragend$/i' => Attribute::JS,
289
            '/^ondragenter$/i' => Attribute::JS,
290
            '/^ondragexit$/i' => Attribute::JS,
291
            '/^ondragleave$/i' => Attribute::JS,
292
            '/^ondragover$/i' => Attribute::JS,
293
            '/^ondragstart$/i' => Attribute::JS,
294
            '/^ondrop$/i' => Attribute::JS,
295
            '/^ondurationchange$/i' => Attribute::JS,
296
            '/^onemptied$/i' => Attribute::JS,
297
            '/^onended$/i' => Attribute::JS,
298
            '/^onerror$/i' => Attribute::JS,
299
            '/^onfocus$/i' => Attribute::JS,
300
            '/^oninput$/i' => Attribute::JS,
301
            '/^oninvalid$/i' => Attribute::JS,
302
            '/^onkeydown$/i' => Attribute::JS,
303
            '/^onkeypress$/i' => Attribute::JS,
304
            '/^onkeyup$/i' => Attribute::JS,
305
            '/^onload$/i' => Attribute::JS,
306
            '/^onloadeddata$/i' => Attribute::JS,
307
            '/^onloadedmetadata$/i' => Attribute::JS,
308
            '/^onloadstart$/i' => Attribute::JS,
309
            '/^onmousedown$/i' => Attribute::JS,
310
            '/^onmouseenter$/i' => Attribute::JS,
311
            '/^onmouseleave$/i' => Attribute::JS,
312
            '/^onmousemove$/i' => Attribute::JS,
313
            '/^onmouseout$/i' => Attribute::JS,
314
            '/^onmouseover$/i' => Attribute::JS,
315
            '/^onmouseup$/i' => Attribute::JS,
316
            '/^onwheel$/i' => Attribute::JS,
317
            '/^onpause$/i' => Attribute::JS,
318
            '/^onplay$/i' => Attribute::JS,
319
            '/^onplaying$/i' => Attribute::JS,
320
            '/^onprogress$/i' => Attribute::JS,
321
            '/^onratechange$/i' => Attribute::JS,
322
            '/^onreset$/i' => Attribute::JS,
323
            '/^onresize$/i' => Attribute::JS,
324
            '/^onscroll$/i' => Attribute::JS,
325
            '/^onseeked$/i' => Attribute::JS,
326
            '/^onseeking$/i' => Attribute::JS,
327
            '/^onselect$/i' => Attribute::JS,
328
            '/^onshow$/i' => Attribute::JS,
329
            '/^onstalled$/i' => Attribute::JS,
330
            '/^onsubmit$/i' => Attribute::JS,
331
            '/^onsuspend$/i' => Attribute::JS,
332
            '/^ontimeupdate$/i' => Attribute::JS,
333
            '/^ontoggle$/i' => Attribute::JS,
334
            '/^onvolumechange$/i' => Attribute::JS,
335
            '/^onwaiting$/i' => Attribute::JS,
336
337
            // WAI-ARIA
338
            // https://w3c.github.io/aria/aria/aria.html
339
            '/^role$/i' => Attribute::CI_STRING,
340
341
            // ARIA global states and properties
342
            '/^aria-atomic$/i' => Attribute::CS_STRING,
343
            '/^aria-busy$/i' => Attribute::CS_STRING,
344
            '/^aria-controls$/i' => Attribute::CS_STRING,
345
            '/^aria-current$/i' => Attribute::CS_STRING,
346
            '/^aria-describedby$/i' => Attribute::CS_STRING,
347
            '/^aria-details$/i' => Attribute::CS_STRING,
348
            '/^aria-disabled$/i' => Attribute::CS_STRING,
349
            '/^aria-dropeffect$/i' => Attribute::CS_STRING,
350
            '/^aria-errormessage$/i' => Attribute::CS_STRING,
351
            '/^aria-flowto$/i' => Attribute::CS_STRING,
352
            '/^aria-grabbed$/i' => Attribute::CS_STRING,
353
            '/^aria-haspopup$/i' => Attribute::CS_STRING,
354
            '/^aria-hidden$/i' => Attribute::CS_STRING,
355
            '/^aria-invalid$/i' => Attribute::CS_STRING,
356
            '/^aria-label$/i' => Attribute::CS_STRING,
357
            '/^aria-labelledby$/i' => Attribute::CS_STRING,
358
            '/^aria-live$/i' => Attribute::CS_STRING,
359
            '/^aria-owns$/i' => Attribute::CS_STRING,
360
            '/^aria-relevant$/i' => Attribute::CS_STRING,
361
            '/^aria-roledescription$/i' => Attribute::CS_STRING,
362
363
            // ARIA widget attributes
364
            '/^aria-autocomplete$/i' => Attribute::CS_STRING,
365
            '/^aria-checked$/i' => Attribute::CS_STRING,
366
            '/^aria-expanded$/i' => Attribute::CS_STRING,
367
            '/^aria-level$/i' => Attribute::CS_STRING,
368
            '/^aria-modal$/i' => Attribute::CS_STRING,
369
            '/^aria-multiline$/i' => Attribute::CS_STRING,
370
            '/^aria-multiselectable$/i' => Attribute::CS_STRING,
371
            '/^aria-orientation$/i' => Attribute::CS_STRING,
372
            '/^aria-placeholder$/i' => Attribute::CS_STRING,
373
            '/^aria-pressed$/i' => Attribute::CS_STRING,
374
            '/^aria-readonly$/i' => Attribute::CS_STRING,
375
            '/^aria-required$/i' => Attribute::CS_STRING,
376
            '/^aria-selected$/i' => Attribute::CS_STRING,
377
            '/^aria-sort$/i' => Attribute::CS_STRING,
378
            '/^aria-valuemax$/i' => Attribute::CS_STRING,
379
            '/^aria-valuemin$/i' => Attribute::CS_STRING,
380
            '/^aria-valuenow$/i' => Attribute::CS_STRING,
381
            '/^aria-valuetext$/i' => Attribute::CS_STRING,
382
383
            // ARIA relationship attributes
384
            '/^aria-activedescendant$/i' => Attribute::CS_STRING,
385
            '/^aria-colcount$/i' => Attribute::CS_STRING,
386
            '/^aria-colindex$/i' => Attribute::CS_STRING,
387
            '/^aria-colspan$/i' => Attribute::CS_STRING,
388
            '/^aria-posinset$/i' => Attribute::CS_STRING,
389
            '/^aria-rowcount$/i' => Attribute::CS_STRING,
390
            '/^aria-rowindex$/i' => Attribute::CS_STRING,
391
            '/^aria-rowspan$/i' => Attribute::CS_STRING,
392
            '/^aria-setsize$/i' => Attribute::CS_STRING
393
        );
394
    }
395
396 101
    private function getAttributeParameters(string $key) : array
397
    {
398 101
        $allowedAttributes = $this->getAllowedAttributes();
399 101
        foreach ($allowedAttributes as $attrRegex => $valueType) {
400 101
            if (preg_match($attrRegex, $key) === 1) {
401
                return array(
402 100
                    'name' => $key,
403 100
                    'regex' => $attrRegex,
404 101
                    'valueType' => $valueType
405
                );
406
            }
407
        }
408
409 11
        return array();
410
    }
411
412
    /**
413
     * Required by the Removable interface.
414
     */
415 137
    public function remove(LoggerInterface $logger)
416
    {
417 137
        $hasRemovableElements = $this->configuration->get('element-blacklist') !== '';
418 137
        $hasRemovableTypes = $this->configuration->get('type-blacklist') !== '';
419 137
        foreach ($this->children as $child) {
420
            // Check types.
421 131
            if ($hasRemovableTypes &&
422 131
                !$this->configuration->isAllowedType($child->getType())) {
423 2
                $logger->debug('Removing ' . $child);
424 2
                $this->removeChild($child);
425
426 2
                continue;
427
            }
428
429
            // Check elements.
430 129
            if ($hasRemovableElements &&
431 129
                $child instanceof self &&
432 129
                !$this->configuration->isAllowedElement($child->getName())) {
433 3
                $logger->debug('Removing ' . $child);
434 3
                $this->removeChild($child);
435
436 3
                continue;
437
            }
438
439
            // Check children.
440 129
            if ($child instanceof Removable) {
441 129
                $child->remove($logger);
442
            }
443
        }
444 137
    }
445
446
    /**
447
     * Required by the Token interface.
448
     */
449 9
    public function toHtml(string $prefix, string $suffix) : string
450
    {
451 9
        $output = $this->buildStartTag($prefix, $suffix);
452 9
        if (empty($this->children)) {
453 6
            return $output;
454
        }
455
456 7
        $output .= $this->buildChildrenHtml($prefix, $suffix);
457
458 7
        return $output . $prefix . '</' . $this->name . '>' . $suffix;
459
    }
460
461 143
    protected function buildStartTag(string $prefix, string $suffix, bool $forceOpen = false) : string
462
    {
463 143
        $output = $prefix . '<' . $this->name;
464 143
        foreach ($this->attributes as $attribute) {
465 94
            $output .= ' ' . (string) $attribute;
466
        }
467
468 143
        if (!$forceOpen && empty($this->children)) {
469 53
            return $output . '/>' . $suffix;
470
        }
471
472 139
        return $output . '>' . $suffix;
473
    }
474
475 139
    protected function buildChildrenHtml(string $prefix, string $suffix) : string
476
    {
477 139
        $output = '';
478 139
        foreach ($this->children as $child) {
479
            $newPrefix = $prefix .
480 135
                str_repeat(
481 135
                    ' ',
482 135
                    $this->configuration->get('indent-spaces')
483
                );
484 135
            $output .= $child->toHtml($newPrefix, $suffix);
485
        }
486
487 139
        return $output;
488
    }
489
490 27
    public function getType() : string
491
    {
492 27
        return Token::ELEMENT;
493
    }
494
495 91
    public function __toString()
496
    {
497 91
        return '"' . $this->name . '" element (line: ' . $this->getLine() .
498 91
            '; position: ' . $this->getPosition() . ')';
499
    }
500
}
501