Response.php 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. <?php
  2. declare(strict_types=1);
  3. namespace Sabre\DAV\Xml\Element;
  4. use Sabre\Xml\Element;
  5. use Sabre\Xml\Reader;
  6. use Sabre\Xml\Writer;
  7. /**
  8. * WebDAV {DAV:}response parser.
  9. *
  10. * This class parses the {DAV:}response element, as defined in:
  11. *
  12. * https://tools.ietf.org/html/rfc4918#section-14.24
  13. *
  14. * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
  15. * @author Evert Pot (http://www.rooftopsolutions.nl/)
  16. * @license http://sabre.io/license/ Modified BSD License
  17. */
  18. class Response implements Element
  19. {
  20. /**
  21. * Url for the response.
  22. *
  23. * @var string
  24. */
  25. protected $href;
  26. /**
  27. * Propertylist, ordered by HTTP status code.
  28. *
  29. * @var array
  30. */
  31. protected $responseProperties;
  32. /**
  33. * The HTTP status for an entire response.
  34. *
  35. * This is currently only used in WebDAV-Sync
  36. *
  37. * @var string|null
  38. */
  39. protected $httpStatus;
  40. /**
  41. * The href argument is a url relative to the root of the server. This
  42. * class will calculate the full path.
  43. *
  44. * The responseProperties argument is a list of properties
  45. * within an array with keys representing HTTP status codes
  46. *
  47. * Besides specific properties, the entire {DAV:}response element may also
  48. * have a http status code.
  49. * In most cases you don't need it.
  50. *
  51. * This is currently used by the Sync extension to indicate that a node is
  52. * deleted.
  53. *
  54. * @param string $href
  55. * @param string $httpStatus
  56. */
  57. public function __construct($href, array $responseProperties, $httpStatus = null)
  58. {
  59. $this->href = $href;
  60. $this->responseProperties = $responseProperties;
  61. $this->httpStatus = $httpStatus;
  62. }
  63. /**
  64. * Returns the url.
  65. *
  66. * @return string
  67. */
  68. public function getHref()
  69. {
  70. return $this->href;
  71. }
  72. /**
  73. * Returns the httpStatus value.
  74. *
  75. * @return string
  76. */
  77. public function getHttpStatus()
  78. {
  79. return $this->httpStatus;
  80. }
  81. /**
  82. * Returns the property list.
  83. *
  84. * @return array
  85. */
  86. public function getResponseProperties()
  87. {
  88. return $this->responseProperties;
  89. }
  90. /**
  91. * The serialize method is called during xml writing.
  92. *
  93. * It should use the $writer argument to encode this object into XML.
  94. *
  95. * Important note: it is not needed to create the parent element. The
  96. * parent element is already created, and we only have to worry about
  97. * attributes, child elements and text (if any).
  98. *
  99. * Important note 2: If you are writing any new elements, you are also
  100. * responsible for closing them.
  101. */
  102. public function xmlSerialize(Writer $writer)
  103. {
  104. /*
  105. * Accordingly to the RFC the element looks like:
  106. * <!ELEMENT response (href, ((href*, status)|(propstat+)), error?, responsedescription? , location?) >
  107. *
  108. * So the response
  109. * - MUST contain a href and
  110. * - EITHER a status and additional href(s)
  111. * OR one or more propstat(s)
  112. */
  113. $writer->writeElement('{DAV:}href', $writer->contextUri.\Sabre\HTTP\encodePath($this->getHref()));
  114. $empty = true;
  115. $httpStatus = $this->getHTTPStatus();
  116. // Add propstat elements
  117. foreach ($this->getResponseProperties() as $status => $properties) {
  118. // Skipping empty lists
  119. if (!$properties || (!is_int($status) && !ctype_digit($status))) {
  120. continue;
  121. }
  122. $empty = false;
  123. $writer->startElement('{DAV:}propstat');
  124. $writer->writeElement('{DAV:}prop', $properties);
  125. $writer->writeElement('{DAV:}status', 'HTTP/1.1 '.$status.' '.\Sabre\HTTP\Response::$statusCodes[$status]);
  126. $writer->endElement(); // {DAV:}propstat
  127. }
  128. // The WebDAV spec only allows the status element on responses _without_ a propstat
  129. if ($empty) {
  130. if (null !== $httpStatus) {
  131. $writer->writeElement('{DAV:}status', 'HTTP/1.1 '.$httpStatus.' '.\Sabre\HTTP\Response::$statusCodes[$httpStatus]);
  132. } else {
  133. /*
  134. * The WebDAV spec _requires_ at least one DAV:propstat to appear for
  135. * every DAV:response if there is no status.
  136. * In some circumstances however, there are no properties to encode.
  137. *
  138. * In those cases we MUST specify at least one DAV:propstat anyway, with
  139. * no properties.
  140. */
  141. $writer->writeElement('{DAV:}propstat', [
  142. '{DAV:}prop' => [],
  143. '{DAV:}status' => 'HTTP/1.1 418 '.\Sabre\HTTP\Response::$statusCodes[418],
  144. ]);
  145. }
  146. }
  147. }
  148. /**
  149. * The deserialize method is called during xml parsing.
  150. *
  151. * This method is called statically, this is because in theory this method
  152. * may be used as a type of constructor, or factory method.
  153. *
  154. * Often you want to return an instance of the current class, but you are
  155. * free to return other data as well.
  156. *
  157. * You are responsible for advancing the reader to the next element. Not
  158. * doing anything will result in a never-ending loop.
  159. *
  160. * If you just want to skip parsing for this element altogether, you can
  161. * just call $reader->next();
  162. *
  163. * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
  164. * the next element.
  165. *
  166. * @return mixed
  167. */
  168. public static function xmlDeserialize(Reader $reader)
  169. {
  170. $reader->pushContext();
  171. $reader->elementMap['{DAV:}propstat'] = 'Sabre\\Xml\\Element\\KeyValue';
  172. // We are overriding the parser for {DAV:}prop. This deserializer is
  173. // almost identical to the one for Sabre\Xml\Element\KeyValue.
  174. //
  175. // The difference is that if there are any child-elements inside of
  176. // {DAV:}prop, that have no value, normally any deserializers are
  177. // called. But we don't want this, because a singular element without
  178. // child-elements implies 'no value' in {DAV:}prop, so we want to skip
  179. // deserializers and just set null for those.
  180. $reader->elementMap['{DAV:}prop'] = function (Reader $reader) {
  181. if ($reader->isEmptyElement) {
  182. $reader->next();
  183. return [];
  184. }
  185. if (!$reader->read()) {
  186. $reader->next();
  187. return [];
  188. }
  189. if (Reader::END_ELEMENT === $reader->nodeType) {
  190. $reader->next();
  191. return [];
  192. }
  193. $values = [];
  194. do {
  195. if (Reader::ELEMENT === $reader->nodeType) {
  196. $clark = $reader->getClark();
  197. if ($reader->isEmptyElement) {
  198. $values[$clark] = null;
  199. $reader->next();
  200. } else {
  201. $values[$clark] = $reader->parseCurrentElement()['value'];
  202. }
  203. } else {
  204. if (!$reader->read()) {
  205. break;
  206. }
  207. }
  208. } while (Reader::END_ELEMENT !== $reader->nodeType);
  209. $reader->read();
  210. return $values;
  211. };
  212. $elems = $reader->parseInnerTree();
  213. $reader->popContext();
  214. $href = null;
  215. $propertyLists = [];
  216. $statusCode = null;
  217. foreach ($elems as $elem) {
  218. switch ($elem['name']) {
  219. case '{DAV:}href':
  220. $href = $elem['value'];
  221. break;
  222. case '{DAV:}propstat':
  223. $status = $elem['value']['{DAV:}status'];
  224. list(, $status) = explode(' ', $status, 3);
  225. $properties = isset($elem['value']['{DAV:}prop']) ? $elem['value']['{DAV:}prop'] : [];
  226. if ($properties) {
  227. $propertyLists[$status] = $properties;
  228. }
  229. break;
  230. case '{DAV:}status':
  231. list(, $statusCode) = explode(' ', $elem['value'], 3);
  232. break;
  233. }
  234. }
  235. return new self($href, $propertyLists, $statusCode);
  236. }
  237. }