Blame view

sources/3rdparty/sabre/dav/lib/Sabre/DAV/PartialUpdate/Plugin.php 7.58 KB
03e52840d   Kload   Init
1
  <?php
6d9380f96   Cédric Dupont   Update sources OC...
2
3
4
5
  
  namespace Sabre\DAV\PartialUpdate;
  
  use Sabre\DAV;
03e52840d   Kload   Init
6
7
8
9
10
11
12
  /**
   * Partial update plugin (Patch method)
   *
   * This plugin provides a way to modify only part of a target resource
   * It may bu used to update a file chunk, upload big a file into smaller
   * chunks or resume an upload.
   *
6d9380f96   Cédric Dupont   Update sources OC...
13
   * $patchPlugin = new \Sabre\DAV\PartialUpdate\Plugin();
03e52840d   Kload   Init
14
15
   * $server->addPlugin($patchPlugin);
   *
6d9380f96   Cédric Dupont   Update sources OC...
16
   * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
03e52840d   Kload   Init
17
   * @author Jean-Tiare LE BIGOT (http://www.jtlebi.fr/)
6d9380f96   Cédric Dupont   Update sources OC...
18
   * @license http://sabre.io/license/ Modified BSD License
03e52840d   Kload   Init
19
   */
6d9380f96   Cédric Dupont   Update sources OC...
20
21
22
23
24
  class Plugin extends DAV\ServerPlugin {
  
      const RANGE_APPEND = 1;
      const RANGE_START = 2;
      const RANGE_END = 3;
03e52840d   Kload   Init
25
26
27
28
  
      /**
       * Reference to server
       *
6d9380f96   Cédric Dupont   Update sources OC...
29
       * @var Sabre\DAV\Server
03e52840d   Kload   Init
30
31
32
33
34
35
36
37
       */
      protected $server;
  
      /**
       * Initializes the plugin
       *
       * This method is automatically called by the Server class after addPlugin.
       *
6d9380f96   Cédric Dupont   Update sources OC...
38
       * @param DAV\Server $server
03e52840d   Kload   Init
39
40
       * @return void
       */
6d9380f96   Cédric Dupont   Update sources OC...
41
      public function initialize(DAV\Server $server) {
03e52840d   Kload   Init
42
43
44
45
46
47
48
49
50
51
  
          $this->server = $server;
          $server->subscribeEvent('unknownMethod',array($this,'unknownMethod'));
  
      }
  
      /**
       * Returns a plugin name.
       *
       * Using this name other plugins will be able to access other plugins
6d9380f96   Cédric Dupont   Update sources OC...
52
       * using DAV\Server::getPlugin
03e52840d   Kload   Init
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
       *
       * @return string
       */
      public function getPluginName() {
  
          return 'partialupdate';
  
      }
  
      /**
       * This method is called by the Server if the user used an HTTP method
       * the server didn't recognize.
       *
       * This plugin intercepts the PATCH methods.
       *
       * @param string $method
       * @param string $uri
       * @return bool|null
       */
      public function unknownMethod($method, $uri) {
  
          switch($method) {
6d9380f96   Cédric Dupont   Update sources OC...
75

03e52840d   Kload   Init
76
77
78
79
80
81
82
83
84
85
86
87
88
              case 'PATCH':
                  return $this->httpPatch($uri);
  
          }
  
      }
  
      /**
       * Use this method to tell the server this plugin defines additional
       * HTTP methods.
       *
       * This method is passed a uri. It should only return HTTP methods that are
       * available for the specified uri.
6d9380f96   Cédric Dupont   Update sources OC...
89
       *
03e52840d   Kload   Init
90
91
92
93
94
95
96
97
       * We claim to support PATCH method (partial update) if and only if
       *     - the node exist
       *     - the node implements our partial update interface
       *
       * @param string $uri
       * @return array
       */
      public function getHTTPMethods($uri) {
6d9380f96   Cédric Dupont   Update sources OC...
98

03e52840d   Kload   Init
99
          $tree = $this->server->tree;
6d9380f96   Cédric Dupont   Update sources OC...
100
101
102
103
104
105
106
          if ($tree->nodeExists($uri)) {
              $node = $tree->getNodeForPath($uri);
              if ($node instanceof IFile || $node instanceof IPatchSupport) {
                  return array('PATCH');
              }
          }
          return array();
03e52840d   Kload   Init
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
  
      }
  
      /**
       * Returns a list of features for the HTTP OPTIONS Dav: header.
       *
       * @return array
       */
      public function getFeatures() {
  
          return array('sabredav-partialupdate');
  
      }
  
      /**
       * Patch an uri
       *
6d9380f96   Cédric Dupont   Update sources OC...
124
       * The WebDAV patch request can be used to modify only a part of an
03e52840d   Kload   Init
125
126
127
128
129
130
131
132
133
134
       * existing resource. If the resource does not exist yet and the first
       * offset is not 0, the request fails
       *
       * @param string $uri
       * @return void
       */
      protected function httpPatch($uri) {
  
          // Get the node. Will throw a 404 if not found
          $node = $this->server->tree->getNodeForPath($uri);
6d9380f96   Cédric Dupont   Update sources OC...
135
136
          if (!$node instanceof IFile && !$node instanceof IPatchSupport) {
              throw new DAV\Exception\MethodNotAllowed('The target resource does not support the PATCH method.');
03e52840d   Kload   Init
137
138
139
140
141
          }
  
          $range = $this->getHTTPUpdateRange();
  
          if (!$range) {
6d9380f96   Cédric Dupont   Update sources OC...
142
              throw new DAV\Exception\BadRequest('No valid "X-Update-Range" found in the headers');
03e52840d   Kload   Init
143
          }
6d9380f96   Cédric Dupont   Update sources OC...
144

03e52840d   Kload   Init
145
146
147
          $contentType = strtolower(
              $this->server->httpRequest->getHeader('Content-Type')
          );
6d9380f96   Cédric Dupont   Update sources OC...
148

03e52840d   Kload   Init
149
          if ($contentType != 'application/x-sabredav-partialupdate') {
6d9380f96   Cédric Dupont   Update sources OC...
150
              throw new DAV\Exception\UnsupportedMediaType('Unknown Content-Type header "' . $contentType . '"');
03e52840d   Kload   Init
151
152
153
          }
  
          $len = $this->server->httpRequest->getHeader('Content-Length');
6d9380f96   Cédric Dupont   Update sources OC...
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
          if (!$len) throw new DAV\Exception\LengthRequired('A Content-Length header is required');
  
          switch($range[0]) {
              case self::RANGE_START :
                  // Calculate the end-range if it doesn't exist.
                  if (!$range[2]) {
                      $range[2] = $range[1] + $len - 1;
                  } else {
                      if ($range[2] < $range[1]) {
                          throw new DAV\Exception\RequestedRangeNotSatisfiable('The end offset (' . $range[2] . ') is lower than the start offset (' . $range[1] . ')');
                      }
                      if($range[2] - $range[1] + 1 != $len) {
                          throw new DAV\Exception\RequestedRangeNotSatisfiable('Actual data length (' . $len . ') is not consistent with begin (' . $range[1] . ') and end (' . $range[2] . ') offsets');
                      }
                  }
                  break;
          }
03e52840d   Kload   Init
171
172
173
174
175
176
177
          // Checking If-None-Match and related headers.
          if (!$this->server->checkPreconditions()) return;
  
          if (!$this->server->broadcastEvent('beforeWriteContent',array($uri, $node, null)))
              return;
  
          $body = $this->server->httpRequest->getBody();
6d9380f96   Cédric Dupont   Update sources OC...
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
  
  
          if ($node instanceof IPatchSupport) {
              $etag = $node->patch($body, $range[0], isset($range[1])?$range[1]:null);
          } else {
              // The old interface
              switch($range[0]) {
                  case self::RANGE_APPEND :
                      throw new DAV\Exception\NotImplemented('This node does not support the append syntax. Please upgrade it to IPatchSupport');
                  case self::RANGE_START :
                      $etag = $node->putRange($body, $range[1]);
                      break;
                  case self::RANGE_END :
                      throw new DAV\Exception\NotImplemented('This node does not support the end-range syntax. Please upgrade it to IPatchSupport');
                      break;
              }
          }
03e52840d   Kload   Init
195
196
197
198
199
200
201
202
203
204
  
          $this->server->broadcastEvent('afterWriteContent',array($uri, $node));
  
          $this->server->httpResponse->setHeader('Content-Length','0');
          if ($etag) $this->server->httpResponse->setHeader('ETag',$etag);
          $this->server->httpResponse->sendStatus(204);
  
          return false;
  
      }
6d9380f96   Cédric Dupont   Update sources OC...
205

03e52840d   Kload   Init
206
207
208
209
     /**
       * Returns the HTTP custom range update header
       *
       * This method returns null if there is no well-formed HTTP range request
6d9380f96   Cédric Dupont   Update sources OC...
210
211
212
213
       * header. It returns array(1) if it was an append request, array(2,
       * $start, $end) if it's a start and end range, lastly it's array(3,
       * $endoffset) if the offset was negative, and should be calculated from
       * the end of the file.
03e52840d   Kload   Init
214
       *
6d9380f96   Cédric Dupont   Update sources OC...
215
       * Examples:
03e52840d   Kload   Init
216
       *
6d9380f96   Cédric Dupont   Update sources OC...
217
218
219
220
221
       * null - invalid
       * array(1) - append
       * array(2,10,15) - update bytes 10, 11, 12, 13, 14, 15
       * array(2,10,null) - update bytes 10 until the end of the patch body
       * array(3,-5) - update from 5 bytes from the end of the file.
03e52840d   Kload   Init
222
223
224
225
226
227
228
229
230
       *
       * @return array|null
       */
      public function getHTTPUpdateRange() {
  
          $range = $this->server->httpRequest->getHeader('X-Update-Range');
          if (is_null($range)) return null;
  
          // Matching "Range: bytes=1234-5678: both numbers are optional
6d9380f96   Cédric Dupont   Update sources OC...
231
          if (!preg_match('/^(append)|(?:bytes=([0-9]+)-([0-9]*))|(?:bytes=(-[0-9]+))$/i',$range,$matches)) return null;
03e52840d   Kload   Init
232

6d9380f96   Cédric Dupont   Update sources OC...
233
234
235
236
237
238
239
240
241
          if ($matches[1]==='append') {
              return array(self::RANGE_APPEND);
          } elseif (strlen($matches[2])>0) {
              return array(self::RANGE_START, $matches[2], $matches[3]?:null);
          } elseif ($matches[4]) {
              return array(self::RANGE_END, $matches[4]);
          } else {
              return null;
          }
03e52840d   Kload   Init
242
243
244
  
      }
  }