Passed
Pull Request — master (#11)
by Tim
01:40
created

DOMDocumentFactory::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 0
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XML;
6
7
use DOMDocument;
8
use InvalidArgumentException;
9
use RuntimeException;
10
use SimpleSAML\Assert\Assert;
11
use SimpleSAML\XML\Exception\IOException;
12
use SimpleSAML\XML\Exception\UnparseableXMLException;
13
14
use function defined;
15
use function file_get_contents;
16
use function is_file;
17
use function is_readable;
18
use function libxml_clear_errors;
19
use function libxml_disable_entity_loader;
20
use function libxml_get_last_error;
21
use function libxml_use_internal_errors;
22
use function sprintf;
23
use function trim;
24
25
/**
26
 * @package simplesamlphp/xml-common
27
 */
28
final class DOMDocumentFactory
29
{
30
    /**
31
     * Constructor for DOMDocumentFactory.
32
     * This class should never be instantiated
33
     */
34
    private function __construct()
35
    {
36
    }
37
38
39
    /**
40
     * @param string $xml
41
     *
42
     * @return \DOMDocument
43
     */
44
    public static function fromString(string $xml): DOMDocument
45
    {
46
        Assert::stringNotEmpty(trim($xml));
47
48
        $internalErrors = libxml_use_internal_errors(true);
49
        libxml_clear_errors();
50
51
        $domDocument = self::create();
52
        $options = LIBXML_DTDLOAD | LIBXML_DTDATTR | LIBXML_NONET | LIBXML_PARSEHUGE;
53
        if (defined('LIBXML_COMPACT')) {
54
            $options |= LIBXML_COMPACT;
55
        }
56
57
        $loaded = $domDocument->loadXML($xml, $options);
58
59
        libxml_use_internal_errors($internalErrors);
60
61
        if (!$loaded) {
62
            $error = libxml_get_last_error();
63
            libxml_clear_errors();
64
65
            throw new UnparseableXMLException($error);
66
        }
67
68
        libxml_clear_errors();
69
70
        foreach ($domDocument->childNodes as $child) {
71
            if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) {
72
                throw new RuntimeException(
73
                    'Dangerous XML detected, DOCTYPE nodes are not allowed in the XML body',
74
                );
75
            }
76
        }
77
78
        return $domDocument;
79
    }
80
81
82
    /**
83
     * @param string $file
84
     *
85
     * @return \DOMDocument
86
     */
87
    public static function fromFile(string $file): DOMDocument
88
    {
89
        // libxml_disable_entity_loader(true) disables \DOMDocument::load() method
90
        // so we need to read the content and use \DOMDocument::loadXML()
91
        error_clear_last();
92
        $xml = @file_get_contents($file);
93
        if ($xml === false) {
94
            /** @psalm-var array $e */
95
            $e = error_get_last();
96
            $error = $e['message'] ?: "Check that the file exists and can be read.";
97
98
            throw new IOException("File '$file' was not loaded;  $error");
99
        }
100
101
        if (trim($xml) === '') {
102
            throw new RuntimeException(sprintf('File "%s" does not have content', $file));
103
        }
104
105
        return static::fromString($xml);
106
    }
107
108
109
    /**
110
     * @param ?string $version
111
     * @param ?string $encoding
112
     * @return \DOMDocument
113
     */
114
    public static function create(?string $version = null, ?string $encoding = null): DOMDocument
115
    {
116
        return new DOMDocument($version ?? '1.0', $encoding ?? '');
117
    }
118
}
119