35 $server->
on(
'method:GET', [$this,
'httpGet']);
36 $server->
on(
'method:OPTIONS', [$this,
'httpOptions']);
37 $server->
on(
'method:HEAD', [$this,
'httpHead']);
38 $server->
on(
'method:DELETE', [$this,
'httpDelete']);
39 $server->
on(
'method:PROPFIND', [$this,
'httpPropFind']);
40 $server->
on(
'method:PROPPATCH', [$this,
'httpPropPatch']);
41 $server->
on(
'method:PUT', [$this,
'httpPut']);
42 $server->
on(
'method:MKCOL', [$this,
'httpMkcol']);
43 $server->
on(
'method:MOVE', [$this,
'httpMove']);
44 $server->
on(
'method:COPY', [$this,
'httpCopy']);
45 $server->
on(
'method:REPORT', [$this,
'httpReport']);
47 $server->
on(
'propPatch', [$this,
'propPatchProtectedPropertyCheck'], 90);
48 $server->
on(
'propPatch', [$this,
'propPatchNodeUpdate'], 200);
49 $server->
on(
'propFind', [$this,
'propFind']);
50 $server->
on(
'propFind', [$this,
'propFindNode'], 120);
51 $server->
on(
'propFind', [$this,
'propFindLate'], 200);
53 $server->
on(
'exception', [$this,
'exception']);
81 $node = $this->server->tree->getNodeForPath(
$path);
83 if (!$node instanceof
IFile)
return;
88 if (is_string($body)) {
99 $httpHeaders = $this->server->getHTTPHeaders(
$path);
104 if (!isset($httpHeaders[
'Content-Type'])) {
105 $httpHeaders[
'Content-Type'] =
'application/octet-stream';
109 if (isset($httpHeaders[
'Content-Length'])) {
111 $nodeSize = $httpHeaders[
'Content-Length'];
114 unset($httpHeaders[
'Content-Length']);
122 $range = $this->server->getHTTPRange();
123 $ifRange = $request->
getHeader(
'If-Range');
124 $ignoreRangeHeader =
false;
128 if ($nodeSize && $range && $ifRange) {
133 $ifRangeDate = new \DateTime($ifRange);
137 if (!isset($httpHeaders[
'Last-Modified'])) $ignoreRangeHeader =
true;
139 $modified = new \DateTime($httpHeaders[
'Last-Modified']);
140 if ($modified > $ifRangeDate) $ignoreRangeHeader =
true;
146 if (!isset($httpHeaders[
'ETag'])) $ignoreRangeHeader =
true;
147 elseif ($httpHeaders[
'ETag'] !== $ifRange) $ignoreRangeHeader =
true;
152 if (!$ignoreRangeHeader && $nodeSize && $range) {
155 if (!is_null($range[0])) {
158 $end = $range[1] ? $range[1] : $nodeSize - 1;
160 throw new Exception\RequestedRangeNotSatisfiable(
'The start offset (' . $range[0] .
') exceeded the size of the entity (' . $nodeSize .
')');
162 if (
$end <
$start)
throw new Exception\RequestedRangeNotSatisfiable(
'The end offset (' . $range[1] .
') is lower than the start offset (' . $range[0] .
')');
163 if (
$end >= $nodeSize)
$end = $nodeSize - 1;
167 $start = $nodeSize - $range[1];
168 $end = $nodeSize - 1;
177 if (!stream_get_meta_data($body)[
'seekable'] || fseek($body,
$start, SEEK_SET) === -1) {
178 $consumeBlock = 8192;
179 for ($consumed = 0;
$start - $consumed > 0;){
180 if (feof($body))
throw new Exception\RequestedRangeNotSatisfiable(
'The start offset (' .
$start .
') exceeded the size of the entity (' . $consumed .
')');
181 $consumed += strlen(fread($body, min(
$start - $consumed, $consumeBlock)));
192 if ($nodeSize) $response->
setHeader(
'Content-Length', $nodeSize);
212 $methods = $this->server->getAllowedMethods($request->
getPath());
214 $response->
setHeader(
'Allow', strtoupper(implode(
', ', $methods)));
215 $features = [
'1',
'3',
'extended-mkcol'];
217 foreach ($this->server->getPlugins() as $plugin) {
218 $features = array_merge($features, $plugin->getFeatures());
221 $response->
setHeader(
'DAV', implode(
', ', $features));
222 $response->
setHeader(
'MS-Author-Via',
'DAV');
223 $response->
setHeader(
'Accept-Ranges',
'bytes');
224 $response->
setHeader(
'Content-Length',
'0');
253 $this->server->invokeMethod(
$subRequest, $response,
false);
263 $response->
setHeader(
'Content-Type',
'text/plain');
264 $response->
setHeader(
'X-Sabre-Real-Status', $e->getHTTPCode());
286 if (!$this->server->emit(
'beforeUnbind', [
$path]))
return false;
287 $this->server->tree->delete(
$path);
288 $this->server->emit(
'afterUnbind', [
$path]);
291 $response->
setHeader(
'Content-Length',
'0');
320 if (strlen($requestBody)) {
322 $propFindXml = $this->server->xml->expect(
'{DAV:}propfind', $requestBody);
324 throw new BadRequest($e->getMessage(), null, $e);
328 $propFindXml->allProp =
true;
329 $propFindXml->properties = [];
332 $depth = $this->server->getHTTPDepth(1);
334 if (!$this->server->enablePropfindDepthInfinity && $depth != 0) $depth = 1;
336 $newProperties = $this->server->getPropertiesIteratorForPath(
$path, $propFindXml->properties, $depth);
340 $response->
setHeader(
'Content-Type',
'application/xml; charset=utf-8');
341 $response->
setHeader(
'Vary',
'Brief,Prefer');
346 $features = [
'1',
'3',
'extended-mkcol'];
347 foreach ($this->server->getPlugins() as $plugin) {
348 $features = array_merge($features, $plugin->getFeatures());
350 $response->
setHeader(
'DAV', implode(
', ', $features));
352 $prefer = $this->server->getHTTPPrefer();
353 $minimal = $prefer[
'return'] ===
'minimal';
355 $data = $this->server->generateMultiStatus($newProperties, $minimal);
379 $propPatch = $this->server->xml->expect(
'{DAV:}propertyupdate', $request->
getBody());
381 throw new BadRequest($e->getMessage(), null, $e);
383 $newProperties = $propPatch->properties;
385 $result = $this->server->updateProperties(
$path, $newProperties);
387 $prefer = $this->server->getHTTPPrefer();
388 $response->
setHeader(
'Vary',
'Brief,Prefer');
390 if ($prefer[
'return'] ===
'minimal') {
397 if ((
int)
$code > 299) {
412 $response->
setHeader(
'Content-Type',
'application/xml; charset=utf-8');
418 if (isset($multiStatus[
$code])) {
419 $multiStatus[
$code][$propertyName] = null;
421 $multiStatus[
$code] = [$propertyName => null];
424 $multiStatus[
'href'] =
$path;
427 $this->server->generateMultiStatus([$multiStatus])
453 if ($request->
getHeader(
'Content-Range')) {
461 throw new Exception\BadRequest(
'Content-Range on PUT requests are forbidden.');
465 if (($expected = $request->
getHeader(
'X-Expected-Entity-Length')) && $expected > 0) {
489 $firstByte = fread($body, 1);
490 if (strlen($firstByte) !== 1) {
491 throw new Exception\Forbidden(
'This server is not compatible with OS/X finder. Consider using a different WebDAV client or webserver.');
497 $newBody =
fopen(
'php://temp',
'r+');
498 fwrite($newBody, $firstByte);
499 stream_copy_to_stream($body, $newBody);
506 if ($this->server->tree->nodeExists(
$path)) {
508 $node = $this->server->tree->getNodeForPath(
$path);
511 if (!($node instanceof
IFile))
throw new Exception\Conflict(
'PUT is not allowed on non-files.');
513 if (!$this->server->updateFile(
$path, $body, $etag)) {
517 $response->
setHeader(
'Content-Length',
'0');
518 if ($etag) $response->
setHeader(
'ETag', $etag);
525 if (!$this->server->createFile(
$path, $body, $etag)) {
530 $response->
setHeader(
'Content-Length',
'0');
531 if ($etag) $response->
setHeader(
'ETag', $etag);
563 throw new Exception\UnsupportedMediaType(
'The request body for the MKCOL request must have an xml Content-Type');
568 $mkcol = $this->server->xml->expect(
'{DAV:}mkcol', $requestBody);
570 throw new Exception\BadRequest($e->getMessage(), null, $e);
573 $properties = $mkcol->getProperties();
575 if (!isset($properties[
'{DAV:}resourcetype']))
576 throw new Exception\BadRequest(
'The mkcol request must include a {DAV:}resourcetype property');
578 $resourceType = $properties[
'{DAV:}resourcetype']->getValue();
579 unset($properties[
'{DAV:}resourcetype']);
584 $resourceType = [
'{DAV:}collection'];
588 $mkcol =
new MkCol($resourceType, $properties);
590 $result = $this->server->createCollection(
$path, $mkcol);
594 $response->
setHeader(
'Content-Type',
'application/xml; charset=utf-8');
597 $this->server->generateMultiStatus([
$result])
601 $response->
setHeader(
'Content-Length',
'0');
624 $moveInfo = $this->server->getCopyAndMoveInfo($request);
626 if ($moveInfo[
'destinationExists']) {
628 if (!$this->server->emit(
'beforeUnbind', [$moveInfo[
'destination']]))
return false;
631 if (!$this->server->emit(
'beforeUnbind', [
$path]))
return false;
632 if (!$this->server->emit(
'beforeBind', [$moveInfo[
'destination']]))
return false;
633 if (!$this->server->emit(
'beforeMove', [
$path, $moveInfo[
'destination']]))
return false;
635 if ($moveInfo[
'destinationExists']) {
637 $this->server->tree->delete($moveInfo[
'destination']);
638 $this->server->emit(
'afterUnbind', [$moveInfo[
'destination']]);
642 $this->server->tree->move(
$path, $moveInfo[
'destination']);
648 $this->server->emit(
'afterMove', [
$path, $moveInfo[
'destination']]);
649 $this->server->emit(
'afterUnbind', [
$path]);
650 $this->server->emit(
'afterBind', [$moveInfo[
'destination']]);
653 $response->
setHeader(
'Content-Length',
'0');
654 $response->
setStatus($moveInfo[
'destinationExists'] ? 204 : 201);
676 $copyInfo = $this->server->getCopyAndMoveInfo($request);
678 if (!$this->server->emit(
'beforeBind', [$copyInfo[
'destination']]))
return false;
679 if ($copyInfo[
'destinationExists']) {
680 if (!$this->server->emit(
'beforeUnbind', [$copyInfo[
'destination']]))
return false;
681 $this->server->tree->delete($copyInfo[
'destination']);
684 $this->server->tree->copy(
$path, $copyInfo[
'destination']);
685 $this->server->emit(
'afterBind', [$copyInfo[
'destination']]);
688 $response->
setHeader(
'Content-Length',
'0');
689 $response->
setStatus($copyInfo[
'destinationExists'] ? 204 : 201);
712 $result = $this->server->xml->parse(
718 if ($this->server->emit(
'report', [$rootElementName,
$result,
$path])) {
721 throw new Exception\ReportNotSupported();
746 $protected = array_intersect(
747 $this->server->protectedProperties,
748 array_keys($mutations)
770 $node = $this->server->tree->getNodeForPath(
$path);
773 $node->propPatch($propPatch);
789 $propFind->
handle(
'{DAV:}getlastmodified',
function() use ($node) {
796 if ($node instanceof
IFile) {
797 $propFind->
handle(
'{DAV:}getcontentlength', [$node,
'getSize']);
798 $propFind->
handle(
'{DAV:}getetag', [$node,
'getETag']);
799 $propFind->
handle(
'{DAV:}getcontenttype', [$node,
'getContentType']);
802 if ($node instanceof
IQuota) {
804 $propFind->
handle(
'{DAV:}quota-used-bytes',
function() use (&$quotaInfo, $node) {
805 $quotaInfo = $node->getQuotaInfo();
806 return $quotaInfo[0];
808 $propFind->
handle(
'{DAV:}quota-available-bytes',
function() use (&$quotaInfo, $node) {
810 $quotaInfo = $node->getQuotaInfo();
812 return $quotaInfo[1];
816 $propFind->
handle(
'{DAV:}supported-report-set',
function() use ($propFind) {
818 foreach ($this->server->getPlugins() as $plugin) {
819 $reports = array_merge($reports, $plugin->getSupportedReportSet($propFind->
getPath()));
823 $propFind->
handle(
'{DAV:}resourcetype',
function() use ($node) {
826 $propFind->
handle(
'{DAV:}supported-method-set',
function() use ($propFind) {
828 $this->server->getAllowedMethods($propFind->
getPath())
848 $nodeProperties = $node->getProperties($propertyNames);
849 foreach ($nodeProperties as $propertyName => $propertyValue) {
850 $propFind->
set($propertyName, $propertyValue, 200);
869 $propFind->
handle(
'{http://calendarserver.org/ns/}getctag',
function() use ($propFind) {
873 $val = $propFind->
get(
'{http://sabredav.org/ns}sync-token');
874 if ($val)
return $val;
876 $val = $propFind->
get(
'{DAV:}sync-token');
877 if ($val && is_scalar($val)) {
880 if ($val && $val instanceof Xml\Property\Href) {
881 return substr($val->getHref(), strlen(Sync\Plugin::SYNCTOKEN_PREFIX));
887 '{http://sabredav.org/ns}sync-token',
891 if (isset($result[
'{http://sabredav.org/ns}sync-token'])) {
892 return $result[
'{http://sabredav.org/ns}sync-token'];
894 if (isset($result[
'{DAV:}sync-token'])) {
895 $val = $result[
'{DAV:}sync-token'];
896 if (is_scalar($val)) {
898 } elseif ($val instanceof Xml\Property\Href) {
899 return substr($val->getHref(), strlen(Sync\Plugin::SYNCTOKEN_PREFIX));
918 $code = $e->getHTTPCode();
930 $this->server->getLogger()->log(
932 'Uncaught exception',
954 'description' =>
'The Core plugin provides a lot of the basic functionality required by WebDAV, such as a default implementation for all HTTP and WebDAV methods.',
This interface represents a HTTP response.
httpMove(RequestInterface $request, ResponseInterface $response)
WebDAV HTTP MOVE method.
httpPropFind(RequestInterface $request, ResponseInterface $response)
WebDAV PROPFIND.
The RequestInterface represents a HTTP request.
handle($propertyName, $valueOrCallBack)
Handles a specific property.
get($propertyName)
Returns the current value for a property.
initialize(Server $server)
Sets up the plugin.
httpCopy(RequestInterface $request, ResponseInterface $response)
WebDAV HTTP COPY method.
This is a base exception for any exception related to parsing xml files.
The baseclass for all server plugins.
setBody($body)
Updates the body resource with a new stream.
on($eventName, callable $callBack, $priority=100)
Subscribe to an event.
This class represents a set of properties that are going to be updated.
propPatchNodeUpdate($path, PropPatch $propPatch)
This method is called during property updates.
foreach($paths as $path) $request
getPath()
Returns the path this PROPFIND request is for.
httpGet(RequestInterface $request, ResponseInterface $response)
This is the default implementation for the GET method.
getBodyAsString()
Returns the body as a string.
httpMkcol(RequestInterface $request, ResponseInterface $response)
WebDAV MKCOL.
This class holds all the information about a PROPFIND request.
httpHead(RequestInterface $request, ResponseInterface $response)
HTTP HEAD.
set($propertyName, $value, $status=null)
Sets the value of the property.
httpPropPatch(RequestInterface $request, ResponseInterface $response)
WebDAV PROPPATCH.
get404Properties()
Returns all propertynames that have a 404 status, and thus don't have a value yet.
httpDelete(RequestInterface $request, ResponseInterface $response)
HTTP Delete.
$stream
PHP stream implementation.
getPluginName()
Returns a plugin name.
getBodyAsStream()
Returns the body as a readable stream resource.
{DAV:}resourcetype property
propFind(PropFind $propFind, INode $node)
This method is called when properties are retrieved.
supported-report-set property.
setResultCode($properties, $resultCode)
Sets the result code for one or more properties.
httpPut(RequestInterface $request, ResponseInterface $response)
HTTP PUT method.
exception($e)
Listens for exception events, and automatically logs them.
setStatus($status)
Sets the HTTP status code.
getUrl()
Returns the request url.
getPluginInfo()
Returns a bunch of meta-data about the plugin.
WebDAV PROPFIND request parser.
getHeader($name)
Returns a specific HTTP header, based on it's name.
httpOptions(RequestInterface $request, ResponseInterface $response)
HTTP OPTIONS.
The core plugin provides all the basic features for a WebDAV server.
httpReport(RequestInterface $request, ResponseInterface $response)
HTTP REPORT method implementation.
This interface represents a file in the directory tree.
getMutations()
Returns the full list of mutations.
The INode interface is the base interface, and the parent class of both ICollection and IFile...
supported-method-set property.
getPath()
Returns the relative path.
This class represents a MKCOL operation.
This property represents the {DAV:}getlastmodified property.
if($path[strlen($path) - 1]==='/') if(is_dir($path)) if(!file_exists($path)) if(preg_match('#\.php$#D', mb_strtolower($path, 'UTF-8'))) $contentType
propPatchProtectedPropertyCheck($path, PropPatch $propPatch)
This method is called during property updates.
propFindNode(PropFind $propFind, INode $node)
Fetches properties for a node.
propFindLate(PropFind $propFind, INode $node)
This method is called when properties are retrieved.
setHeader($name, $value)
Updates a HTTP header.
getBody()
Returns the message body, as it's internal representation.
getLastModified()
Returns the last modification time, as a unix timestamp.
addHeaders(array $headers)
Adds a new set of HTTP headers.