Issues (387)

Branch: develop

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/phpDocumentor/Transformer/Writer/Xml.php (1 issue)

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
declare(strict_types=1);
3
4
/**
5
 * This file is part of phpDocumentor.
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @author    Mike van Riel <[email protected]>
11
 * @copyright 2010-2018 Mike van Riel / Naenius (http://www.naenius.com)
12
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
13
 * @link      http://phpdoc.org
14
 */
15
16
namespace phpDocumentor\Transformer\Writer;
17
18
use phpDocumentor\Application;
19
use phpDocumentor\Descriptor\ClassDescriptor;
20
use phpDocumentor\Descriptor\ConstantDescriptor;
21
use phpDocumentor\Descriptor\FileDescriptor;
22
use phpDocumentor\Descriptor\FunctionDescriptor;
23
use phpDocumentor\Descriptor\InterfaceDescriptor;
24
use phpDocumentor\Descriptor\ProjectDescriptor;
25
use phpDocumentor\Descriptor\TraitDescriptor;
26
use phpDocumentor\Descriptor\Validator\Error;
27
use phpDocumentor\Transformer\Behaviour\Tag\AuthorTag;
28
use phpDocumentor\Transformer\Behaviour\Tag\CoversTag;
29
use phpDocumentor\Transformer\Behaviour\Tag\IgnoreTag;
30
use phpDocumentor\Transformer\Behaviour\Tag\InternalTag;
31
use phpDocumentor\Transformer\Behaviour\Tag\LicenseTag;
32
use phpDocumentor\Transformer\Behaviour\Tag\MethodTag;
33
use phpDocumentor\Transformer\Behaviour\Tag\ParamTag;
34
use phpDocumentor\Transformer\Behaviour\Tag\PropertyTag;
35
use phpDocumentor\Transformer\Behaviour\Tag\ReturnTag;
36
use phpDocumentor\Transformer\Behaviour\Tag\UsesTag;
37
use phpDocumentor\Transformer\Behaviour\Tag\VarTag;
38
use phpDocumentor\Transformer\Router\RouterAbstract;
39
use phpDocumentor\Transformer\Transformation;
40
use phpDocumentor\Transformer\Transformer;
41
use phpDocumentor\Transformer\Writer\Xml\ArgumentConverter;
42
use phpDocumentor\Transformer\Writer\Xml\ConstantConverter;
43
use phpDocumentor\Transformer\Writer\Xml\DocBlockConverter;
44
use phpDocumentor\Transformer\Writer\Xml\InterfaceConverter;
45
use phpDocumentor\Transformer\Writer\Xml\MethodConverter;
46
use phpDocumentor\Transformer\Writer\Xml\PropertyConverter;
47
use phpDocumentor\Transformer\Writer\Xml\TagConverter;
48
use phpDocumentor\Transformer\Writer\Xml\TraitConverter;
49
50
/**
51
 * Converts the structural information of phpDocumentor into an XML file.
52
 */
