PageRenderTime 43ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Sabre/DAV/PartialUpdate/Plugin.php

https://github.com/KOLANICH/SabreDAV
PHP | 212 lines | 73 code | 46 blank | 93 comment | 16 complexity | 1c1d796eb9f14f48f72cebf55c12099c MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. namespace Sabre\DAV\PartialUpdate;
  3. use Sabre\DAV;
  4. /**
  5. * Partial update plugin (Patch method)
  6. *
  7. * This plugin provides a way to modify only part of a target resource
  8. * It may bu used to update a file chunk, upload big a file into smaller
  9. * chunks or resume an upload.
  10. *
  11. * $patchPlugin = new \Sabre\DAV\PartialUpdate\Plugin();
  12. * $server->addPlugin($patchPlugin);
  13. *
  14. * @copyright Copyright (C) 2007-2013 fruux GmbH (https://fruux.com/).
  15. * @author Jean-Tiare LE BIGOT (http://www.jtlebi.fr/)
  16. * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
  17. */
  18. class Plugin extends DAV\ServerPlugin {
  19. /**
  20. * Reference to server
  21. *
  22. * @var Sabre\DAV\Server
  23. */
  24. protected $server;
  25. /**
  26. * Initializes the plugin
  27. *
  28. * This method is automatically called by the Server class after addPlugin.
  29. *
  30. * @param DAV\Server $server
  31. * @return void
  32. */
  33. public function initialize(DAV\Server $server) {
  34. $this->server = $server;
  35. $server->subscribeEvent('unknownMethod',array($this,'unknownMethod'));
  36. }
  37. /**
  38. * Returns a plugin name.
  39. *
  40. * Using this name other plugins will be able to access other plugins
  41. * using DAV\Server::getPlugin
  42. *
  43. * @return string
  44. */
  45. public function getPluginName() {
  46. return 'partialupdate';
  47. }
  48. /**
  49. * This method is called by the Server if the user used an HTTP method
  50. * the server didn't recognize.
  51. *
  52. * This plugin intercepts the PATCH methods.
  53. *
  54. * @param string $method
  55. * @param string $uri
  56. * @return bool|null
  57. */
  58. public function unknownMethod($method, $uri) {
  59. switch($method) {
  60. case 'PATCH':
  61. return $this->httpPatch($uri);
  62. }
  63. }
  64. /**
  65. * Use this method to tell the server this plugin defines additional
  66. * HTTP methods.
  67. *
  68. * This method is passed a uri. It should only return HTTP methods that are
  69. * available for the specified uri.
  70. *
  71. * We claim to support PATCH method (partial update) if and only if
  72. * - the node exist
  73. * - the node implements our partial update interface
  74. *
  75. * @param string $uri
  76. * @return array
  77. */
  78. public function getHTTPMethods($uri) {
  79. $tree = $this->server->tree;
  80. if ($tree->nodeExists($uri) &&
  81. $tree->getNodeForPath($uri) instanceof IFile) {
  82. return array('PATCH');
  83. }
  84. return array();
  85. }
  86. /**
  87. * Returns a list of features for the HTTP OPTIONS Dav: header.
  88. *
  89. * @return array
  90. */
  91. public function getFeatures() {
  92. return array('sabredav-partialupdate');
  93. }
  94. /**
  95. * Patch an uri
  96. *
  97. * The WebDAV patch request can be used to modify only a part of an
  98. * existing resource. If the resource does not exist yet and the first
  99. * offset is not 0, the request fails
  100. *
  101. * @param string $uri
  102. * @return void
  103. */
  104. protected function httpPatch($uri) {
  105. // Get the node. Will throw a 404 if not found
  106. $node = $this->server->tree->getNodeForPath($uri);
  107. if (!($node instanceof IFile)) {
  108. throw new DAV\Exception\MethodNotAllowed('The target resource does not support the PATCH method.');
  109. }
  110. $range = $this->getHTTPUpdateRange();
  111. if (!$range) {
  112. throw new DAV\Exception\BadRequest('No valid "X-Update-Range" found in the headers');
  113. }
  114. $contentType = strtolower(
  115. $this->server->httpRequest->getHeader('Content-Type')
  116. );
  117. if ($contentType != 'application/x-sabredav-partialupdate') {
  118. throw new DAV\Exception\UnsupportedMediaType('Unknown Content-Type header "' . $contentType . '"');
  119. }
  120. $len = $this->server->httpRequest->getHeader('Content-Length');
  121. // Load the begin and end data
  122. $start = ($range[0])?$range[0]:0;
  123. $end = ($range[1])?$range[1]:$len-1;
  124. // Check consistency
  125. if($end < $start)
  126. throw new DAV\Exception\RequestedRangeNotSatisfiable('The end offset (' . $range[1] . ') is lower than the start offset (' . $range[0] . ')');
  127. if($end - $start + 1 != $len)
  128. throw new DAV\Exception\RequestedRangeNotSatisfiable('Actual data length (' . $len . ') is not consistent with begin (' . $range[0] . ') and end (' . $range[1] . ') offsets');
  129. // Checking If-None-Match and related headers.
  130. if (!$this->server->checkPreconditions()) return;
  131. if (!$this->server->broadcastEvent('beforeWriteContent',array($uri, $node, null)))
  132. return;
  133. $body = $this->server->httpRequest->getBody();
  134. $etag = $node->putRange($body, $start-1);
  135. $this->server->broadcastEvent('afterWriteContent',array($uri, $node));
  136. $this->server->httpResponse->setHeader('Content-Length','0');
  137. if ($etag) $this->server->httpResponse->setHeader('ETag',$etag);
  138. $this->server->httpResponse->sendStatus(204);
  139. return false;
  140. }
  141. /**
  142. * Returns the HTTP custom range update header
  143. *
  144. * This method returns null if there is no well-formed HTTP range request
  145. * header or array($start, $end).
  146. *
  147. * The first number is the offset of the first byte in the range.
  148. * The second number is the offset of the last byte in the range.
  149. *
  150. * If the second offset is null, it should be treated as the offset of the last byte of the entity
  151. * If the first offset is null, the second offset should be used to retrieve the last x bytes of the entity
  152. *
  153. * @return array|null
  154. */
  155. public function getHTTPUpdateRange() {
  156. $range = $this->server->httpRequest->getHeader('X-Update-Range');
  157. if (is_null($range)) return null;
  158. // Matching "Range: bytes=1234-5678: both numbers are optional
  159. if (!preg_match('/^bytes=([0-9]*)-([0-9]*)$/i',$range,$matches)) return null;
  160. if ($matches[1]==='' && $matches[2]==='') return null;
  161. return array(
  162. $matches[1]!==''?$matches[1]:null,
  163. $matches[2]!==''?$matches[2]:null,
  164. );
  165. }
  166. }