ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
XML.php
Go to the documentation of this file.
1<?php
8namespace SimpleSAML\Utils;
9
12
13class XML
14{
15
35 public static function checkSAMLMessage($message, $type)
36 {
37 $allowed_types = array('saml20', 'saml11', 'saml-meta');
38 if (!(is_string($message) && in_array($type, $allowed_types, true))) {
39 throw new \InvalidArgumentException('Invalid input parameters.');
40 }
41
42 // a SAML message should not contain a doctype-declaration
43 if (strpos($message, '<!DOCTYPE') !== false) {
44 throw new \SimpleSAML_Error_Exception('XML contained a doctype declaration.');
45 }
46
47 // see if debugging is enabled for XML validation
48 $debug = \SimpleSAML_Configuration::getInstance()->getArrayize('debug', array('validatexml' => false));
49 $enabled = \SimpleSAML_Configuration::getInstance()->getBoolean('debug.validatexml', false);
50
51 if (!(in_array('validatexml', $debug, true) // implicitly enabled
52 || (array_key_exists('validatexml', $debug) && $debug['validatexml'] === true) // explicitly enabled
53 // TODO: deprecate this option and remove it in 2.0
54 || $enabled // old 'debug.validatexml' configuration option
55 )) {
56 // XML validation is disabled
57 return;
58 }
59
60 $result = true;
61 switch ($type) {
62 case 'saml11':
63 $result = self::isValid($message, 'oasis-sstc-saml-schema-protocol-1.1.xsd');
64 break;
65 case 'saml20':
66 $result = self::isValid($message, 'saml-schema-protocol-2.0.xsd');
67 break;
68 case 'saml-meta':
69 $result = self::isValid($message, 'saml-schema-metadata-2.0.xsd');
70 }
71 if ($result !== true) {
73 }
74 }
75
76
94 public static function debugSAMLMessage($message, $type)
95 {
96 if (!(is_string($type) && (is_string($message) || $message instanceof \DOMElement))) {
97 throw new \InvalidArgumentException('Invalid input parameters.');
98 }
99
100 // see if debugging is enabled for SAML messages
101 $debug = \SimpleSAML_Configuration::getInstance()->getArrayize('debug', array('saml' => false));
102
103 if (!(in_array('saml', $debug, true) // implicitly enabled
104 || (array_key_exists('saml', $debug) && $debug['saml'] === true) // explicitly enabled
105 // TODO: deprecate the old style and remove it in 2.0
106 || (array_key_exists(0, $debug) && $debug[0] === true) // old style 'debug'
107 )) {
108 // debugging messages is disabled
109 return;
110 }
111
112 if ($message instanceof \DOMElement) {
113 $message = $message->ownerDocument->saveXML($message);
114 }
115
116 switch ($type) {
117 case 'in':
118 Logger::debug('Received message:');
119 break;
120 case 'out':
121 Logger::debug('Sending message:');
122 break;
123 case 'decrypt':
124 Logger::debug('Decrypted message:');
125 break;
126 case 'encrypt':
127 Logger::debug('Encrypted message:');
128 break;
129 default:
130 assert(false);
131 }
132
134 foreach (explode("\n", $str) as $line) {
135 Logger::debug($line);
136 }
137 }
138
139
156 public static function formatDOMElement(\DOMNode $root, $indentBase = '')
157 {
158 if (!is_string($indentBase)) {
159 throw new \InvalidArgumentException('Invalid input parameters');
160 }
161
162 // check what this element contains
163 $fullText = ''; // all text in this element
164 $textNodes = array(); // text nodes which should be deleted
165 $childNodes = array(); // other child nodes
166 for ($i = 0; $i < $root->childNodes->length; $i++) {
168 $child = $root->childNodes->item($i);
169
170 if ($child instanceof \DOMText) {
171 $textNodes[] = $child;
172 $fullText .= $child->wholeText;
173 } elseif ($child instanceof \DOMComment || $child instanceof \DOMElement) {
174 $childNodes[] = $child;
175 } else {
176 // unknown node type. We don't know how to format this
177 return;
178 }
179 }
180
181 $fullText = trim($fullText);
182 if (strlen($fullText) > 0) {
183 // we contain textelf
184 $hasText = true;
185 } else {
186 $hasText = false;
187 }
188
189 $hasChildNode = (count($childNodes) > 0);
190
191 if ($hasText && $hasChildNode) {
192 // element contains both text and child nodes - we don't know how to format this one
193 return;
194 }
195
196 // remove text nodes
197 foreach ($textNodes as $node) {
198 $root->removeChild($node);
199 }
200
201 if ($hasText) {
202 // only text - add a single text node to the element with the full text
203 $root->appendChild(new \DOMText($fullText));
204 return;
205 }
206
207 if (!$hasChildNode) {
208 // empty node. Nothing to do
209 return;
210 }
211
212 /* Element contains only child nodes - add indentation before each one, and
213 * format child elements.
214 */
215 $childIndentation = $indentBase.' ';
216 foreach ($childNodes as $node) {
217 // add indentation before node
218 $root->insertBefore(new \DOMText("\n".$childIndentation), $node);
219
220 // format child elements
221 if ($node instanceof \DOMElement) {
222 self::formatDOMElement($node, $childIndentation);
223 }
224 }
225
226 // add indentation before closing tag
227 $root->appendChild(new \DOMText("\n".$indentBase));
228 }
229
230
246 public static function formatXMLString($xml, $indentBase = '')
247 {
248 if (!is_string($xml) || !is_string($indentBase)) {
249 throw new \InvalidArgumentException('Invalid input parameters');
250 }
251
252 try {
254 } catch (\Exception $e) {
255 throw new \DOMException('Error parsing XML string.');
256 }
257
258 $root = $doc->firstChild;
259 self::formatDOMElement($root, $indentBase);
260
261 return $doc->saveXML($root);
262 }
263
264
280 public static function getDOMChildren(\DOMNode $element, $localName, $namespaceURI)
281 {
282 if (!is_string($localName) || !is_string($namespaceURI)) {
283 throw new \InvalidArgumentException('Invalid input parameters.');
284 }
285
286 $ret = array();
287
288 for ($i = 0; $i < $element->childNodes->length; $i++) {
290 $child = $element->childNodes->item($i);
291
292 // skip text nodes and comment elements
293 if ($child instanceof \DOMText || $child instanceof \DOMComment) {
294 continue;
295 }
296
297 if (self::isDOMNodeOfType($child, $localName, $namespaceURI) === true) {
298 $ret[] = $child;
299 }
300 }
301
302 return $ret;
303 }
304
305
316 public static function getDOMText(\DOMElement $element)
317 {
318 $txt = '';
319
320 for ($i = 0; $i < $element->childNodes->length; $i++) {
322 $child = $element->childNodes->item($i);
323 if (!($child instanceof \DOMText)) {
324 throw new \SimpleSAML_Error_Exception($element->localName.' contained a non-text child node.');
325 }
326
327 $txt .= $child->wholeText;
328 }
329
330 $txt = trim($txt);
331 return $txt;
332 }
333
334
357 public static function isDOMNodeOfType(\DOMNode $element, $name, $nsURI)
358 {
359 if (!is_string($name) || !is_string($nsURI) || strlen($nsURI) === 0) {
360 // most likely a comment-node
361 return false;
362 }
363
364 // check if the namespace is a shortcut, and expand it if it is
365 if ($nsURI[0] === '@') {
366 // the defined shortcuts
367 $shortcuts = array(
368 '@ds' => 'http://www.w3.org/2000/09/xmldsig#',
369 '@md' => 'urn:oasis:names:tc:SAML:2.0:metadata',
370 '@saml1' => 'urn:oasis:names:tc:SAML:1.0:assertion',
371 '@saml1md' => 'urn:oasis:names:tc:SAML:profiles:v1metadata',
372 '@saml1p' => 'urn:oasis:names:tc:SAML:1.0:protocol',
373 '@saml2' => 'urn:oasis:names:tc:SAML:2.0:assertion',
374 '@saml2p' => 'urn:oasis:names:tc:SAML:2.0:protocol',
375 '@shibmd' => 'urn:mace:shibboleth:metadata:1.0',
376 );
377
378 // check if it is a valid shortcut
379 if (!array_key_exists($nsURI, $shortcuts)) {
380 throw new \InvalidArgumentException('Unknown namespace shortcut: '.$nsURI);
381 }
382
383 // expand the shortcut
384 $nsURI = $shortcuts[$nsURI];
385 }
386 if ($element->localName !== $name) {
387 return false;
388 }
389 if ($element->namespaceURI !== $nsURI) {
390 return false;
391 }
392 return true;
393 }
394
395
411 public static function isValid($xml, $schema)
412 {
413 if (!(is_string($schema) && (is_string($xml) || $xml instanceof \DOMDocument))) {
414 throw new \InvalidArgumentException('Invalid input parameters.');
415 }
416
418
419 if ($xml instanceof \DOMDocument) {
420 $dom = $xml;
421 $res = true;
422 } else {
423 try {
425 $res = true;
426 } catch (\Exception $e) {
427 $res = false;
428 }
429 }
430
431 if ($res) {
434 $schemaPath = $config->resolvePath('schemas');
435 $schemaPath .= './';
436 $schemaFile = $schemaPath.$schema;
437
438 $res = $dom->schemaValidate($schemaFile);
439 if ($res) {
440 Errors::end();
441 return true;
442 }
443
444 $errorText = "Schema validation failed on XML string:\n";
445 } else {
446 $errorText = "Failed to parse XML string for schema validation:\n";
447 }
448
450 $errorText .= Errors::formatErrors($errors);
451
452 return $errorText;
453 }
454}
$result
An exception for terminatinating execution or to throw for unit testing.
static warning($string)
Definition: Logger.php:179
static debug($string)
Definition: Logger.php:213
static isDOMNodeOfType(\DOMNode $element, $name, $nsURI)
This function checks if the DOMElement has the correct localName and namespaceURI.
Definition: XML.php:357
static checkSAMLMessage($message, $type)
This function performs some sanity checks on XML documents, and optionally validates them against the...
Definition: XML.php:35
static debugSAMLMessage($message, $type)
Helper function to log SAML messages that we send or receive.
Definition: XML.php:94
static formatXMLString($xml, $indentBase='')
Format an XML string.
Definition: XML.php:246
static formatErrors($errors)
Format a list of errors as a string.
Definition: Errors.php:127
static begin()
Start error logging.
Definition: Errors.php:48
static end()
End error logging.
Definition: Errors.php:77
static getInstance($instancename='simplesaml')
Get a configuration file by its instance name.
$i
Definition: disco.tpl.php:19
$txt
Definition: error.php:11
if($format !==null) $name
Definition: metadata.php:146
$xml
Definition: metadata.php:240
catch(Exception $e) $message
$errors
Definition: index.php:6
$debug
Definition: loganalyzer.php:16
$ret
Definition: parser.php:6
$type
foreach($_POST as $key=> $value) $res