53
class Xml extends WriterAbstract
54
{
55
    /** @var \DOMDocument $xml */
56
    protected $xml;
57
58
    protected $docBlockConverter;
59
60
    protected $argumentConverter;
61
62
    protected $methodConverter;
63
64
    protected $propertyConverter;
65
66
    protected $constantConverter;
67
68
    protected $interfaceConverter;
69
70
    protected $traitConverter;
71
72 2
    public function __construct(RouterAbstract $router)
73
    {
74 2
        $this->docBlockConverter = new DocBlockConverter(new TagConverter(), $router);
75 2
        $this->argumentConverter = new ArgumentConverter();
76 2
        $this->methodConverter = new MethodConverter($this->argumentConverter, $this->docBlockConverter);
77 2
        $this->propertyConverter = new PropertyConverter($this->docBlockConverter);
78 2
        $this->constantConverter = new ConstantConverter($this->docBlockConverter);
79 2
        $this->interfaceConverter = new InterfaceConverter(
80 2
            $this->docBlockConverter,
81 2
            $this->methodConverter,
82 2
            $this->constantConverter
83
        );
84 2
        $this->traitConverter = new TraitConverter(
85 2
            $this->docBlockConverter,
86 2
            $this->methodConverter,
87 2
            $this->propertyConverter
88
        );
89 2
    }
90
91
    /**
92
     * This method generates the AST output
93
     *
94
     * @param ProjectDescriptor $project        Document containing the structure.
95
     * @param Transformation    $transformation Transformation to execute.
96
     */
97 2
    public function transform(ProjectDescriptor $project, Transformation $transformation)
98
    {
99 2
        $artifact = $this->getDestinationPath($transformation);
100
101 2
        $this->checkForSpacesInPath($artifact);
102
103 2
        $this->xml = new \DOMDocument('1.0', 'utf-8');
104 2
        $this->xml->formatOutput = true;
105 2
        $document_element = new \DOMElement('project');
106 2
        $this->xml->appendChild($document_element);
107
108 2
        $document_element->setAttribute('title', $project->getName());
109 2
        $document_element->setAttribute('version', Application::VERSION());
110
111 2
        $this->buildPartials($document_element, $project);
112
113 2
        $transformer = $transformation->getTransformer();
114
115 2
        foreach ($project->getFiles() as $file) {
116 1
            $this->buildFile($document_element, $file, $transformer);
117
        }
118
119 2
        $this->finalize($project);
120 2
        file_put_contents($artifact, $this->xml->saveXML());
121 2
    }
122
123 2
    protected function buildPartials(\DOMElement $parent, ProjectDescriptor $project)
124
    {
125 2
        $child = new \DOMElement('partials');
126 2
        $parent->appendChild($child);
127 2
        foreach ($project->getPartials() as $name => $element) {
128
            $partial = new \DOMElement('partial');
129
            $child->appendChild($partial);
130
            $partial->setAttribute('name', $name);
131
            $partial->appendChild(new \DOMText($element));
132
        }
133 2
    }
134
135 1
    protected function buildFile(\DOMElement $parent, FileDescriptor $file, Transformer $transformer)
136
    {
137 1
        $child = new \DOMElement('file');
138 1
        $parent->appendChild($child);
139
140 1
        $path = ltrim($file->getPath(), './');
141 1
        $child->setAttribute('path', $path);
142 1
        $child->setAttribute(
143 1
            'generated-path',
144 1
            $transformer->generateFilename($path)
145
        );
146 1
        $child->setAttribute('hash', $file->getHash());
147
148 1
        $this->docBlockConverter->convert($child, $file);
149
150
        // add namespace aliases
151 1
        foreach ($file->getNamespaceAliases() as $alias => $namespace) {
152 1
            $alias_obj = new \DOMElement('namespace-alias', (string) $namespace);
153 1
            $child->appendChild($alias_obj);
154 1
            $alias_obj->setAttribute('name', (string) $alias);
155
        }
156
157
        /** @var ConstantDescriptor $constant */
158 1
        foreach ($file->getConstants() as $constant) {
159
            $this->constantConverter->convert($child, $constant);
160
        }
161
162
        /** @var FunctionDescriptor $function */
163 1
        foreach ($file->getFunctions() as $function) {
164
            $this->buildFunction($child, $function);
165
        }
166
167
        /** @var InterfaceDescriptor $interface */
168 1
        foreach ($file->getInterfaces() as $interface) {
169
            $this->interfaceConverter->convert($child, $interface);
170
        }
171
172
        /** @var ClassDescriptor $class */
173 1
        foreach ($file->getClasses() as $class) {
174
            $this->buildClass($child, $class);
175
        }
176
177
        /** @var TraitDescriptor $class */
178 1
        foreach ($file->getTraits() as $trait) {
179
            $this->traitConverter->convert($child, $trait);
180
        }
181
182
        // add markers
183 1
        if (count($file->getMarkers()) > 0) {
184
            $markers = new \DOMElement('markers');
185
            $child->appendChild($markers);
186
187
            foreach ($file->getMarkers() as $marker) {
188
                if (! $marker['type']) {
189
                    continue;
190
                }
191
192
                $type = preg_replace('/[^A-Za-z0-9\-]/', '', $marker['type']);
193
                $marker_obj = new \DOMElement(strtolower($type));
194
                $markers->appendChild($marker_obj);
195
196
                if (array_key_exists('message', $marker)) {
197
                    $marker_obj->appendChild(new \DOMText(trim((string) $marker['message'])));
198
                }
199
                $marker_obj->setAttribute('line', (string) $marker['line']);
200
            }
201
        }
202
203
        // if we want to include the source for each file; append a new
204
        // element 'source' which contains a compressed, encoded version
205
        // of the source
206 1
        if ($file->getSource()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $file->getSource() of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
207
            $child->appendChild(new \DOMElement('source', base64_encode(gzcompress($file->getSource()))));
208
        }
209 1
    }
210
211
    /**
212
     * Retrieves the destination location for this artifact.
213
     *
214
     * @return string
215
     */
216 2
    protected function getDestinationPath(Transformation $transformation)
217
    {
218 2
        return $transformation->getTransformer()->getTarget()
219 2
            . DIRECTORY_SEPARATOR . $transformation->getArtifact();
220
    }
221
222
    /**
223
     * Export this function definition to the given parent DOMElement.
224
     *
225
     * @param \DOMElement        $parent   Element to augment.
226
     * @param FunctionDescriptor $function Element to export.
227
     * @param \DOMElement        $child    if supplied this element will be augmented instead of freshly added.
228
     */
229
    public function buildFunction(\DOMElement $parent, FunctionDescriptor $function, \DOMElement $child = null)
230
    {
231
        if (!$child) {
232
            $child = new \DOMElement('function');
233
            $parent->appendChild($child);
234
        }
235
236
        $namespace = $function->getNamespace()
237
            ?: $parent->getAttribute('namespace');
238
        $child->setAttribute('namespace', ltrim((string) $namespace, '\\'));
239
        $child->setAttribute('line', (string) $function->getLine());
240
241
        $child->appendChild(new \DOMElement('name', $function->getName()));
242
        $child->appendChild(new \DOMElement('full_name', (string) $function->getFullyQualifiedStructuralElementName()));
243
244
        $this->docBlockConverter->convert($child, $function);
245
246
        foreach ($function->getArguments() as $argument) {
247
            $this->argumentConverter->convert($child, $argument);
248
        }
249
    }
250
251
    /**
252
     * Exports the given reflection object to the parent XML element.
253
     *
254
     * This method creates a new child element on the given parent XML element
255
     * and takes the properties of the Reflection argument and sets the
256
     * elements and attributes on the child.
257
     *
258
     * If a child DOMElement is provided then the properties and attributes are
259
     * set on this but the child element is not appended onto the parent. This
260
     * is the responsibility of the invoker. Essentially this means that the
261
     * $parent argument is ignored in this case.
262
     *
263
     * @param \DOMElement     $parent The parent element to augment.
264
     * @param ClassDescriptor $class  The data source.
265
     * @param \DOMElement     $child  Optional: child element to use instead of creating a
266
     *      new one on the $parent.
267
     */
268
    public function buildClass(\DOMElement $parent, ClassDescriptor $class, \DOMElement $child = null)
269
    {
270
        if (!$child) {
271
            $child = new \DOMElement('class');
272
            $parent->appendChild($child);
273
        }
274
275
        $child->setAttribute('final', $class->isFinal() ? 'true' : 'false');
276
        $child->setAttribute('abstract', $class->isAbstract() ? 'true' : 'false');
277
278
        if ($class->getParent() !== null) {
279
            $parentFqcn = $class->getParent() instanceof ClassDescriptor
280
                ? (string) $class->getParent()->getFullyQualifiedStructuralElementName()
281
                : (string) $class->getParent();
282
            $child->appendChild(new \DOMElement('extends', $parentFqcn));
283
        }
284
285
        /** @var InterfaceDescriptor $interface */
286
        foreach ($class->getInterfaces() as $interface) {
287
            $interfaceFqcn = $interface instanceof InterfaceDescriptor
288
                ? (string) $interface->getFullyQualifiedStructuralElementName()
289
                : (string) $interface;
290
            $child->appendChild(new \DOMElement('implements', $interfaceFqcn));
291
        }
292
293
        if ($child === null) {
294
            $child = new \DOMElement('interface');
295
            $parent->appendChild($child);
296
        }
297
298
        $namespace = (string) $class->getNamespace()->getFullyQualifiedStructuralElementName();
299
        $child->setAttribute('namespace', ltrim($namespace, '\\'));
300
        $child->setAttribute('line', (string) $class->getLine());
301
302
        $child->appendChild(new \DOMElement('name', $class->getName()));
303
        $child->appendChild(new \DOMElement('full_name', (string) $class->getFullyQualifiedStructuralElementName()));
304
305
        $this->docBlockConverter->convert($child, $class);
306
307
        foreach ($class->getConstants() as $constant) {
308
            // TODO #840: Workaround; for some reason there are NULLs in the constants array.
309
            if ($constant) {
310
                $this->constantConverter->convert($child, $constant);
311
            }
312
        }
313
314
        foreach ($class->getInheritedConstants() as $constant) {
315
            // TODO #840: Workaround; for some reason there are NULLs in the constants array.
316
            if ($constant) {
317
                $this->constantConverter->convert($child, $constant);
318
            }
319
        }
320
321
        foreach ($class->getProperties() as $property) {
322
            // TODO #840: Workaround; for some reason there are NULLs in the properties array.
323
            if ($property) {
324
                $this->propertyConverter->convert($child, $property);
325
            }
326
        }
327
328
        foreach ($class->getInheritedProperties() as $property) {
329
            // TODO #840: Workaround; for some reason there are NULLs in the properties array.
330
            if ($property) {
331
                $this->propertyConverter->convert($child, $property);
332
            }
333
        }
334
335
        foreach ($class->getMethods() as $method) {
336
            // TODO #840: Workaround; for some reason there are NULLs in the methods array.
337
            if ($method) {
338
                $this->methodConverter->convert($child, $method);
339
            }
340
        }
341
342
        foreach ($class->getInheritedMethods() as $method) {
343
            // TODO #840: Workaround; for some reason there are NULLs in the methods array.
344
            if ($method) {
345
                $methodElement = $this->methodConverter->convert($child, $method);
346
                $methodElement->appendChild(
347
                    new \DOMElement(
348
                        'inherited_from',
349
                        (string) $method->getParent()->getFullyQualifiedStructuralElementName()
350
                    )
351
                );
352
            }
353
        }
354
    }
355
356
    /**
357
     * Finalizes the processing and executing all post-processing actions.
358
     *
359
     * This method is responsible for extracting and manipulating the data that
360
     * is global to the project, such as:
361
     *
362
     * - Package tree
363
     * - Namespace tree
364
     * - Marker list
365
     * - Deprecated elements listing
366
     * - Removal of objects related to visibility
367
     */
368 2
    protected function finalize(ProjectDescriptor $projectDescriptor)
369
    {
370
        // TODO: move all these behaviours to a central location for all template parsers
371 2
        $behaviour = new AuthorTag();
372 2
        $behaviour->process($this->xml);
373 2
        $behaviour = new CoversTag();
374 2
        $behaviour->process($this->xml);
375 2
        $behaviour = new IgnoreTag();
376 2
        $behaviour->process($this->xml);
377 2
        $behaviour = new InternalTag(
378 2
            $projectDescriptor->isVisibilityAllowed(ProjectDescriptor\Settings::VISIBILITY_INTERNAL)
379
        );
380 2
        $behaviour->process($this->xml);
381 2
        $behaviour = new LicenseTag();
382 2
        $behaviour->process($this->xml);
383 2
        $behaviour = new MethodTag();
384 2
        $behaviour->process($this->xml);
385 2
        $behaviour = new ParamTag();
386 2
        $behaviour->process($this->xml);
387 2
        $behaviour = new PropertyTag();
388 2
        $behaviour->process($this->xml);
389 2
        $behaviour = new ReturnTag();
390 2
        $behaviour->process($this->xml);
391 2
        $behaviour = new UsesTag();
392 2
        $behaviour->process($this->xml);
393 2
        $behaviour = new VarTag();
394 2
        $behaviour->process($this->xml);
395 2
        $this->buildPackageTree($this->xml);
396 2
        $this->buildNamespaceTree($this->xml);
397 2
        $this->buildDeprecationList($this->xml);
398 2
    }
399
400
    /**
401
     * Collects all packages and subpackages, and adds a new section in the
402
     * DOM to provide an overview.
403
     *
404
     * @param \DOMDocument $dom Packages are extracted and a summary inserted
405
     *     in this object.
406
     */
407 2
    protected function buildPackageTree(\DOMDocument $dom)
408
    {
409 2
        $xpath = new \DOMXPath($dom);
410 2
        $packages = ['global' => true];
411 2
        $qry = $xpath->query('//@package');
412 2
        for ($i = 0; $i < $qry->length; ++$i) {
413 1
            if (isset($packages[$qry->item($i)->nodeValue])) {
414
                continue;
415
            }
416
417 1
            $packages[$qry->item($i)->nodeValue] = true;
418
        }
419
420 2
        $packages = $this->generateNamespaceTree(array_keys($packages));
421 2
        $this->generateNamespaceElements($packages, $dom->documentElement, 'package');
422 2
    }
423
424
    /**
425
     * Collects all namespaces and sub-namespaces, and adds a new section in
426
     * the DOM to provide an overview.
427
     *
428
     * @param \DOMDocument $dom Namespaces are extracted and a summary inserted
429
     *     in this object.
430
     */
431 2
    protected function buildNamespaceTree(\DOMDocument $dom)
432
    {
433 2
        $xpath = new \DOMXPath($dom);
434 2
        $namespaces = [];
435 2
        $qry = $xpath->query('//@namespace');
436 2
        for ($i = 0; $i < $qry->length; ++$i) {
437
            if (isset($namespaces[$qry->item($i)->nodeValue])) {
438
                continue;
439
            }
440
441
            $namespaces[$qry->item($i)->nodeValue] = true;
442
        }
443
444 2
        $namespaces = $this->generateNamespaceTree(array_keys($namespaces));
445 2
        $this->generateNamespaceElements($namespaces, $dom->documentElement);
446 2
    }
447
448
    /**
449
     * Adds a node to the xml for deprecations and the count value
450
     *
451
     * @param \DOMDocument $dom Markers are extracted and a summary inserted in this object.
452
     */
453 2
    protected function buildDeprecationList(\DOMDocument $dom)
454
    {
455 2
        $nodes = $this->getNodeListForTagBasedQuery($dom, 'deprecated');
456
457 2
        $node = new \DOMElement('deprecated');
458 2
        $dom->documentElement->appendChild($node);
459 2
        $node->setAttribute('count', (string) $nodes->length);
460 2
    }
461
462
    /**
463
     * Build a tag based query string and return result
464
     *
465
     * @param \DOMDocument $dom    Markers are extracted and a summary inserted
466
     *      in this object.
467
     * @param string       $marker The marker we're searching for throughout xml
468
     *
469
     * @return \DOMNodeList
470
     */
471 2
    protected function getNodeListForTagBasedQuery($dom, $marker)
472
    {
473 2
        $xpath = new \DOMXPath($dom);
474
475 2
        $query = '/project/file/markers/' . $marker . '|';
476 2
        $query .= '/project/file/docblock/tag[@name="' . $marker . '"]|';
477 2
        $query .= '/project/file/class/docblock/tag[@name="' . $marker . '"]|';
478 2
        $query .= '/project/file/class/*/docblock/tag[@name="' . $marker . '"]|';
479 2
        $query .= '/project/file/interface/docblock/tag[@name="' . $marker . '"]|';
480 2
        $query .= '/project/file/interface/*/docblock/tag[@name="' . $marker . '"]|';
481 2
        $query .= '/project/file/function/docblock/tag[@name="' . $marker . '"]|';
482 2
        $query .= '/project/file/constant/docblock/tag[@name="' . $marker . '"]';
483
484 2
        return $xpath->query($query);
485
    }
486
487
    /**
488
     * Generates a hierarchical array of namespaces with their singular name
489
     * from a single level list of namespaces with their full name.
490
     *
491
     * @param array $namespaces the list of namespaces as retrieved from the xml.
492
     *
493
     * @return array
494
     */
495 2
    protected function generateNamespaceTree($namespaces)
496
    {
497 2
        sort($namespaces);
498
499 2
        $result = [];
500 2
        foreach ($namespaces as $namespace) {
501 2
            if (!$namespace) {
502
                $namespace = 'global';
503
            }
504
505 2
            $namespace_list = explode('\\', $namespace);
506
507 2
            $node = &$result;
508 2
            foreach ($namespace_list as $singular) {
509 2
                if (!isset($node[$singular])) {
510 2
                    $node[$singular] = [];
511
                }
512
513 2
                $node = &$node[$singular];
514
            }
515
        }
516
517 2
        return $result;
518
    }
519
520
    /**
521
     * Recursive method to create a hierarchical set of nodes in the dom.
522
     *
523
     * @param array[]     $namespaces     the list of namespaces to process.
524
     * @param \DOMElement $parent_element the node to receive the children of
525
     *                                    the above list.
526
     * @param string      $node_name      the name of the summary element.
527
     */
528 2
    protected function generateNamespaceElements($namespaces, $parent_element, $node_name = 'namespace')
529
    {
530 2
        foreach ($namespaces as $name => $sub_namespaces) {
531 2
            $node = new \DOMElement($node_name);
532 2
            $parent_element->appendChild($node);
533 2
            $node->setAttribute('name', $name);
534 2
            $fullName = $parent_element->nodeName === $node_name
535
                ? $parent_element->getAttribute('full_name') . '\\' . $name
536 2
                : $name;
537 2
            $node->setAttribute('full_name', $fullName);
538 2
            $this->generateNamespaceElements($sub_namespaces, $node, $node_name);
539
        }
540 2
    }
541
}
542