XMLUtil.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. <?php
  2. /**
  3. * XML utilities for WebDAV
  4. *
  5. * @package Sabre
  6. * @subpackage DAV
  7. * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
  8. * @author Evert Pot (http://www.rooftopsolutions.nl/)
  9. * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
  10. */
  11. class Sabre_DAV_XMLUtil {
  12. /**
  13. * Returns the 'clark notation' for an element.
  14. *
  15. * For example, and element encoded as:
  16. * <b:myelem xmlns:b="http://www.example.org/" />
  17. * will be returned as:
  18. * {http://www.example.org}myelem
  19. *
  20. * This format is used throughout the SabreDAV sourcecode.
  21. * Elements encoded with the urn:DAV namespace will
  22. * be returned as if they were in the DAV: namespace. This is to avoid
  23. * compatibility problems.
  24. *
  25. * This function will return null if a nodetype other than an Element is passed.
  26. *
  27. * @param DOMNode $dom
  28. * @return string
  29. */
  30. static function toClarkNotation(DOMNode $dom) {
  31. if ($dom->nodeType !== XML_ELEMENT_NODE) return null;
  32. // Mapping back to the real namespace, in case it was dav
  33. if ($dom->namespaceURI=='urn:DAV') $ns = 'DAV:'; else $ns = $dom->namespaceURI;
  34. // Mapping to clark notation
  35. return '{' . $ns . '}' . $dom->localName;
  36. }
  37. /**
  38. * Parses a clark-notation string, and returns the namespace and element
  39. * name components.
  40. *
  41. * If the string was invalid, it will throw an InvalidArgumentException.
  42. *
  43. * @param string $str
  44. * @throws InvalidArgumentException
  45. * @return array
  46. */
  47. static function parseClarkNotation($str) {
  48. if (!preg_match('/^{([^}]*)}(.*)$/',$str,$matches)) {
  49. throw new InvalidArgumentException('\'' . $str . '\' is not a valid clark-notation formatted string');
  50. }
  51. return array(
  52. $matches[1],
  53. $matches[2]
  54. );
  55. }
  56. /**
  57. * This method takes an XML document (as string) and converts all instances of the
  58. * DAV: namespace to urn:DAV
  59. *
  60. * This is unfortunately needed, because the DAV: namespace violates the xml namespaces
  61. * spec, and causes the DOM to throw errors
  62. *
  63. * @param string $xmlDocument
  64. * @return array|string|null
  65. */
  66. static function convertDAVNamespace($xmlDocument) {
  67. // This is used to map the DAV: namespace to urn:DAV. This is needed, because the DAV:
  68. // namespace is actually a violation of the XML namespaces specification, and will cause errors
  69. return preg_replace("/xmlns(:[A-Za-z0-9_]*)?=(\"|\')DAV:(\\2)/","xmlns\\1=\\2urn:DAV\\2",$xmlDocument);
  70. }
  71. /**
  72. * This method provides a generic way to load a DOMDocument for WebDAV use.
  73. *
  74. * This method throws a Sabre_DAV_Exception_BadRequest exception for any xml errors.
  75. * It does not preserve whitespace, and it converts the DAV: namespace to urn:DAV.
  76. *
  77. * @param string $xml
  78. * @throws Sabre_DAV_Exception_BadRequest
  79. * @return DOMDocument
  80. */
  81. static function loadDOMDocument($xml) {
  82. if (empty($xml))
  83. throw new Sabre_DAV_Exception_BadRequest('Empty XML document sent');
  84. // The BitKinex client sends xml documents as UTF-16. PHP 5.3.1 (and presumably lower)
  85. // does not support this, so we must intercept this and convert to UTF-8.
  86. if (substr($xml,0,12) === "\x3c\x00\x3f\x00\x78\x00\x6d\x00\x6c\x00\x20\x00") {
  87. // Note: the preceeding byte sequence is "<?xml" encoded as UTF_16, without the BOM.
  88. $xml = iconv('UTF-16LE','UTF-8',$xml);
  89. // Because the xml header might specify the encoding, we must also change this.
  90. // This regex looks for the string encoding="UTF-16" and replaces it with
  91. // encoding="UTF-8".
  92. $xml = preg_replace('|<\?xml([^>]*)encoding="UTF-16"([^>]*)>|u','<?xml\1encoding="UTF-8"\2>',$xml);
  93. }
  94. // Retaining old error setting
  95. $oldErrorSetting = libxml_use_internal_errors(true);
  96. // Clearing any previous errors
  97. libxml_clear_errors();
  98. $dom = new DOMDocument();
  99. $dom->loadXML(self::convertDAVNamespace($xml),LIBXML_NOWARNING | LIBXML_NOERROR);
  100. // We don't generally care about any whitespace
  101. $dom->preserveWhiteSpace = false;
  102. if ($error = libxml_get_last_error()) {
  103. libxml_clear_errors();
  104. throw new Sabre_DAV_Exception_BadRequest('The request body had an invalid XML body. (message: ' . $error->message . ', errorcode: ' . $error->code . ', line: ' . $error->line . ')');
  105. }
  106. // Restoring old mechanism for error handling
  107. if ($oldErrorSetting===false) libxml_use_internal_errors(false);
  108. return $dom;
  109. }
  110. /**
  111. * Parses all WebDAV properties out of a DOM Element
  112. *
  113. * Generally WebDAV properties are enclosed in {DAV:}prop elements. This
  114. * method helps by going through all these and pulling out the actual
  115. * propertynames, making them array keys and making the property values,
  116. * well.. the array values.
  117. *
  118. * If no value was given (self-closing element) null will be used as the
  119. * value. This is used in for example PROPFIND requests.
  120. *
  121. * Complex values are supported through the propertyMap argument. The
  122. * propertyMap should have the clark-notation properties as it's keys, and
  123. * classnames as values.
  124. *
  125. * When any of these properties are found, the unserialize() method will be
  126. * (statically) called. The result of this method is used as the value.
  127. *
  128. * @param DOMElement $parentNode
  129. * @param array $propertyMap
  130. * @return array
  131. */
  132. static function parseProperties(DOMElement $parentNode, array $propertyMap = array()) {
  133. $propList = array();
  134. foreach($parentNode->childNodes as $propNode) {
  135. if (Sabre_DAV_XMLUtil::toClarkNotation($propNode)!=='{DAV:}prop') continue;
  136. foreach($propNode->childNodes as $propNodeData) {
  137. /* If there are no elements in here, we actually get 1 text node, this special case is dedicated to netdrive */
  138. if ($propNodeData->nodeType != XML_ELEMENT_NODE) continue;
  139. $propertyName = Sabre_DAV_XMLUtil::toClarkNotation($propNodeData);
  140. if (isset($propertyMap[$propertyName])) {
  141. $propList[$propertyName] = call_user_func(array($propertyMap[$propertyName],'unserialize'),$propNodeData);
  142. } else {
  143. $propList[$propertyName] = $propNodeData->textContent;
  144. }
  145. }
  146. }
  147. return $propList;
  148. }
  149. }