ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
XML.php
Go to the documentation of this file.
1 <?php
8 namespace SimpleSAML\Utils;
9 
12 
13 class 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 
133  $str = self::formatXMLString($message);
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 
417  Errors::begin();
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 
449  $errors = Errors::end();
450  $errorText .= Errors::formatErrors($errors);
451 
452  return $errorText;
453  }
454 }
$result
$type
static debug($string)
Definition: Logger.php:213
static formatXMLString($xml, $indentBase='')
Format an XML string.
Definition: XML.php:246
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 formatErrors($errors)
Format a list of errors as a string.
Definition: Errors.php:127
$xml
Definition: metadata.php:240
static debugSAMLMessage($message, $type)
Helper function to log SAML messages that we send or receive.
Definition: XML.php:94
if($format !==null) $name
Definition: metadata.php:146
catch(Exception $e) $message
foreach($_POST as $key=> $value) $res
static warning($string)
Definition: Logger.php:179
$txt
Definition: error.php:11
Create styles array
The data for the language used.
$debug
Definition: loganalyzer.php:16
static begin()
Start error logging.
Definition: Errors.php:48
$errors
Definition: index.php:6
$ret
Definition: parser.php:6
static end()
End error logging.
Definition: Errors.php:77
$i
Definition: disco.tpl.php:19
static getInstance($instancename='simplesaml')
Get a configuration file by its instance name.