3 * Partial update addon (Patch method)
5 * This addon provides a way to modify only part of a target resource
6 * It may bu used to update a file chunk, upload big a file into smaller
7 * chunks or resume an upload.
9 * $patchPlugin = new Sabre_DAV_Patch_Plugin();
10 * $server->addPlugin($patchPlugin);
14 * @copyright Copyright (C) 2007-2012 Rooftop Solutions. All rights reserved.
15 * @author Jean-Tiare LE BIGOT (http://www.jtlebi.fr/)
16 * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License
18 class Sabre_DAV_PartialUpdate_Plugin extends Sabre_DAV_ServerPlugin {
23 * @var Sabre_DAV_Server
28 * Initializes the addon
30 * This method is automatically called by the Server class after addPlugin.
32 * @param Sabre_DAV_Server $server
35 public function initialize(Sabre_DAV_Server $server) {
37 $this->server = $server;
38 $server->subscribeEvent('unknownMethod',array($this,'unknownMethod'));
43 * Returns a addon name.
45 * Using this name other addons will be able to access other addons
46 * using Sabre_DAV_Server::getPlugin
50 public function getPluginName() {
52 return 'partialupdate';
57 * This method is called by the Server if the user used an HTTP method
58 * the server didn't recognize.
60 * This addon intercepts the PATCH methods.
62 * @param string $method
66 public function unknownMethod($method, $uri) {
71 return $this->httpPatch($uri);
78 * Use this method to tell the server this addon defines additional
81 * This method is passed a uri. It should only return HTTP methods that are
82 * available for the specified uri.
84 * We claim to support PATCH method (partial update) if and only if
86 * - the node implements our partial update interface
91 public function getHTTPMethods($uri) {
93 $tree = $this->server->tree;
95 if ($tree->nodeExists($uri) &&
96 $tree->getNodeForPath($uri) instanceof Sabre_DAV_PartialUpdate_IFile) {
97 return array('PATCH');
105 * Returns a list of features for the HTTP OPTIONS Dav: header.
109 public function getFeatures() {
111 return array('sabredav-partialupdate');
118 * The WebDAV patch request can be used to modify only a part of an
119 * existing resource. If the resource does not exist yet and the first
120 * offset is not 0, the request fails
125 protected function httpPatch($uri) {
127 // Get the node. Will throw a 404 if not found
128 $node = $this->server->tree->getNodeForPath($uri);
129 if (!($node instanceof Sabre_DAV_PartialUpdate_IFile)) {
130 throw new Sabre_DAV_Exception_MethodNotAllowed('The target resource does not support the PATCH method.');
133 $range = $this->getHTTPUpdateRange();
136 throw new Sabre_DAV_Exception_BadRequest('No valid "X-Update-Range" found in the headers');
139 $contentType = strtolower(
140 $this->server->httpRequest->getHeader('Content-Type')
143 if ($contentType != 'application/x-sabredav-partialupdate') {
144 throw new Sabre_DAV_Exception_UnsupportedMediaType('Unknown Content-Type header "' . $contentType . '"');
147 $len = $this->server->httpRequest->getHeader('Content-Length');
149 // Load the begin and end data
150 $start = ($range[0])?$range[0]:0;
151 $end = ($range[1])?$range[1]:$len-1;
155 throw new Sabre_DAV_Exception_RequestedRangeNotSatisfiable('The end offset (' . $range[1] . ') is lower than the start offset (' . $range[0] . ')');
156 if($end - $start + 1 != $len)
157 throw new Sabre_DAV_Exception_RequestedRangeNotSatisfiable('Actual data length (' . $len . ') is not consistent with begin (' . $range[0] . ') and end (' . $range[1] . ') offsets');
159 // Checking If-None-Match and related headers.
160 if (!$this->server->checkPreconditions()) return;
162 if (!$this->server->broadcastEvent('beforeWriteContent',array($uri, $node, null)))
165 $body = $this->server->httpRequest->getBody();
166 $etag = $node->putRange($body, $start-1);
168 $this->server->broadcastEvent('afterWriteContent',array($uri, $node));
170 $this->server->httpResponse->setHeader('Content-Length','0');
171 if ($etag) $this->server->httpResponse->setHeader('ETag',$etag);
172 $this->server->httpResponse->sendStatus(204);
179 * Returns the HTTP custom range update header
181 * This method returns null if there is no well-formed HTTP range request
182 * header or array($start, $end).
184 * The first number is the offset of the first byte in the range.
185 * The second number is the offset of the last byte in the range.
187 * If the second offset is null, it should be treated as the offset of the last byte of the entity
188 * If the first offset is null, the second offset should be used to retrieve the last x bytes of the entity
192 public function getHTTPUpdateRange() {
194 $range = $this->server->httpRequest->getHeader('X-Update-Range');
195 if (is_null($range)) return null;
197 // Matching "Range: bytes=1234-5678: both numbers are optional
199 if (!preg_match('/^bytes=([0-9]*)-([0-9]*)$/i',$range,$matches)) return null;
201 if ($matches[1]==='' && $matches[2]==='') return null;
204 $matches[1]!==''?$matches[1]:null,
205 $matches[2]!==''?$matches[2]:null,