File manager - Edit - /home/jardides/www/Jardi-design/images/administrator/Connector.tar
Back
Sabre/LockPlugin.php 0000604 00000004714 15247115714 0010371 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Stefan Weil <sw@weilnetz.de> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OCA\DAV\Connector\Sabre\Exception\FileLocked; use OCP\Lock\ILockingProvider; use OCP\Lock\LockedException; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\ServerPlugin; use Sabre\HTTP\RequestInterface; class LockPlugin extends ServerPlugin { /** * Reference to main server object * * @var \Sabre\DAV\Server */ private $server; /** * {@inheritdoc} */ public function initialize(\Sabre\DAV\Server $server) { $this->server = $server; $this->server->on('beforeMethod', [$this, 'getLock'], 50); $this->server->on('afterMethod', [$this, 'releaseLock'], 50); } public function getLock(RequestInterface $request) { // we can't listen on 'beforeMethod:PUT' due to order of operations with setting up the tree // so instead we limit ourselves to the PUT method manually if ($request->getMethod() !== 'PUT' || isset($_SERVER['HTTP_OC_CHUNKED'])) { return; } try { $node = $this->server->tree->getNodeForPath($request->getPath()); } catch (NotFound $e) { return; } if ($node instanceof Node) { try { $node->acquireLock(ILockingProvider::LOCK_SHARED); } catch (LockedException $e) { throw new FileLocked($e->getMessage(), $e->getCode(), $e); } } } public function releaseLock(RequestInterface $request) { if ($request->getMethod() !== 'PUT' || isset($_SERVER['HTTP_OC_CHUNKED'])) { return; } try { $node = $this->server->tree->getNodeForPath($request->getPath()); } catch (NotFound $e) { return; } if ($node instanceof Node) { $node->releaseLock(ILockingProvider::LOCK_SHARED); } } } Sabre/Auth.php 0000604 00000017520 15247115714 0007222 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Bart Visscher <bartv@thisnet.nl> * @author Christoph Wurst <christoph@owncloud.com> * @author Jakob Sack <mail@jakobsack.de> * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Markus Goetz <markus@woboq.com> * @author Michael Gapczynski <GapczynskiM@gmail.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use Exception; use OC\Authentication\Exceptions\PasswordLoginForbiddenException; use OC\Authentication\TwoFactorAuth\Manager; use OC\Security\Bruteforce\Throttler; use OC\User\Session; use OCA\DAV\Connector\Sabre\Exception\PasswordLoginForbidden; use OCP\IRequest; use OCP\ISession; use Sabre\DAV\Auth\Backend\AbstractBasic; use Sabre\DAV\Exception\NotAuthenticated; use Sabre\DAV\Exception\ServiceUnavailable; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; class Auth extends AbstractBasic { const DAV_AUTHENTICATED = 'AUTHENTICATED_TO_DAV_BACKEND'; /** @var ISession */ private $session; /** @var Session */ private $userSession; /** @var IRequest */ private $request; /** @var string */ private $currentUser; /** @var Manager */ private $twoFactorManager; /** @var Throttler */ private $throttler; /** * @param ISession $session * @param Session $userSession * @param IRequest $request * @param Manager $twoFactorManager * @param Throttler $throttler * @param string $principalPrefix */ public function __construct(ISession $session, Session $userSession, IRequest $request, Manager $twoFactorManager, Throttler $throttler, $principalPrefix = 'principals/users/') { $this->session = $session; $this->userSession = $userSession; $this->twoFactorManager = $twoFactorManager; $this->request = $request; $this->throttler = $throttler; $this->principalPrefix = $principalPrefix; // setup realm $defaults = new \OCP\Defaults(); $this->realm = $defaults->getName(); } /** * Whether the user has initially authenticated via DAV * * This is required for WebDAV clients that resent the cookies even when the * account was changed. * * @see https://github.com/owncloud/core/issues/13245 * * @param string $username * @return bool */ public function isDavAuthenticated($username) { return !is_null($this->session->get(self::DAV_AUTHENTICATED)) && $this->session->get(self::DAV_AUTHENTICATED) === $username; } /** * Validates a username and password * * This method should return true or false depending on if login * succeeded. * * @param string $username * @param string $password * @return bool * @throws PasswordLoginForbidden */ protected function validateUserPass($username, $password) { if ($this->userSession->isLoggedIn() && $this->isDavAuthenticated($this->userSession->getUser()->getUID()) ) { \OC_Util::setupFS($this->userSession->getUser()->getUID()); $this->session->close(); return true; } else { \OC_Util::setupFS(); //login hooks may need early access to the filesystem try { if ($this->userSession->logClientIn($username, $password, $this->request, $this->throttler)) { \OC_Util::setupFS($this->userSession->getUser()->getUID()); $this->session->set(self::DAV_AUTHENTICATED, $this->userSession->getUser()->getUID()); $this->session->close(); return true; } else { $this->session->close(); return false; } } catch (PasswordLoginForbiddenException $ex) { $this->session->close(); throw new PasswordLoginForbidden(); } } } /** * @param RequestInterface $request * @param ResponseInterface $response * @return array * @throws NotAuthenticated * @throws ServiceUnavailable */ function check(RequestInterface $request, ResponseInterface $response) { try { $result = $this->auth($request, $response); return $result; } catch (NotAuthenticated $e) { throw $e; } catch (Exception $e) { $class = get_class($e); $msg = $e->getMessage(); \OC::$server->getLogger()->logException($e); throw new ServiceUnavailable("$class: $msg"); } } /** * Checks whether a CSRF check is required on the request * * @return bool */ private function requiresCSRFCheck() { // GET requires no check at all if($this->request->getMethod() === 'GET') { return false; } // Official Nextcloud clients require no checks if($this->request->isUserAgent([ IRequest::USER_AGENT_CLIENT_DESKTOP, IRequest::USER_AGENT_CLIENT_ANDROID, IRequest::USER_AGENT_CLIENT_IOS, ])) { return false; } // If not logged-in no check is required if(!$this->userSession->isLoggedIn()) { return false; } // POST always requires a check if($this->request->getMethod() === 'POST') { return true; } // If logged-in AND DAV authenticated no check is required if($this->userSession->isLoggedIn() && $this->isDavAuthenticated($this->userSession->getUser()->getUID())) { return false; } return true; } /** * @param RequestInterface $request * @param ResponseInterface $response * @return array * @throws NotAuthenticated */ private function auth(RequestInterface $request, ResponseInterface $response) { $forcedLogout = false; if(!$this->request->passesCSRFCheck() && $this->requiresCSRFCheck()) { // In case of a fail with POST we need to recheck the credentials if($this->request->getMethod() === 'POST') { $forcedLogout = true; } else { $response->setStatus(401); throw new \Sabre\DAV\Exception\NotAuthenticated('CSRF check not passed.'); } } if($forcedLogout) { $this->userSession->logout(); } else { if($this->twoFactorManager->needsSecondFactor($this->userSession->getUser())) { throw new \Sabre\DAV\Exception\NotAuthenticated('2FA challenge not passed.'); } if (\OC_User::handleApacheAuth() || //Fix for broken webdav clients ($this->userSession->isLoggedIn() && is_null($this->session->get(self::DAV_AUTHENTICATED))) || //Well behaved clients that only send the cookie are allowed ($this->userSession->isLoggedIn() && $this->session->get(self::DAV_AUTHENTICATED) === $this->userSession->getUser()->getUID() && $request->getHeader('Authorization') === null) ) { $user = $this->userSession->getUser()->getUID(); \OC_Util::setupFS($user); $this->currentUser = $user; $this->session->close(); return [true, $this->principalPrefix . $user]; } } if (!$this->userSession->isLoggedIn() && in_array('XMLHttpRequest', explode(',', $request->getHeader('X-Requested-With')))) { // do not re-authenticate over ajax, use dummy auth name to prevent browser popup $response->addHeader('WWW-Authenticate','DummyBasic realm="' . $this->realm . '"'); $response->setStatus(401); throw new \Sabre\DAV\Exception\NotAuthenticated('Cannot authenticate over ajax calls'); } $data = parent::check($request, $response); if($data[0] === true) { $startPos = strrpos($data[1], '/') + 1; $user = $this->userSession->getUser()->getUID(); $data[1] = substr_replace($data[1], $user, $startPos); } return $data; } } Sabre/Principal.php 0000604 00000013646 15247115714 0010247 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Bart Visscher <bartv@thisnet.nl> * @author Jakob Sack <mail@jakobsack.de> * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Thomas Tanghus <thomas@tanghus.net> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OCP\IGroup; use OCP\IGroupManager; use OCP\IUser; use OCP\IUserManager; use Sabre\DAV\Exception; use \Sabre\DAV\PropPatch; use Sabre\DAVACL\PrincipalBackend\BackendInterface; use Sabre\HTTP\URLUtil; class Principal implements BackendInterface { /** @var IUserManager */ private $userManager; /** @var IGroupManager */ private $groupManager; /** @var string */ private $principalPrefix; /** @var bool */ private $hasGroups; /** * @param IUserManager $userManager * @param IGroupManager $groupManager * @param string $principalPrefix */ public function __construct(IUserManager $userManager, IGroupManager $groupManager, $principalPrefix = 'principals/users/') { $this->userManager = $userManager; $this->groupManager = $groupManager; $this->principalPrefix = trim($principalPrefix, '/'); $this->hasGroups = ($principalPrefix === 'principals/users/'); } /** * Returns a list of principals based on a prefix. * * This prefix will often contain something like 'principals'. You are only * expected to return principals that are in this base path. * * You are expected to return at least a 'uri' for every user, you can * return any additional properties if you wish so. Common properties are: * {DAV:}displayname * * @param string $prefixPath * @return string[] */ public function getPrincipalsByPrefix($prefixPath) { $principals = []; if ($prefixPath === $this->principalPrefix) { foreach($this->userManager->search('') as $user) { $principals[] = $this->userToPrincipal($user); } } return $principals; } /** * Returns a specific principal, specified by it's path. * The returned structure should be the exact same as from * getPrincipalsByPrefix. * * @param string $path * @return array */ public function getPrincipalByPath($path) { list($prefix, $name) = URLUtil::splitPath($path); if ($prefix === $this->principalPrefix) { $user = $this->userManager->get($name); if (!is_null($user)) { return $this->userToPrincipal($user); } } return null; } /** * Returns the list of members for a group-principal * * @param string $principal * @return string[] * @throws Exception */ public function getGroupMemberSet($principal) { // TODO: for now the group principal has only one member, the user itself $principal = $this->getPrincipalByPath($principal); if (!$principal) { throw new Exception('Principal not found'); } return [$principal['uri']]; } /** * Returns the list of groups a principal is a member of * * @param string $principal * @param bool $needGroups * @return array * @throws Exception */ public function getGroupMembership($principal, $needGroups = false) { list($prefix, $name) = URLUtil::splitPath($principal); if ($prefix === $this->principalPrefix) { $user = $this->userManager->get($name); if (!$user) { throw new Exception('Principal not found'); } if ($this->hasGroups || $needGroups) { $groups = $this->groupManager->getUserGroups($user); $groups = array_map(function($group) { /** @var IGroup $group */ return 'principals/groups/' . urlencode($group->getGID()); }, $groups); return $groups; } } return []; } /** * Updates the list of group members for a group principal. * * The principals should be passed as a list of uri's. * * @param string $principal * @param string[] $members * @throws Exception */ public function setGroupMemberSet($principal, array $members) { throw new Exception('Setting members of the group is not supported yet'); } /** * @param string $path * @param PropPatch $propPatch * @return int */ function updatePrincipal($path, PropPatch $propPatch) { return 0; } /** * @param string $prefixPath * @param array $searchProperties * @param string $test * @return array */ function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') { return []; } /** * @param string $uri * @param string $principalPrefix * @return string */ function findByUri($uri, $principalPrefix) { if (substr($uri, 0, 7) === 'mailto:') { $email = substr($uri, 7); $users = $this->userManager->getByEmail($email); if (count($users) === 1) { return $this->principalPrefix . '/' . $users[0]->getUID(); } } return ''; } /** * @param IUser $user * @return array */ protected function userToPrincipal($user) { $userId = $user->getUID(); $displayName = $user->getDisplayName(); $principal = [ 'uri' => $this->principalPrefix . '/' . $userId, '{DAV:}displayname' => is_null($displayName) ? $userId : $displayName, ]; $email = $user->getEMailAddress(); if (!empty($email)) { $principal['{http://sabredav.org/ns}email-address'] = $email; } return $principal; } public function getPrincipalPrefix() { return $this->principalPrefix; } } Sabre/FilesPlugin.php 0000604 00000036727 15247115714 0010554 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Björn Schießle <bjoern@schiessle.org> * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Robin McCorkell <robin@mccorkell.me.uk> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OC\Files\View; use OCP\Files\ForbiddenException; use OCP\IPreview; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\IFile; use \Sabre\DAV\PropFind; use \Sabre\DAV\PropPatch; use Sabre\DAV\ServerPlugin; use Sabre\DAV\Tree; use \Sabre\HTTP\RequestInterface; use \Sabre\HTTP\ResponseInterface; use OCP\Files\StorageNotAvailableException; use OCP\IConfig; use OCP\IRequest; use OCA\DAV\Upload\FutureFile; class FilesPlugin extends ServerPlugin { // namespace const NS_OWNCLOUD = 'http://owncloud.org/ns'; const NS_NEXTCLOUD = 'http://nextcloud.org/ns'; const FILEID_PROPERTYNAME = '{http://owncloud.org/ns}id'; const INTERNAL_FILEID_PROPERTYNAME = '{http://owncloud.org/ns}fileid'; const PERMISSIONS_PROPERTYNAME = '{http://owncloud.org/ns}permissions'; const SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-collaboration-services.org/ns}share-permissions'; const DOWNLOADURL_PROPERTYNAME = '{http://owncloud.org/ns}downloadURL'; const SIZE_PROPERTYNAME = '{http://owncloud.org/ns}size'; const GETETAG_PROPERTYNAME = '{DAV:}getetag'; const LASTMODIFIED_PROPERTYNAME = '{DAV:}lastmodified'; const OWNER_ID_PROPERTYNAME = '{http://owncloud.org/ns}owner-id'; const OWNER_DISPLAY_NAME_PROPERTYNAME = '{http://owncloud.org/ns}owner-display-name'; const CHECKSUMS_PROPERTYNAME = '{http://owncloud.org/ns}checksums'; const DATA_FINGERPRINT_PROPERTYNAME = '{http://owncloud.org/ns}data-fingerprint'; const HAS_PREVIEW_PROPERTYNAME = '{http://nextcloud.org/ns}has-preview'; const MOUNT_TYPE_PROPERTYNAME = '{http://nextcloud.org/ns}mount-type'; const IS_ENCRYPTED_PROPERTYNAME = '{http://nextcloud.org/ns}is-encrypted'; /** * Reference to main server object * * @var \Sabre\DAV\Server */ private $server; /** * @var Tree */ private $tree; /** * Whether this is public webdav. * If true, some returned information will be stripped off. * * @var bool */ private $isPublic; /** * @var View */ private $fileView; /** * @var bool */ private $downloadAttachment; /** * @var IConfig */ private $config; /** * @var IRequest */ private $request; /** * @var IPreview */ private $previewManager; /** * @param Tree $tree * @param IConfig $config * @param IRequest $request * @param IPreview $previewManager * @param bool $isPublic * @param bool $downloadAttachment */ public function __construct(Tree $tree, IConfig $config, IRequest $request, IPreview $previewManager, $isPublic = false, $downloadAttachment = true) { $this->tree = $tree; $this->config = $config; $this->request = $request; $this->isPublic = $isPublic; $this->downloadAttachment = $downloadAttachment; $this->previewManager = $previewManager; } /** * This initializes the plugin. * * This function is called by \Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param \Sabre\DAV\Server $server * @return void */ public function initialize(\Sabre\DAV\Server $server) { $server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc'; $server->xml->namespaceMap[self::NS_NEXTCLOUD] = 'nc'; $server->protectedProperties[] = self::FILEID_PROPERTYNAME; $server->protectedProperties[] = self::INTERNAL_FILEID_PROPERTYNAME; $server->protectedProperties[] = self::PERMISSIONS_PROPERTYNAME; $server->protectedProperties[] = self::SHARE_PERMISSIONS_PROPERTYNAME; $server->protectedProperties[] = self::SIZE_PROPERTYNAME; $server->protectedProperties[] = self::DOWNLOADURL_PROPERTYNAME; $server->protectedProperties[] = self::OWNER_ID_PROPERTYNAME; $server->protectedProperties[] = self::OWNER_DISPLAY_NAME_PROPERTYNAME; $server->protectedProperties[] = self::CHECKSUMS_PROPERTYNAME; $server->protectedProperties[] = self::DATA_FINGERPRINT_PROPERTYNAME; $server->protectedProperties[] = self::HAS_PREVIEW_PROPERTYNAME; $server->protectedProperties[] = self::MOUNT_TYPE_PROPERTYNAME; $server->protectedProperties[] = self::IS_ENCRYPTED_PROPERTYNAME; // normally these cannot be changed (RFC4918), but we want them modifiable through PROPPATCH $allowedProperties = ['{DAV:}getetag']; $server->protectedProperties = array_diff($server->protectedProperties, $allowedProperties); $this->server = $server; $this->server->on('propFind', array($this, 'handleGetProperties')); $this->server->on('propPatch', array($this, 'handleUpdateProperties')); $this->server->on('afterBind', array($this, 'sendFileIdHeader')); $this->server->on('afterWriteContent', array($this, 'sendFileIdHeader')); $this->server->on('afterMethod:GET', [$this,'httpGet']); $this->server->on('afterMethod:GET', array($this, 'handleDownloadToken')); $this->server->on('afterResponse', function($request, ResponseInterface $response) { $body = $response->getBody(); if (is_resource($body)) { fclose($body); } }); $this->server->on('beforeMove', [$this, 'checkMove']); $this->server->on('beforeMove', [$this, 'beforeMoveFutureFile']); } /** * Plugin that checks if a move can actually be performed. * * @param string $source source path * @param string $destination destination path * @throws Forbidden * @throws NotFound */ function checkMove($source, $destination) { $sourceNode = $this->tree->getNodeForPath($source); if (!$sourceNode instanceof Node) { return; } list($sourceDir,) = \Sabre\HTTP\URLUtil::splitPath($source); list($destinationDir,) = \Sabre\HTTP\URLUtil::splitPath($destination); if ($sourceDir !== $destinationDir) { $sourceNodeFileInfo = $sourceNode->getFileInfo(); if (is_null($sourceNodeFileInfo)) { throw new NotFound($source . ' does not exist'); } if (!$sourceNodeFileInfo->isDeletable()) { throw new Forbidden($source . " cannot be deleted"); } } } /** * This sets a cookie to be able to recognize the start of the download * the content must not be longer than 32 characters and must only contain * alphanumeric characters * * @param RequestInterface $request * @param ResponseInterface $response */ function handleDownloadToken(RequestInterface $request, ResponseInterface $response) { $queryParams = $request->getQueryParameters(); /** * this sets a cookie to be able to recognize the start of the download * the content must not be longer than 32 characters and must only contain * alphanumeric characters */ if (isset($queryParams['downloadStartSecret'])) { $token = $queryParams['downloadStartSecret']; if (!isset($token[32]) && preg_match('!^[a-zA-Z0-9]+$!', $token) === 1) { // FIXME: use $response->setHeader() instead setcookie('ocDownloadStarted', $token, time() + 20, '/'); } } } /** * Add headers to file download * * @param RequestInterface $request * @param ResponseInterface $response */ function httpGet(RequestInterface $request, ResponseInterface $response) { // Only handle valid files $node = $this->tree->getNodeForPath($request->getPath()); if (!($node instanceof IFile)) return; // adds a 'Content-Disposition: attachment' header in case no disposition // header has been set before if ($this->downloadAttachment && $response->getHeader('Content-Disposition') === null) { $filename = $node->getName(); if ($this->request->isUserAgent( [ \OC\AppFramework\Http\Request::USER_AGENT_IE, \OC\AppFramework\Http\Request::USER_AGENT_ANDROID_MOBILE_CHROME, \OC\AppFramework\Http\Request::USER_AGENT_FREEBOX, ])) { $response->addHeader('Content-Disposition', 'attachment; filename="' . rawurlencode($filename) . '"'); } else { $response->addHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . rawurlencode($filename) . '; filename="' . rawurlencode($filename) . '"'); } } if ($node instanceof \OCA\DAV\Connector\Sabre\File) { //Add OC-Checksum header /** @var $node File */ $checksum = $node->getChecksum(); if ($checksum !== null && $checksum !== '') { $response->addHeader('OC-Checksum', $checksum); } } } /** * Adds all ownCloud-specific properties * * @param PropFind $propFind * @param \Sabre\DAV\INode $node * @return void */ public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) { $httpRequest = $this->server->httpRequest; if ($node instanceof \OCA\DAV\Connector\Sabre\Node) { /** * This was disabled, because it made dir listing throw an exception, * so users were unable to navigate into folders where one subitem * is blocked by the files_accesscontrol app, see: * https://github.com/nextcloud/files_accesscontrol/issues/65 if (!$node->getFileInfo()->isReadable()) { // avoid detecting files through this means throw new NotFound(); } */ $propFind->handle(self::FILEID_PROPERTYNAME, function() use ($node) { return $node->getFileId(); }); $propFind->handle(self::INTERNAL_FILEID_PROPERTYNAME, function() use ($node) { return $node->getInternalFileId(); }); $propFind->handle(self::PERMISSIONS_PROPERTYNAME, function() use ($node) { $perms = $node->getDavPermissions(); if ($this->isPublic) { // remove mount information $perms = str_replace(['S', 'M'], '', $perms); } return $perms; }); $propFind->handle(self::SHARE_PERMISSIONS_PROPERTYNAME, function() use ($node, $httpRequest) { return $node->getSharePermissions( $httpRequest->getRawServerValue('PHP_AUTH_USER') ); }); $propFind->handle(self::GETETAG_PROPERTYNAME, function() use ($node) { return $node->getETag(); }); $propFind->handle(self::OWNER_ID_PROPERTYNAME, function() use ($node) { $owner = $node->getOwner(); if (!$owner) { return null; } else { return $owner->getUID(); } }); $propFind->handle(self::OWNER_DISPLAY_NAME_PROPERTYNAME, function() use ($node) { $owner = $node->getOwner(); if (!$owner) { return null; } else { return $owner->getDisplayName(); } }); $propFind->handle(self::IS_ENCRYPTED_PROPERTYNAME, function() use ($node) { $result = $node->getFileInfo()->isEncrypted() ? '1' : '0'; return $result; }); $propFind->handle(self::HAS_PREVIEW_PROPERTYNAME, function () use ($node) { return json_encode($this->previewManager->isAvailable($node->getFileInfo())); }); $propFind->handle(self::SIZE_PROPERTYNAME, function() use ($node) { return $node->getSize(); }); $propFind->handle(self::MOUNT_TYPE_PROPERTYNAME, function () use ($node) { return $node->getFileInfo()->getMountPoint()->getMountType(); }); } if ($node instanceof \OCA\DAV\Connector\Sabre\Node) { $propFind->handle(self::DATA_FINGERPRINT_PROPERTYNAME, function() use ($node) { return $this->config->getSystemValue('data-fingerprint', ''); }); } if ($node instanceof \OCA\DAV\Connector\Sabre\File) { $propFind->handle(self::DOWNLOADURL_PROPERTYNAME, function() use ($node) { /** @var $node \OCA\DAV\Connector\Sabre\File */ try { $directDownloadUrl = $node->getDirectDownload(); if (isset($directDownloadUrl['url'])) { return $directDownloadUrl['url']; } } catch (StorageNotAvailableException $e) { return false; } catch (ForbiddenException $e) { return false; } return false; }); $propFind->handle(self::CHECKSUMS_PROPERTYNAME, function() use ($node) { $checksum = $node->getChecksum(); if ($checksum === NULL || $checksum === '') { return null; } return new ChecksumList($checksum); }); } if ($node instanceof \OCA\DAV\Connector\Sabre\Directory) { $propFind->handle(self::SIZE_PROPERTYNAME, function() use ($node) { return $node->getSize(); }); } } /** * Update ownCloud-specific properties * * @param string $path * @param PropPatch $propPatch * * @return void */ public function handleUpdateProperties($path, PropPatch $propPatch) { $node = $this->tree->getNodeForPath($path); if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) { return; } $propPatch->handle(self::LASTMODIFIED_PROPERTYNAME, function($time) use ($node) { if (empty($time)) { return false; } $node->touch($time); return true; }); $propPatch->handle(self::GETETAG_PROPERTYNAME, function($etag) use ($node) { if (empty($etag)) { return false; } if ($node->setEtag($etag) !== -1) { return true; } return false; }); } /** * @param string $filePath * @param \Sabre\DAV\INode $node * @throws \Sabre\DAV\Exception\BadRequest */ public function sendFileIdHeader($filePath, \Sabre\DAV\INode $node = null) { // chunked upload handling if (isset($_SERVER['HTTP_OC_CHUNKED'])) { list($path, $name) = \Sabre\HTTP\URLUtil::splitPath($filePath); $info = \OC_FileChunking::decodeName($name); if (!empty($info)) { $filePath = $path . '/' . $info['name']; } } // we get the node for the given $filePath here because in case of afterCreateFile $node is the parent folder if (!$this->server->tree->nodeExists($filePath)) { return; } $node = $this->server->tree->getNodeForPath($filePath); if ($node instanceof \OCA\DAV\Connector\Sabre\Node) { $fileId = $node->getFileId(); if (!is_null($fileId)) { $this->server->httpResponse->setHeader('OC-FileId', $fileId); } } } /** * Move handler for future file. * * This overrides the default move behavior to prevent Sabre * to delete the target file before moving. Because deleting would * lose the file id and metadata. * * @param string $path source path * @param string $destination destination path * @return bool|void false to stop handling, void to skip this handler */ public function beforeMoveFutureFile($path, $destination) { $sourceNode = $this->tree->getNodeForPath($path); if (!$sourceNode instanceof FutureFile) { // skip handling as the source is not a chunked FutureFile return; } if (!$this->tree->nodeExists($destination)) { // skip and let the default handler do its work return; } // do a move manually, skipping Sabre's default "delete" for existing nodes $this->tree->move($path, $destination); // trigger all default events (copied from CorePlugin::move) $this->server->emit('afterMove', [$path, $destination]); $this->server->emit('afterUnbind', [$path]); $this->server->emit('afterBind', [$destination]); $response = $this->server->httpResponse; $response->setHeader('Content-Length', '0'); $response->setStatus(204); return false; } } Sabre/Server.php 0000604 00000002457 15247115714 0007572 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Morris Jobke <hey@morrisjobke.de> * @author scolebrook <scolebrook@mac.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; /** * Class \OCA\DAV\Connector\Sabre\Server * * This class overrides some methods from @see \Sabre\DAV\Server. * * @see \Sabre\DAV\Server */ class Server extends \Sabre\DAV\Server { /** * @see \Sabre\DAV\Server */ public function __construct($treeOrNode = null) { parent::__construct($treeOrNode); self::$exposeVersion = false; $this->enablePropfindDepthInfinity = true; } } Sabre/ChecksumList.php 0000604 00000003754 15247115714 0010723 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Roeland Jago Douma <roeland@famdouma.nl> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use Sabre\Xml\XmlSerializable; use Sabre\Xml\Writer; /** * Checksumlist property * * This property contains multiple "checksum" elements, each containing a * checksum name. */ class ChecksumList implements XmlSerializable { const NS_OWNCLOUD = 'http://owncloud.org/ns'; /** @var string[] of TYPE:CHECKSUM */ private $checksums; /** * @param string $checksum */ public function __construct($checksum) { $this->checksums = explode(',', $checksum); } /** * The xmlSerialize metod is called during xml writing. * * Use the $writer argument to write its own xml serialization. * * An important note: do _not_ create a parent element. Any element * implementing XmlSerializble should only ever write what's considered * its 'inner xml'. * * The parent of the current element is responsible for writing a * containing element. * * This allows serializers to be re-used for different element names. * * If you are opening new elements, you must also close them again. * * @param Writer $writer * @return void */ function xmlSerialize(Writer $writer) { foreach ($this->checksums as $checksum) { $writer->writeElement('{' . self::NS_OWNCLOUD . '}checksum', $checksum); } } } Sabre/TagsPlugin.php 0000604 00000016662 15247115714 0010404 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; /** * ownCloud * * @author Vincent Petry * @copyright 2014 Vincent Petry <pvince81@owncloud.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE * License as published by the Free Software Foundation; either * version 3 of the License, or any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU AFFERO GENERAL PUBLIC LICENSE for more details. * * You should have received a copy of the GNU Affero General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. * */ use \Sabre\DAV\PropFind; use \Sabre\DAV\PropPatch; class TagsPlugin extends \Sabre\DAV\ServerPlugin { // namespace const NS_OWNCLOUD = 'http://owncloud.org/ns'; const TAGS_PROPERTYNAME = '{http://owncloud.org/ns}tags'; const FAVORITE_PROPERTYNAME = '{http://owncloud.org/ns}favorite'; const TAG_FAVORITE = '_$!<Favorite>!$_'; /** * Reference to main server object * * @var \Sabre\DAV\Server */ private $server; /** * @var \OCP\ITagManager */ private $tagManager; /** * @var \OCP\ITags */ private $tagger; /** * Array of file id to tags array * The null value means the cache wasn't initialized. * * @var array */ private $cachedTags; /** * @var \Sabre\DAV\Tree */ private $tree; /** * @param \Sabre\DAV\Tree $tree tree * @param \OCP\ITagManager $tagManager tag manager */ public function __construct(\Sabre\DAV\Tree $tree, \OCP\ITagManager $tagManager) { $this->tree = $tree; $this->tagManager = $tagManager; $this->tagger = null; $this->cachedTags = array(); } /** * This initializes the plugin. * * This function is called by \Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param \Sabre\DAV\Server $server * @return void */ public function initialize(\Sabre\DAV\Server $server) { $server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc'; $server->xml->elementMap[self::TAGS_PROPERTYNAME] = 'OCA\\DAV\\Connector\\Sabre\\TagList'; $this->server = $server; $this->server->on('propFind', array($this, 'handleGetProperties')); $this->server->on('propPatch', array($this, 'handleUpdateProperties')); } /** * Returns the tagger * * @return \OCP\ITags tagger */ private function getTagger() { if (!$this->tagger) { $this->tagger = $this->tagManager->load('files'); } return $this->tagger; } /** * Returns tags and favorites. * * @param integer $fileId file id * @return array list($tags, $favorite) with $tags as tag array * and $favorite is a boolean whether the file was favorited */ private function getTagsAndFav($fileId) { $isFav = false; $tags = $this->getTags($fileId); if ($tags) { $favPos = array_search(self::TAG_FAVORITE, $tags); if ($favPos !== false) { $isFav = true; unset($tags[$favPos]); } } return array($tags, $isFav); } /** * Returns tags for the given file id * * @param integer $fileId file id * @return array list of tags for that file */ private function getTags($fileId) { if (isset($this->cachedTags[$fileId])) { return $this->cachedTags[$fileId]; } else { $tags = $this->getTagger()->getTagsForObjects(array($fileId)); if ($tags !== false) { if (empty($tags)) { return array(); } return current($tags); } } return null; } /** * Updates the tags of the given file id * * @param int $fileId * @param array $tags array of tag strings */ private function updateTags($fileId, $tags) { $tagger = $this->getTagger(); $currentTags = $this->getTags($fileId); $newTags = array_diff($tags, $currentTags); foreach ($newTags as $tag) { if ($tag === self::TAG_FAVORITE) { continue; } $tagger->tagAs($fileId, $tag); } $deletedTags = array_diff($currentTags, $tags); foreach ($deletedTags as $tag) { if ($tag === self::TAG_FAVORITE) { continue; } $tagger->unTag($fileId, $tag); } } /** * Adds tags and favorites properties to the response, * if requested. * * @param PropFind $propFind * @param \Sabre\DAV\INode $node * @return void */ public function handleGetProperties( PropFind $propFind, \Sabre\DAV\INode $node ) { if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) { return; } // need prefetch ? if ($node instanceof \OCA\DAV\Connector\Sabre\Directory && $propFind->getDepth() !== 0 && (!is_null($propFind->getStatus(self::TAGS_PROPERTYNAME)) || !is_null($propFind->getStatus(self::FAVORITE_PROPERTYNAME)) )) { // note: pre-fetching only supported for depth <= 1 $folderContent = $node->getChildren(); $fileIds[] = (int)$node->getId(); foreach ($folderContent as $info) { $fileIds[] = (int)$info->getId(); } $tags = $this->getTagger()->getTagsForObjects($fileIds); if ($tags === false) { // the tags API returns false on error... $tags = array(); } $this->cachedTags = $this->cachedTags + $tags; $emptyFileIds = array_diff($fileIds, array_keys($tags)); // also cache the ones that were not found foreach ($emptyFileIds as $fileId) { $this->cachedTags[$fileId] = []; } } $tags = null; $isFav = null; $propFind->handle(self::TAGS_PROPERTYNAME, function() use ($tags, &$isFav, $node) { list($tags, $isFav) = $this->getTagsAndFav($node->getId()); return new TagList($tags); }); $propFind->handle(self::FAVORITE_PROPERTYNAME, function() use ($isFav, $node) { if (is_null($isFav)) { list(, $isFav) = $this->getTagsAndFav($node->getId()); } if ($isFav) { return 1; } else { return 0; } }); } /** * Updates tags and favorites properties, if applicable. * * @param string $path * @param PropPatch $propPatch * * @return void */ public function handleUpdateProperties($path, PropPatch $propPatch) { $node = $this->tree->getNodeForPath($path); if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) { return; } $propPatch->handle(self::TAGS_PROPERTYNAME, function($tagList) use ($node) { $this->updateTags($node->getId(), $tagList->getTags()); return true; }); $propPatch->handle(self::FAVORITE_PROPERTYNAME, function($favState) use ($node) { if ((int)$favState === 1 || $favState === 'true') { $this->getTagger()->tagAs($node->getId(), self::TAG_FAVORITE); } else { $this->getTagger()->unTag($node->getId(), self::TAG_FAVORITE); } if (is_null($favState)) { // confirm deletion return 204; } return 200; }); } } Sabre/QuotaPlugin.php 0000604 00000010427 15247115714 0010570 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Felix Moeller <mail@felixmoeller.de> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author scambra <sergio@entrecables.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OCP\Files\FileInfo; use OCP\Files\StorageNotAvailableException; use Sabre\DAV\Exception\InsufficientStorage; use Sabre\DAV\Exception\ServiceUnavailable; use Sabre\HTTP\URLUtil; /** * This plugin check user quota and deny creating files when they exceeds the quota. * * @author Sergio Cambra * @copyright Copyright (C) 2012 entreCables S.L. All rights reserved. * @license http://code.google.com/p/sabredav/wiki/License Modified BSD License */ class QuotaPlugin extends \Sabre\DAV\ServerPlugin { /** * @var \OC\Files\View */ private $view; /** * Reference to main server object * * @var \Sabre\DAV\Server */ private $server; /** * @param \OC\Files\View $view */ public function __construct($view) { $this->view = $view; } /** * This initializes the plugin. * * This function is called by \Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the requires event subscriptions. * * @param \Sabre\DAV\Server $server * @return void */ public function initialize(\Sabre\DAV\Server $server) { $this->server = $server; $server->on('beforeWriteContent', array($this, 'checkQuota'), 10); $server->on('beforeCreateFile', array($this, 'checkQuota'), 10); } /** * This method is called before any HTTP method and validates there is enough free space to store the file * * @param string $uri * @throws InsufficientStorage * @return bool */ public function checkQuota($uri) { $length = $this->getLength(); if ($length) { if (substr($uri, 0, 1) !== '/') { $uri = '/' . $uri; } list($parentUri, $newName) = URLUtil::splitPath($uri); if(is_null($parentUri)) { $parentUri = ''; } $req = $this->server->httpRequest; if ($req->getHeader('OC-Chunked')) { $info = \OC_FileChunking::decodeName($newName); $chunkHandler = $this->getFileChunking($info); // subtract the already uploaded size to see whether // there is still enough space for the remaining chunks $length -= $chunkHandler->getCurrentSize(); // use target file name for free space check in case of shared files $uri = rtrim($parentUri, '/') . '/' . $info['name']; } $freeSpace = $this->getFreeSpace($uri); if ($freeSpace !== FileInfo::SPACE_UNKNOWN && $freeSpace !== FileInfo::SPACE_UNLIMITED && $length > $freeSpace) { if (isset($chunkHandler)) { $chunkHandler->cleanup(); } throw new InsufficientStorage(); } } return true; } public function getFileChunking($info) { // FIXME: need a factory for better mocking support return new \OC_FileChunking($info); } public function getLength() { $req = $this->server->httpRequest; $length = $req->getHeader('X-Expected-Entity-Length'); if (!is_numeric($length)) { $length = $req->getHeader('Content-Length'); $length = is_numeric($length) ? $length : null; } $ocLength = $req->getHeader('OC-Total-Length'); if (is_numeric($length) && is_numeric($ocLength)) { return max($length, $ocLength); } return $length; } /** * @param string $uri * @return mixed * @throws ServiceUnavailable */ public function getFreeSpace($uri) { try { $freeSpace = $this->view->free_space(ltrim($uri, '/')); return $freeSpace; } catch (StorageNotAvailableException $e) { throw new ServiceUnavailable($e->getMessage()); } } } Sabre/ExceptionLoggerPlugin.php 0000604 00000006054 15247115714 0012576 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Morris Jobke <hey@morrisjobke.de> * @author Pierre Jochem <pierrejochem@msn.com> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OCP\ILogger; use Sabre\DAV\Exception; use Sabre\HTTP\Response; class ExceptionLoggerPlugin extends \Sabre\DAV\ServerPlugin { protected $nonFatalExceptions = [ 'Sabre\DAV\Exception\NotAuthenticated' => true, // If tokenauth can throw this exception (which is basically as // NotAuthenticated. So not fatal. 'OCA\DAV\Connector\Sabre\Exception\PasswordLoginForbidden' => true, // the sync client uses this to find out whether files exist, // so it is not always an error, log it as debug 'Sabre\DAV\Exception\NotFound' => true, // this one mostly happens when the same file is uploaded at // exactly the same time from two clients, only one client // wins, the second one gets "Precondition failed" 'Sabre\DAV\Exception\PreconditionFailed' => true, // forbidden can be expected when trying to upload to // read-only folders for example 'Sabre\DAV\Exception\Forbidden' => true, // Happens when an external storage or federated share is temporarily // not available 'Sabre\DAV\Exception\StorageNotAvailableException' => true, ]; /** @var string */ private $appName; /** @var ILogger */ private $logger; /** * @param string $loggerAppName app name to use when logging * @param ILogger $logger */ public function __construct($loggerAppName, $logger) { $this->appName = $loggerAppName; $this->logger = $logger; } /** * This initializes the plugin. * * This function is called by \Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param \Sabre\DAV\Server $server * @return void */ public function initialize(\Sabre\DAV\Server $server) { $server->on('exception', array($this, 'logException'), 10); } /** * Log exception * */ public function logException(\Exception $ex) { $exceptionClass = get_class($ex); $level = \OCP\Util::FATAL; if (isset($this->nonFatalExceptions[$exceptionClass])) { $level = \OCP\Util::DEBUG; } $this->logger->logException($ex, [ 'app' => $this->appName, 'level' => $level, ]); } } Sabre/ShareTypeList.php 0000604 00000004123 15247115714 0011054 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use Sabre\Xml\Element; use Sabre\Xml\Reader; use Sabre\Xml\Writer; /** * ShareTypeList property * * This property contains multiple "share-type" elements, each containing a share type. */ class ShareTypeList implements Element { const NS_OWNCLOUD = 'http://owncloud.org/ns'; /** * Share types * * @var int[] */ private $shareTypes; /** * @param int[] $shareTypes */ public function __construct($shareTypes) { $this->shareTypes = $shareTypes; } /** * Returns the share types * * @return int[] */ public function getShareTypes() { return $this->shareTypes; } /** * The deserialize method is called during xml parsing. * * @param Reader $reader * @return mixed */ static function xmlDeserialize(Reader $reader) { $shareTypes = []; $tree = $reader->parseInnerTree(); if ($tree === null) { return null; } foreach ($tree as $elem) { if ($elem['name'] === '{' . self::NS_OWNCLOUD . '}share-type') { $shareTypes[] = (int)$elem['value']; } } return new self($shareTypes); } /** * The xmlSerialize metod is called during xml writing. * * @param Writer $writer * @return void */ function xmlSerialize(Writer $writer) { foreach ($this->shareTypes as $shareType) { $writer->writeElement('{' . self::NS_OWNCLOUD . '}share-type', $shareType); } } } Sabre/BearerAuth.php 0000604 00000005151 15247115714 0010340 0 ustar 00 <?php /** * @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch> * * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ namespace OCA\DAV\Connector\Sabre; use OCP\IRequest; use OCP\ISession; use OCP\IUserSession; use Sabre\DAV\Auth\Backend\AbstractBearer; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; class BearerAuth extends AbstractBearer { /** @var IUserSession */ private $userSession; /** @var ISession */ private $session; /** @var IRequest */ private $request; /** @var string */ private $principalPrefix; /** * @param IUserSession $userSession * @param ISession $session * @param string $principalPrefix * @param IRequest $request */ public function __construct(IUserSession $userSession, ISession $session, IRequest $request, $principalPrefix = 'principals/users/') { $this->userSession = $userSession; $this->session = $session; $this->request = $request; $this->principalPrefix = $principalPrefix; // setup realm $defaults = new \OCP\Defaults(); $this->realm = $defaults->getName(); } private function setupUserFs($userId) { \OC_Util::setupFS($userId); $this->session->close(); return $this->principalPrefix . $userId; } /** * {@inheritdoc} */ public function validateBearerToken($bearerToken) { \OC_Util::setupFS(); if(!$this->userSession->isLoggedIn()) { $this->userSession->tryTokenLogin($this->request); } if($this->userSession->isLoggedIn()) { return $this->setupUserFs($this->userSession->getUser()->getUID()); } return false; } /** * \Sabre\DAV\Auth\Backend\AbstractBearer::challenge sets an WWW-Authenticate * header which some DAV clients can't handle. Thus we override this function * and make it simply return a 401. * * @param RequestInterface $request * @param ResponseInterface $response */ public function challenge(RequestInterface $request, ResponseInterface $response) { $response->setStatus(401); } } Sabre/TagList.php 0000604 00000006162 15247115714 0007670 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Morris Jobke <hey@morrisjobke.de> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use Sabre\Xml\Element; use Sabre\Xml\Reader; use Sabre\Xml\Writer; /** * TagList property * * This property contains multiple "tag" elements, each containing a tag name. */ class TagList implements Element { const NS_OWNCLOUD = 'http://owncloud.org/ns'; /** * tags * * @var array */ private $tags; /** * @param array $tags */ public function __construct(array $tags) { $this->tags = $tags; } /** * Returns the tags * * @return array */ public function getTags() { return $this->tags; } /** * The deserialize method is called during xml parsing. * * This method is called statictly, this is because in theory this method * may be used as a type of constructor, or factory method. * * Often you want to return an instance of the current class, but you are * free to return other data as well. * * You are responsible for advancing the reader to the next element. Not * doing anything will result in a never-ending loop. * * If you just want to skip parsing for this element altogether, you can * just call $reader->next(); * * $reader->parseInnerTree() will parse the entire sub-tree, and advance to * the next element. * * @param Reader $reader * @return mixed */ static function xmlDeserialize(Reader $reader) { $tags = []; $tree = $reader->parseInnerTree(); if ($tree === null) { return null; } foreach ($tree as $elem) { if ($elem['name'] === '{' . self::NS_OWNCLOUD . '}tag') { $tags[] = $elem['value']; } } return new self($tags); } /** * The xmlSerialize metod is called during xml writing. * * Use the $writer argument to write its own xml serialization. * * An important note: do _not_ create a parent element. Any element * implementing XmlSerializble should only ever write what's considered * its 'inner xml'. * * The parent of the current element is responsible for writing a * containing element. * * This allows serializers to be re-used for different element names. * * If you are opening new elements, you must also close them again. * * @param Writer $writer * @return void */ function xmlSerialize(Writer $writer) { foreach ($this->tags as $tag) { $writer->writeElement('{' . self::NS_OWNCLOUD . '}tag', $tag); } } } Sabre/CommentPropertiesPlugin.php 0000604 00000011115 15247115714 0013151 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OCP\Comments\ICommentsManager; use OCP\IUserSession; use Sabre\DAV\PropFind; use Sabre\DAV\ServerPlugin; class CommentPropertiesPlugin extends ServerPlugin { const PROPERTY_NAME_HREF = '{http://owncloud.org/ns}comments-href'; const PROPERTY_NAME_COUNT = '{http://owncloud.org/ns}comments-count'; const PROPERTY_NAME_UNREAD = '{http://owncloud.org/ns}comments-unread'; /** @var \Sabre\DAV\Server */ protected $server; /** @var ICommentsManager */ private $commentsManager; /** @var IUserSession */ private $userSession; private $cachedUnreadCount = []; private $cachedFolders = []; public function __construct(ICommentsManager $commentsManager, IUserSession $userSession) { $this->commentsManager = $commentsManager; $this->userSession = $userSession; } /** * This initializes the plugin. * * This function is called by Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param \Sabre\DAV\Server $server * @return void */ function initialize(\Sabre\DAV\Server $server) { $this->server = $server; $this->server->on('propFind', array($this, 'handleGetProperties')); } /** * Adds tags and favorites properties to the response, * if requested. * * @param PropFind $propFind * @param \Sabre\DAV\INode $node * @return void */ public function handleGetProperties( PropFind $propFind, \Sabre\DAV\INode $node ) { if (!($node instanceof File) && !($node instanceof Directory)) { return; } // need prefetch ? if ($node instanceof \OCA\DAV\Connector\Sabre\Directory && $propFind->getDepth() !== 0 && !is_null($propFind->getStatus(self::PROPERTY_NAME_UNREAD)) ) { $unreadCounts = $this->commentsManager->getNumberOfUnreadCommentsForFolder($node->getId(), $this->userSession->getUser()); $this->cachedFolders[] = $node->getPath(); foreach ($unreadCounts as $id => $count) { $this->cachedUnreadCount[$id] = $count; } } $propFind->handle(self::PROPERTY_NAME_COUNT, function() use ($node) { return $this->commentsManager->getNumberOfCommentsForObject('files', strval($node->getId())); }); $propFind->handle(self::PROPERTY_NAME_HREF, function() use ($node) { return $this->getCommentsLink($node); }); $propFind->handle(self::PROPERTY_NAME_UNREAD, function() use ($node) { if (isset($this->cachedUnreadCount[$node->getId()])) { return $this->cachedUnreadCount[$node->getId()]; } else { list($parentPath,) = \Sabre\Uri\split($node->getPath()); if ($parentPath === '') { $parentPath = '/'; } // if we already cached the folder this file is in we know there are no shares for this file if (array_search($parentPath, $this->cachedFolders) === false) { return $this->getUnreadCount($node); } else { return 0; } } }); } /** * returns a reference to the comments node * * @param Node $node * @return mixed|string */ public function getCommentsLink(Node $node) { $href = $this->server->getBaseUri(); $entryPoint = strpos($href, '/remote.php/'); if($entryPoint === false) { // in case we end up somewhere else, unexpectedly. return null; } $commentsPart = 'dav/comments/files/' . rawurldecode($node->getId()); $href = substr_replace($href, $commentsPart, $entryPoint + strlen('/remote.php/')); return $href; } /** * returns the number of unread comments for the currently logged in user * on the given file or directory node * * @param Node $node * @return Int|null */ public function getUnreadCount(Node $node) { $user = $this->userSession->getUser(); if(is_null($user)) { return null; } $lastRead = $this->commentsManager->getReadMark('files', strval($node->getId()), $user); return $this->commentsManager->getNumberOfCommentsForObject('files', strval($node->getId()), $lastRead); } } Sabre/ServerFactory.php 0000604 00000015043 15247115714 0011115 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OC\Files\Node\Folder; use OCA\DAV\Files\BrowserErrorPagePlugin; use OCP\Files\Mount\IMountManager; use OCP\IConfig; use OCP\IDBConnection; use OCP\ILogger; use OCP\IPreview; use OCP\IRequest; use OCP\ITagManager; use OCP\IUserSession; use Sabre\DAV\Auth\Backend\BackendInterface; use Sabre\DAV\Auth\Plugin; class ServerFactory { /** @var IConfig */ private $config; /** @var ILogger */ private $logger; /** @var IDBConnection */ private $databaseConnection; /** @var IUserSession */ private $userSession; /** @var IMountManager */ private $mountManager; /** @var ITagManager */ private $tagManager; /** @var IRequest */ private $request; /** @var IPreview */ private $previewManager; /** * @param IConfig $config * @param ILogger $logger * @param IDBConnection $databaseConnection * @param IUserSession $userSession * @param IMountManager $mountManager * @param ITagManager $tagManager * @param IRequest $request * @param IPreview $previewManager */ public function __construct( IConfig $config, ILogger $logger, IDBConnection $databaseConnection, IUserSession $userSession, IMountManager $mountManager, ITagManager $tagManager, IRequest $request, IPreview $previewManager ) { $this->config = $config; $this->logger = $logger; $this->databaseConnection = $databaseConnection; $this->userSession = $userSession; $this->mountManager = $mountManager; $this->tagManager = $tagManager; $this->request = $request; $this->previewManager = $previewManager; } /** * @param string $baseUri * @param string $requestUri * @param Plugin $authPlugin * @param callable $viewCallBack callback that should return the view for the dav endpoint * @return Server */ public function createServer($baseUri, $requestUri, Plugin $authPlugin, callable $viewCallBack) { // Fire up server $objectTree = new \OCA\DAV\Connector\Sabre\ObjectTree(); $server = new \OCA\DAV\Connector\Sabre\Server($objectTree); // Set URL explicitly due to reverse-proxy situations $server->httpRequest->setUrl($requestUri); $server->setBaseUri($baseUri); // Load plugins $server->addPlugin(new \OCA\DAV\Connector\Sabre\MaintenancePlugin($this->config)); $server->addPlugin(new \OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin($this->config)); $server->addPlugin($authPlugin); // FIXME: The following line is a workaround for legacy components relying on being able to send a GET to / $server->addPlugin(new \OCA\DAV\Connector\Sabre\DummyGetResponsePlugin()); $server->addPlugin(new \OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin('webdav', $this->logger)); $server->addPlugin(new \OCA\DAV\Connector\Sabre\LockPlugin()); // Some WebDAV clients do require Class 2 WebDAV support (locking), since // we do not provide locking we emulate it using a fake locking plugin. if($this->request->isUserAgent([ '/WebDAVFS/', '/Microsoft Office OneNote 2013/', '/Microsoft-WebDAV-MiniRedir/', ])) { $server->addPlugin(new \OCA\DAV\Connector\Sabre\FakeLockerPlugin()); } if (BrowserErrorPagePlugin::isBrowserRequest($this->request)) { $server->addPlugin(new BrowserErrorPagePlugin()); } // wait with registering these until auth is handled and the filesystem is setup $server->on('beforeMethod', function () use ($server, $objectTree, $viewCallBack) { // ensure the skeleton is copied $userFolder = \OC::$server->getUserFolder(); /** @var \OC\Files\View $view */ $view = $viewCallBack($server); if ($userFolder instanceof Folder && $userFolder->getPath() === $view->getRoot()) { $rootInfo = $userFolder; } else { $rootInfo = $view->getFileInfo(''); } // Create Nextcloud Dir if ($rootInfo->getType() === 'dir') { $root = new \OCA\DAV\Connector\Sabre\Directory($view, $rootInfo, $objectTree); } else { $root = new \OCA\DAV\Connector\Sabre\File($view, $rootInfo); } $objectTree->init($root, $view, $this->mountManager); $server->addPlugin( new \OCA\DAV\Connector\Sabre\FilesPlugin( $objectTree, $this->config, $this->request, $this->previewManager, false, !$this->config->getSystemValue('debug', false) ) ); $server->addPlugin(new \OCA\DAV\Connector\Sabre\QuotaPlugin($view)); if($this->userSession->isLoggedIn()) { $server->addPlugin(new \OCA\DAV\Connector\Sabre\TagsPlugin($objectTree, $this->tagManager)); $server->addPlugin(new \OCA\DAV\Connector\Sabre\SharesPlugin( $objectTree, $this->userSession, $userFolder, \OC::$server->getShareManager() )); $server->addPlugin(new \OCA\DAV\Connector\Sabre\CommentPropertiesPlugin(\OC::$server->getCommentsManager(), $this->userSession)); $server->addPlugin(new \OCA\DAV\Connector\Sabre\FilesReportPlugin( $objectTree, $view, \OC::$server->getSystemTagManager(), \OC::$server->getSystemTagObjectMapper(), \OC::$server->getTagManager(), $this->userSession, \OC::$server->getGroupManager(), $userFolder )); // custom properties plugin must be the last one $server->addPlugin( new \Sabre\DAV\PropertyStorage\Plugin( new \OCA\DAV\Connector\Sabre\CustomPropertiesBackend( $objectTree, $this->databaseConnection, $this->userSession->getUser() ) ) ); } $server->addPlugin(new \OCA\DAV\Connector\Sabre\CopyEtagHeaderPlugin()); }, 30); // priority 30: after auth (10) and acl(20), before lock(50) and handling the request return $server; } } Sabre/MaintenancePlugin.php 0000604 00000004515 15247115714 0011722 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Bart Visscher <bartv@thisnet.nl> * @author Joas Schilling <coding@schilljs.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OCP\IConfig; use Sabre\DAV\Exception\ServiceUnavailable; use Sabre\DAV\ServerPlugin; class MaintenancePlugin extends ServerPlugin { /** @var IConfig */ private $config; /** * Reference to main server object * * @var Server */ private $server; /** * @param IConfig $config */ public function __construct(IConfig $config = null) { $this->config = $config; if (is_null($config)) { $this->config = \OC::$server->getConfig(); } } /** * This initializes the plugin. * * This function is called by \Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param \Sabre\DAV\Server $server * @return void */ public function initialize(\Sabre\DAV\Server $server) { $this->server = $server; $this->server->on('beforeMethod', array($this, 'checkMaintenanceMode'), 1); } /** * This method is called before any HTTP method and returns http status code 503 * in case the system is in maintenance mode. * * @throws ServiceUnavailable * @return bool */ public function checkMaintenanceMode() { if ($this->config->getSystemValue('maintenance', false)) { throw new ServiceUnavailable('System in maintenance mode.'); } if (\OC::checkUpgrade(false)) { throw new ServiceUnavailable('Upgrade needed'); } return true; } } Sabre/SharesPlugin.php 0000604 00000012627 15247115714 0010730 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use \Sabre\DAV\PropFind; use OCP\IUserSession; use OCP\Share\IShare; /** * Sabre Plugin to provide share-related properties */ class SharesPlugin extends \Sabre\DAV\ServerPlugin { const NS_OWNCLOUD = 'http://owncloud.org/ns'; const SHARETYPES_PROPERTYNAME = '{http://owncloud.org/ns}share-types'; /** * Reference to main server object * * @var \Sabre\DAV\Server */ private $server; /** * @var \OCP\Share\IManager */ private $shareManager; /** * @var \Sabre\DAV\Tree */ private $tree; /** * @var string */ private $userId; /** * @var \OCP\Files\Folder */ private $userFolder; /** * @var IShare[] */ private $cachedShareTypes; private $cachedFolders = []; /** * @param \Sabre\DAV\Tree $tree tree * @param IUserSession $userSession user session * @param \OCP\Files\Folder $userFolder user home folder * @param \OCP\Share\IManager $shareManager share manager */ public function __construct( \Sabre\DAV\Tree $tree, IUserSession $userSession, \OCP\Files\Folder $userFolder, \OCP\Share\IManager $shareManager ) { $this->tree = $tree; $this->shareManager = $shareManager; $this->userFolder = $userFolder; $this->userId = $userSession->getUser()->getUID(); $this->cachedShareTypes = []; } /** * This initializes the plugin. * * This function is called by \Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param \Sabre\DAV\Server $server */ public function initialize(\Sabre\DAV\Server $server) { $server->xml->namespacesMap[self::NS_OWNCLOUD] = 'oc'; $server->xml->elementMap[self::SHARETYPES_PROPERTYNAME] = 'OCA\\DAV\\Connector\\Sabre\\ShareTypeList'; $server->protectedProperties[] = self::SHARETYPES_PROPERTYNAME; $this->server = $server; $this->server->on('propFind', array($this, 'handleGetProperties')); } /** * Return a list of share types for outgoing shares * * @param \OCP\Files\Node $node file node * * @return int[] array of share types */ private function getShareTypes(\OCP\Files\Node $node) { $shareTypes = []; $requestedShareTypes = [ \OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_LINK, \OCP\Share::SHARE_TYPE_REMOTE, \OCP\Share::SHARE_TYPE_EMAIL, ]; foreach ($requestedShareTypes as $requestedShareType) { // one of each type is enough to find out about the types $shares = $this->shareManager->getSharesBy( $this->userId, $requestedShareType, $node, false, 1 ); if (!empty($shares)) { $shareTypes[] = $requestedShareType; } } return $shareTypes; } private function getSharesTypesInFolder(\OCP\Files\Folder $node) { $shares = $this->shareManager->getSharesInFolder( $this->userId, $node, true ); $shareTypesByFileId = []; foreach($shares as $fileId => $sharesForFile) { $types = array_map(function(IShare $share) { return $share->getShareType(); }, $sharesForFile); $types = array_unique($types); sort($types); $shareTypesByFileId[$fileId] = $types; } return $shareTypesByFileId; } /** * Adds shares to propfind response * * @param PropFind $propFind propfind object * @param \Sabre\DAV\INode $sabreNode sabre node */ public function handleGetProperties( PropFind $propFind, \Sabre\DAV\INode $sabreNode ) { if (!($sabreNode instanceof \OCA\DAV\Connector\Sabre\Node)) { return; } // need prefetch ? if ($sabreNode instanceof \OCA\DAV\Connector\Sabre\Directory && $propFind->getDepth() !== 0 && !is_null($propFind->getStatus(self::SHARETYPES_PROPERTYNAME)) ) { $folderNode = $this->userFolder->get($sabreNode->getPath()); $childShares = $this->getSharesTypesInFolder($folderNode); $this->cachedFolders[] = $sabreNode->getPath(); $this->cachedShareTypes[$folderNode->getId()] = $this->getShareTypes($folderNode); foreach ($childShares as $id => $shares) { $this->cachedShareTypes[$id] = $shares; } } $propFind->handle(self::SHARETYPES_PROPERTYNAME, function () use ($sabreNode) { if (isset($this->cachedShareTypes[$sabreNode->getId()])) { $shareTypes = $this->cachedShareTypes[$sabreNode->getId()]; } else { list($parentPath,) = \Sabre\Uri\split($sabreNode->getPath()); if ($parentPath === '') { $parentPath = '/'; } // if we already cached the folder this file is in we know there are no shares for this file if (array_search($parentPath, $this->cachedFolders) === false) { $node = $this->userFolder->get($sabreNode->getPath()); $shareTypes = $this->getShareTypes($node); } else { return []; } } return new ShareTypeList($shareTypes); }); } } Sabre/FilesReportPlugin.php 0000604 00000024267 15247115714 0011744 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Joas Schilling <coding@schilljs.com> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OC\Files\View; use Sabre\DAV\Exception\PreconditionFailed; use Sabre\DAV\Exception\BadRequest; use Sabre\DAV\ServerPlugin; use Sabre\DAV\Tree; use Sabre\DAV\Xml\Element\Response; use Sabre\DAV\Xml\Response\MultiStatus; use Sabre\DAV\PropFind; use OCP\SystemTag\ISystemTagObjectMapper; use OCP\IUserSession; use OCP\Files\Folder; use OCP\IGroupManager; use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\TagNotFoundException; use OCP\ITagManager; class FilesReportPlugin extends ServerPlugin { // namespace const NS_OWNCLOUD = 'http://owncloud.org/ns'; const REPORT_NAME = '{http://owncloud.org/ns}filter-files'; const SYSTEMTAG_PROPERTYNAME = '{http://owncloud.org/ns}systemtag'; /** * Reference to main server object * * @var \Sabre\DAV\Server */ private $server; /** * @var Tree */ private $tree; /** * @var View */ private $fileView; /** * @var ISystemTagManager */ private $tagManager; /** * @var ISystemTagObjectMapper */ private $tagMapper; /** * Manager for private tags * * @var ITagManager */ private $fileTagger; /** * @var IUserSession */ private $userSession; /** * @var IGroupManager */ private $groupManager; /** * @var Folder */ private $userFolder; /** * @param Tree $tree * @param View $view * @param ISystemTagManager $tagManager * @param ISystemTagObjectMapper $tagMapper * @param ITagManager $fileTagger manager for private tags * @param IUserSession $userSession * @param IGroupManager $groupManager * @param Folder $userFolder */ public function __construct(Tree $tree, View $view, ISystemTagManager $tagManager, ISystemTagObjectMapper $tagMapper, ITagManager $fileTagger, IUserSession $userSession, IGroupManager $groupManager, Folder $userFolder ) { $this->tree = $tree; $this->fileView = $view; $this->tagManager = $tagManager; $this->tagMapper = $tagMapper; $this->fileTagger = $fileTagger; $this->userSession = $userSession; $this->groupManager = $groupManager; $this->userFolder = $userFolder; } /** * This initializes the plugin. * * This function is called by \Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param \Sabre\DAV\Server $server * @return void */ public function initialize(\Sabre\DAV\Server $server) { $server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc'; $this->server = $server; $this->server->on('report', array($this, 'onReport')); } /** * Returns a list of reports this plugin supports. * * This will be used in the {DAV:}supported-report-set property. * * @param string $uri * @return array */ public function getSupportedReportSet($uri) { return [self::REPORT_NAME]; } /** * REPORT operations to look for files * * @param string $reportName * @param $report * @param string $uri * @return bool * @throws BadRequest * @throws PreconditionFailed * @internal param $ [] $report */ public function onReport($reportName, $report, $uri) { $reportTargetNode = $this->server->tree->getNodeForPath($uri); if (!$reportTargetNode instanceof Directory || $reportName !== self::REPORT_NAME) { return; } $ns = '{' . $this::NS_OWNCLOUD . '}'; $requestedProps = []; $filterRules = []; // parse report properties and gather filter info foreach ($report as $reportProps) { $name = $reportProps['name']; if ($name === $ns . 'filter-rules') { $filterRules = $reportProps['value']; } else if ($name === '{DAV:}prop') { // propfind properties foreach ($reportProps['value'] as $propVal) { $requestedProps[] = $propVal['name']; } } } if (empty($filterRules)) { // an empty filter would return all existing files which would be slow throw new BadRequest('Missing filter-rule block in request'); } // gather all file ids matching filter try { $resultFileIds = $this->processFilterRules($filterRules); } catch (TagNotFoundException $e) { throw new PreconditionFailed('Cannot filter by non-existing tag', 0, $e); } // find sabre nodes by file id, restricted to the root node path $results = $this->findNodesByFileIds($reportTargetNode, $resultFileIds); $filesUri = $this->getFilesBaseUri($uri, $reportTargetNode->getPath()); $responses = $this->prepareResponses($filesUri, $requestedProps, $results); $xml = $this->server->xml->write( '{DAV:}multistatus', new MultiStatus($responses) ); $this->server->httpResponse->setStatus(207); $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8'); $this->server->httpResponse->setBody($xml); return false; } /** * Returns the base uri of the files root by removing * the subpath from the URI * * @param string $uri URI from this request * @param string $subPath subpath to remove from the URI * * @return string files base uri */ private function getFilesBaseUri($uri, $subPath) { $uri = trim($uri, '/'); $subPath = trim($subPath, '/'); if (empty($subPath)) { $filesUri = $uri; } else { $filesUri = substr($uri, 0, strlen($uri) - strlen($subPath)); } $filesUri = trim($filesUri, '/'); if (empty($filesUri)) { return ''; } return '/' . $filesUri; } /** * Find file ids matching the given filter rules * * @param array $filterRules * @return array array of unique file id results * * @throws TagNotFoundException whenever a tag was not found */ protected function processFilterRules($filterRules) { $ns = '{' . $this::NS_OWNCLOUD . '}'; $resultFileIds = null; $systemTagIds = []; $favoriteFilter = null; foreach ($filterRules as $filterRule) { if ($filterRule['name'] === $ns . 'systemtag') { $systemTagIds[] = $filterRule['value']; } if ($filterRule['name'] === $ns . 'favorite') { $favoriteFilter = true; } } if ($favoriteFilter !== null) { $resultFileIds = $this->fileTagger->load('files')->getFavorites(); if (empty($resultFileIds)) { return []; } } if (!empty($systemTagIds)) { $fileIds = $this->getSystemTagFileIds($systemTagIds); if (empty($resultFileIds)) { $resultFileIds = $fileIds; } else { $resultFileIds = array_intersect($fileIds, $resultFileIds); } } return $resultFileIds; } private function getSystemTagFileIds($systemTagIds) { $resultFileIds = null; // check user permissions, if applicable if (!$this->isAdmin()) { // check visibility/permission $tags = $this->tagManager->getTagsByIds($systemTagIds); $unknownTagIds = []; foreach ($tags as $tag) { if (!$tag->isUserVisible()) { $unknownTagIds[] = $tag->getId(); } } if (!empty($unknownTagIds)) { throw new TagNotFoundException('Tag with ids ' . implode(', ', $unknownTagIds) . ' not found'); } } // fetch all file ids and intersect them foreach ($systemTagIds as $systemTagId) { $fileIds = $this->tagMapper->getObjectIdsForTags($systemTagId, 'files'); if (empty($fileIds)) { // This tag has no files, nothing can ever show up return []; } // first run ? if ($resultFileIds === null) { $resultFileIds = $fileIds; } else { $resultFileIds = array_intersect($resultFileIds, $fileIds); } if (empty($resultFileIds)) { // Empty intersection, nothing can show up anymore return []; } } return $resultFileIds; } /** * Prepare propfind response for the given nodes * * @param string $filesUri $filesUri URI leading to root of the files URI, * with a leading slash but no trailing slash * @param string[] $requestedProps requested properties * @param Node[] nodes nodes for which to fetch and prepare responses * @return Response[] */ public function prepareResponses($filesUri, $requestedProps, $nodes) { $responses = []; foreach ($nodes as $node) { $propFind = new PropFind($filesUri . $node->getPath(), $requestedProps); $this->server->getPropertiesByNode($propFind, $node); // copied from Sabre Server's getPropertiesForPath $result = $propFind->getResultForMultiStatus(); $result['href'] = $propFind->getPath(); $resourceType = $this->server->getResourceTypeForNode($node); if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) { $result['href'] .= '/'; } $responses[] = new Response( rtrim($this->server->getBaseUri(), '/') . $filesUri . $node->getPath(), $result, 200 ); } return $responses; } /** * Find Sabre nodes by file ids * * @param Node $rootNode root node for search * @param array $fileIds file ids * @return Node[] array of Sabre nodes */ public function findNodesByFileIds($rootNode, $fileIds) { $folder = $this->userFolder; if (trim($rootNode->getPath(), '/') !== '') { $folder = $folder->get($rootNode->getPath()); } $results = []; foreach ($fileIds as $fileId) { $entry = $folder->getById($fileId); if ($entry) { $entry = current($entry); if ($entry instanceof \OCP\Files\File) { $results[] = new File($this->fileView, $entry); } else if ($entry instanceof \OCP\Files\Folder) { $results[] = new Directory($this->fileView, $entry); } } } return $results; } /** * Returns whether the currently logged in user is an administrator */ private function isAdmin() { $user = $this->userSession->getUser(); if ($user !== null) { return $this->groupManager->isAdmin($user->getUID()); } return false; } } Sabre/CopyEtagHeaderPlugin.php 0000604 00000004454 15247115714 0012326 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use \Sabre\HTTP\RequestInterface; use \Sabre\HTTP\ResponseInterface; /** * Copies the "Etag" header to "OC-Etag" after any request. * This is a workaround for setups that automatically strip * or mangle Etag headers. */ class CopyEtagHeaderPlugin extends \Sabre\DAV\ServerPlugin { /** @var \Sabre\DAV\Server */ private $server; /** * This initializes the plugin. * * @param \Sabre\DAV\Server $server Sabre server * * @return void */ public function initialize(\Sabre\DAV\Server $server) { $this->server = $server; $server->on('afterMethod', [$this, 'afterMethod']); $server->on('afterMove', [$this, 'afterMove']); } /** * After method, copy the "Etag" header to "OC-Etag" header. * * @param RequestInterface $request request * @param ResponseInterface $response response */ public function afterMethod(RequestInterface $request, ResponseInterface $response) { $eTag = $response->getHeader('Etag'); if (!empty($eTag)) { $response->setHeader('OC-ETag', $eTag); } } /** * Called after a node is moved. * * This allows the backend to move all the associated properties. * * @param string $source * @param string $destination * @return void */ function afterMove($source, $destination) { $node = $this->server->tree->getNodeForPath($destination); if ($node instanceof File) { $eTag = $node->getETag(); $this->server->httpResponse->setHeader('OC-ETag', $eTag); $this->server->httpResponse->setHeader('ETag', $eTag); } } } Sabre/ObjectTree.php 0000604 00000016053 15247115714 0010347 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Bjoern Schiessle <bjoern@schiessle.org> * @author Björn Schießle <bjoern@schiessle.org> * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OCA\DAV\Connector\Sabre\Exception\Forbidden; use OCA\DAV\Connector\Sabre\Exception\InvalidPath; use OCA\DAV\Connector\Sabre\Exception\FileLocked; use OC\Files\FileInfo; use OC\Files\Mount\MoveableMount; use OCP\Files\ForbiddenException; use OCP\Files\StorageInvalidException; use OCP\Files\StorageNotAvailableException; use OCP\Lock\LockedException; class ObjectTree extends \Sabre\DAV\Tree { /** * @var \OC\Files\View */ protected $fileView; /** * @var \OCP\Files\Mount\IMountManager */ protected $mountManager; /** * Creates the object */ public function __construct() { } /** * @param \Sabre\DAV\INode $rootNode * @param \OC\Files\View $view * @param \OCP\Files\Mount\IMountManager $mountManager */ public function init(\Sabre\DAV\INode $rootNode, \OC\Files\View $view, \OCP\Files\Mount\IMountManager $mountManager) { $this->rootNode = $rootNode; $this->fileView = $view; $this->mountManager = $mountManager; } /** * If the given path is a chunked file name, converts it * to the real file name. Only applies if the OC-CHUNKED header * is present. * * @param string $path chunk file path to convert * * @return string path to real file */ private function resolveChunkFile($path) { if (isset($_SERVER['HTTP_OC_CHUNKED'])) { // resolve to real file name to find the proper node list($dir, $name) = \Sabre\HTTP\URLUtil::splitPath($path); if ($dir == '/' || $dir == '.') { $dir = ''; } $info = \OC_FileChunking::decodeName($name); // only replace path if it was really the chunked file if (isset($info['transferid'])) { // getNodePath is called for multiple nodes within a chunk // upload call $path = $dir . '/' . $info['name']; $path = ltrim($path, '/'); } } return $path; } public function cacheNode(Node $node) { $this->cache[trim($node->getPath(), '/')] = $node; } /** * Returns the INode object for the requested path * * @param string $path * @return \Sabre\DAV\INode * @throws InvalidPath * @throws \Sabre\DAV\Exception\Locked * @throws \Sabre\DAV\Exception\NotFound * @throws \Sabre\DAV\Exception\ServiceUnavailable */ public function getNodeForPath($path) { if (!$this->fileView) { throw new \Sabre\DAV\Exception\ServiceUnavailable('filesystem not setup'); } $path = trim($path, '/'); if (isset($this->cache[$path])) { return $this->cache[$path]; } if ($path) { try { $this->fileView->verifyPath($path, basename($path)); } catch (\OCP\Files\InvalidPathException $ex) { throw new InvalidPath($ex->getMessage()); } } // Is it the root node? if (!strlen($path)) { return $this->rootNode; } if (pathinfo($path, PATHINFO_EXTENSION) === 'part') { // read from storage $absPath = $this->fileView->getAbsolutePath($path); $mount = $this->fileView->getMount($path); $storage = $mount->getStorage(); $internalPath = $mount->getInternalPath($absPath); if ($storage && $storage->file_exists($internalPath)) { /** * @var \OC\Files\Storage\Storage $storage */ // get data directly $data = $storage->getMetaData($internalPath); $info = new FileInfo($absPath, $storage, $internalPath, $data, $mount); } else { $info = null; } } else { // resolve chunk file name to real name, if applicable $path = $this->resolveChunkFile($path); // read from cache try { $info = $this->fileView->getFileInfo($path); } catch (StorageNotAvailableException $e) { throw new \Sabre\DAV\Exception\ServiceUnavailable('Storage is temporarily not available'); } catch (StorageInvalidException $e) { throw new \Sabre\DAV\Exception\NotFound('Storage ' . $path . ' is invalid'); } catch (LockedException $e) { throw new \Sabre\DAV\Exception\Locked(); } catch (ForbiddenException $e) { throw new \Sabre\DAV\Exception\Forbidden(); } } if (!$info) { throw new \Sabre\DAV\Exception\NotFound('File with name ' . $path . ' could not be located'); } if ($info->getType() === 'dir') { $node = new \OCA\DAV\Connector\Sabre\Directory($this->fileView, $info, $this); } else { $node = new \OCA\DAV\Connector\Sabre\File($this->fileView, $info); } $this->cache[$path] = $node; return $node; } /** * Copies a file or directory. * * This method must work recursively and delete the destination * if it exists * * @param string $source * @param string $destination * @throws FileLocked * @throws Forbidden * @throws InvalidPath * @throws \Exception * @throws \Sabre\DAV\Exception\Forbidden * @throws \Sabre\DAV\Exception\Locked * @throws \Sabre\DAV\Exception\NotFound * @throws \Sabre\DAV\Exception\ServiceUnavailable * @return void */ public function copy($source, $destination) { if (!$this->fileView) { throw new \Sabre\DAV\Exception\ServiceUnavailable('filesystem not setup'); } $info = $this->fileView->getFileInfo(dirname($destination)); if ($this->fileView->file_exists($destination)) { $destinationPermission = $info && $info->isUpdateable(); } else { $destinationPermission = $info && $info->isCreatable(); } if (!$destinationPermission) { throw new Forbidden('No permissions to copy object.'); } // this will trigger existence check $this->getNodeForPath($source); list($destinationDir, $destinationName) = \Sabre\HTTP\URLUtil::splitPath($destination); try { $this->fileView->verifyPath($destinationDir, $destinationName); } catch (\OCP\Files\InvalidPathException $ex) { throw new InvalidPath($ex->getMessage()); } try { $this->fileView->copy($source, $destination); } catch (StorageNotAvailableException $e) { throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage()); } catch (ForbiddenException $ex) { throw new Forbidden($ex->getMessage(), $ex->getRetry()); } catch (LockedException $e) { throw new FileLocked($e->getMessage(), $e->getCode(), $e); } list($destinationDir,) = \Sabre\HTTP\URLUtil::splitPath($destination); $this->markDirty($destinationDir); } } Sabre/Exception/PasswordLoginForbidden.php 0000604 00000003060 15247115714 0014661 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Christoph Wurst <christoph@owncloud.com> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre\Exception; use DOMElement; use Sabre\DAV\Server; use Sabre\DAV\Exception\NotAuthenticated; class PasswordLoginForbidden extends NotAuthenticated { const NS_OWNCLOUD = 'http://owncloud.org/ns'; public function getHTTPCode() { return 401; } /** * This method allows the exception to include additional information * into the WebDAV error response * * @param Server $server * @param DOMElement $errorNode * @return void */ public function serialize(Server $server, DOMElement $errorNode) { // set ownCloud namespace $errorNode->setAttribute('xmlns:o', self::NS_OWNCLOUD); $error = $errorNode->ownerDocument->createElementNS('o:', 'o:hint', 'password login forbidden'); $errorNode->appendChild($error); } } Sabre/Exception/EntityTooLarge.php 0000604 00000002304 15247115714 0013162 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Morris Jobke <hey@morrisjobke.de> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre\Exception; /** * Entity Too Large * * This exception is thrown whenever a user tries to upload a file which exceeds hard limitations * */ class EntityTooLarge extends \Sabre\DAV\Exception { /** * Returns the HTTP status code for this exception * * @return int */ public function getHTTPCode() { return 413; } } Sabre/Exception/FileLocked.php 0000604 00000002731 15247115714 0012256 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Owen Winkler <a_github@midnightcircus.com> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre\Exception; use Exception; class FileLocked extends \Sabre\DAV\Exception { public function __construct($message = "", $code = 0, Exception $previous = null) { if($previous instanceof \OCP\Files\LockNotAcquiredException) { $message = sprintf('Target file %s is locked by another process.', $previous->path); } parent::__construct($message, $code, $previous); } /** * Returns the HTTP status code for this exception * * @return int */ public function getHTTPCode() { return 423; } } Sabre/Exception/InvalidPath.php 0000604 00000003672 15247115714 0012465 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre\Exception; use Sabre\DAV\Exception; class InvalidPath extends Exception { const NS_OWNCLOUD = 'http://owncloud.org/ns'; /** * @var bool */ private $retry; /** * @param string $message * @param bool $retry */ public function __construct($message, $retry = false) { parent::__construct($message); $this->retry = $retry; } /** * Returns the HTTP status code for this exception * * @return int */ public function getHTTPCode() { return 400; } /** * This method allows the exception to include additional information * into the WebDAV error response * * @param \Sabre\DAV\Server $server * @param \DOMElement $errorNode * @return void */ public function serialize(\Sabre\DAV\Server $server,\DOMElement $errorNode) { // set ownCloud namespace $errorNode->setAttribute('xmlns:o', self::NS_OWNCLOUD); // adding the retry node $error = $errorNode->ownerDocument->createElementNS('o:','o:retry', var_export($this->retry, true)); $errorNode->appendChild($error); // adding the message node $error = $errorNode->ownerDocument->createElementNS('o:','o:reason', $this->getMessage()); $errorNode->appendChild($error); } } Sabre/Exception/UnsupportedMediaType.php 0000604 00000002332 15247115714 0014404 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Morris Jobke <hey@morrisjobke.de> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre\Exception; /** * Unsupported Media Type * * This exception is thrown whenever a user tries to upload a file which holds content which is not allowed * */ class UnsupportedMediaType extends \Sabre\DAV\Exception { /** * Returns the HTTP status code for this exception * * @return int */ public function getHTTPCode() { return 415; } } Sabre/Exception/Forbidden.php 0000604 00000003563 15247115714 0012155 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Joas Schilling <coding@schilljs.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre\Exception; class Forbidden extends \Sabre\DAV\Exception\Forbidden { const NS_OWNCLOUD = 'http://owncloud.org/ns'; /** * @var bool */ private $retry; /** * @param string $message * @param bool $retry * @param \Exception $previous */ public function __construct($message, $retry = false, \Exception $previous = null) { parent::__construct($message, 0, $previous); $this->retry = $retry; } /** * This method allows the exception to include additional information * into the WebDAV error response * * @param \Sabre\DAV\Server $server * @param \DOMElement $errorNode * @return void */ public function serialize(\Sabre\DAV\Server $server,\DOMElement $errorNode) { // set ownCloud namespace $errorNode->setAttribute('xmlns:o', self::NS_OWNCLOUD); // adding the retry node $error = $errorNode->ownerDocument->createElementNS('o:','o:retry', var_export($this->retry, true)); $errorNode->appendChild($error); // adding the message node $error = $errorNode->ownerDocument->createElementNS('o:','o:reason', $this->getMessage()); $errorNode->appendChild($error); } } Sabre/File.php 0000604 00000046711 15247115714 0007204 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Bart Visscher <bartv@thisnet.nl> * @author Björn Schießle <bjoern@schiessle.org> * @author Jakob Sack <mail@jakobsack.de> * @author Joas Schilling <coding@schilljs.com> * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Owen Winkler <a_github@midnightcircus.com> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OC\Files\Filesystem; use OCA\DAV\Connector\Sabre\Exception\EntityTooLarge; use OCA\DAV\Connector\Sabre\Exception\FileLocked; use OCA\DAV\Connector\Sabre\Exception\Forbidden as DAVForbiddenException; use OCA\DAV\Connector\Sabre\Exception\UnsupportedMediaType; use OCP\Encryption\Exceptions\GenericEncryptionException; use OCP\Files\EntityTooLargeException; use OCP\Files\ForbiddenException; use OCP\Files\InvalidContentException; use OCP\Files\InvalidPathException; use OCP\Files\LockNotAcquiredException; use OCP\Files\NotPermittedException; use OCP\Files\StorageNotAvailableException; use OCP\Lock\ILockingProvider; use OCP\Lock\LockedException; use Sabre\DAV\Exception; use Sabre\DAV\Exception\BadRequest; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotImplemented; use Sabre\DAV\Exception\ServiceUnavailable; use Sabre\DAV\IFile; use Sabre\DAV\Exception\NotFound; class File extends Node implements IFile { /** * Updates the data * * The data argument is a readable stream resource. * * After a successful put operation, you may choose to return an ETag. The * etag must always be surrounded by double-quotes. These quotes must * appear in the actual string you're returning. * * Clients may use the ETag from a PUT request to later on make sure that * when they update the file, the contents haven't changed in the mean * time. * * If you don't plan to store the file byte-by-byte, and you return a * different object on a subsequent GET you are strongly recommended to not * return an ETag, and just return null. * * @param resource $data * * @throws Forbidden * @throws UnsupportedMediaType * @throws BadRequest * @throws Exception * @throws EntityTooLarge * @throws ServiceUnavailable * @throws FileLocked * @return string|null */ public function put($data) { try { $exists = $this->fileView->file_exists($this->path); if ($this->info && $exists && !$this->info->isUpdateable()) { throw new Forbidden(); } } catch (StorageNotAvailableException $e) { throw new ServiceUnavailable("File is not updatable: " . $e->getMessage()); } // verify path of the target $this->verifyPath(); // chunked handling if (isset($_SERVER['HTTP_OC_CHUNKED'])) { try { return $this->createFileChunked($data); } catch (\Exception $e) { $this->convertToSabreException($e); } } list($partStorage) = $this->fileView->resolvePath($this->path); $needsPartFile = $this->needsPartFile($partStorage) && (strlen($this->path) > 1); if ($needsPartFile) { // mark file as partial while uploading (ignored by the scanner) $partFilePath = $this->getPartFileBasePath($this->path) . '.ocTransferId' . rand() . '.part'; } else { // upload file directly as the final path $partFilePath = $this->path; } // the part file and target file might be on a different storage in case of a single file storage (e.g. single file share) /** @var \OC\Files\Storage\Storage $partStorage */ list($partStorage, $internalPartPath) = $this->fileView->resolvePath($partFilePath); /** @var \OC\Files\Storage\Storage $storage */ list($storage, $internalPath) = $this->fileView->resolvePath($this->path); try { $target = $partStorage->fopen($internalPartPath, 'wb'); if ($target === false) { \OCP\Util::writeLog('webdav', '\OC\Files\Filesystem::fopen() failed', \OCP\Util::ERROR); // because we have no clue about the cause we can only throw back a 500/Internal Server Error throw new Exception('Could not write file contents'); } list($count, $result) = \OC_Helper::streamCopy($data, $target); fclose($target); if ($result === false) { $expected = -1; if (isset($_SERVER['CONTENT_LENGTH'])) { $expected = $_SERVER['CONTENT_LENGTH']; } throw new Exception('Error while copying file to target location (copied bytes: ' . $count . ', expected filesize: ' . $expected . ' )'); } // if content length is sent by client: // double check if the file was fully received // compare expected and actual size if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') { $expected = $_SERVER['CONTENT_LENGTH']; if ($count != $expected) { throw new BadRequest('expected filesize ' . $expected . ' got ' . $count); } } } catch (\Exception $e) { if ($needsPartFile) { $partStorage->unlink($internalPartPath); } $this->convertToSabreException($e); } try { $view = \OC\Files\Filesystem::getView(); if ($view) { $run = $this->emitPreHooks($exists); } else { $run = true; } try { $this->changeLock(ILockingProvider::LOCK_EXCLUSIVE); } catch (LockedException $e) { if ($needsPartFile) { $partStorage->unlink($internalPartPath); } throw new FileLocked($e->getMessage(), $e->getCode(), $e); } if ($needsPartFile) { // rename to correct path try { if ($run) { $renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath); $fileExists = $storage->file_exists($internalPath); } if (!$run || $renameOkay === false || $fileExists === false) { \OCP\Util::writeLog('webdav', 'renaming part file to final file failed ($run: ' . ( $run ? 'true' : 'false' ) . ', $renameOkay: ' . ( $renameOkay ? 'true' : 'false' ) . ', $fileExists: ' . ( $fileExists ? 'true' : 'false' ) . ')', \OCP\Util::ERROR); throw new Exception('Could not rename part file to final file'); } } catch (ForbiddenException $ex) { throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry()); } catch (\Exception $e) { $partStorage->unlink($internalPartPath); $this->convertToSabreException($e); } } // since we skipped the view we need to scan and emit the hooks ourselves $storage->getUpdater()->update($internalPath); try { $this->changeLock(ILockingProvider::LOCK_SHARED); } catch (LockedException $e) { throw new FileLocked($e->getMessage(), $e->getCode(), $e); } // allow sync clients to send the mtime along in a header $request = \OC::$server->getRequest(); if (isset($request->server['HTTP_X_OC_MTIME'])) { $mtimeStr = $request->server['HTTP_X_OC_MTIME']; if (!is_numeric($mtimeStr)) { throw new \InvalidArgumentException('X-OC-Mtime header must be an integer (unix timestamp).'); } $mtime = intval($mtimeStr); if ($this->fileView->touch($this->path, $mtime)) { header('X-OC-MTime: accepted'); } } if ($view) { $this->emitPostHooks($exists); } $this->refreshInfo(); if (isset($request->server['HTTP_OC_CHECKSUM'])) { $checksum = trim($request->server['HTTP_OC_CHECKSUM']); $this->fileView->putFileInfo($this->path, ['checksum' => $checksum]); $this->refreshInfo(); } else if ($this->getChecksum() !== null && $this->getChecksum() !== '') { $this->fileView->putFileInfo($this->path, ['checksum' => '']); $this->refreshInfo(); } } catch (StorageNotAvailableException $e) { throw new ServiceUnavailable("Failed to check file size: " . $e->getMessage()); } return '"' . $this->info->getEtag() . '"'; } private function getPartFileBasePath($path) { $partFileInStorage = \OC::$server->getConfig()->getSystemValue('part_file_in_storage', true); if ($partFileInStorage) { return $path; } else { return md5($path); // will place it in the root of the view with a unique name } } /** * @param string $path */ private function emitPreHooks($exists, $path = null) { if (is_null($path)) { $path = $this->path; } $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path)); $run = true; if (!$exists) { \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_create, array( \OC\Files\Filesystem::signal_param_path => $hookPath, \OC\Files\Filesystem::signal_param_run => &$run, )); } else { \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_update, array( \OC\Files\Filesystem::signal_param_path => $hookPath, \OC\Files\Filesystem::signal_param_run => &$run, )); } \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_write, array( \OC\Files\Filesystem::signal_param_path => $hookPath, \OC\Files\Filesystem::signal_param_run => &$run, )); return $run; } /** * @param string $path */ private function emitPostHooks($exists, $path = null) { if (is_null($path)) { $path = $this->path; } $hookPath = Filesystem::getView()->getRelativePath($this->fileView->getAbsolutePath($path)); if (!$exists) { \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_create, array( \OC\Files\Filesystem::signal_param_path => $hookPath )); } else { \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_update, array( \OC\Files\Filesystem::signal_param_path => $hookPath )); } \OC_Hook::emit(\OC\Files\Filesystem::CLASSNAME, \OC\Files\Filesystem::signal_post_write, array( \OC\Files\Filesystem::signal_param_path => $hookPath )); } /** * Returns the data * * @return resource * @throws Forbidden * @throws ServiceUnavailable */ public function get() { //throw exception if encryption is disabled but files are still encrypted try { if (!$this->info->isReadable()) { // do a if the file did not exist throw new NotFound(); } $res = $this->fileView->fopen(ltrim($this->path, '/'), 'rb'); if ($res === false) { throw new ServiceUnavailable("Could not open file"); } return $res; } catch (GenericEncryptionException $e) { // returning 503 will allow retry of the operation at a later point in time throw new ServiceUnavailable("Encryption not ready: " . $e->getMessage()); } catch (StorageNotAvailableException $e) { throw new ServiceUnavailable("Failed to open file: " . $e->getMessage()); } catch (ForbiddenException $ex) { throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry()); } catch (LockedException $e) { throw new FileLocked($e->getMessage(), $e->getCode(), $e); } } /** * Delete the current file * * @throws Forbidden * @throws ServiceUnavailable */ public function delete() { if (!$this->info->isDeletable()) { throw new Forbidden(); } try { if (!$this->fileView->unlink($this->path)) { // assume it wasn't possible to delete due to permissions throw new Forbidden(); } } catch (StorageNotAvailableException $e) { throw new ServiceUnavailable("Failed to unlink: " . $e->getMessage()); } catch (ForbiddenException $ex) { throw new DAVForbiddenException($ex->getMessage(), $ex->getRetry()); } catch (LockedException $e) { throw new FileLocked($e->getMessage(), $e->getCode(), $e); } } /** * Returns the mime-type for a file * * If null is returned, we'll assume application/octet-stream * * @return string */ public function getContentType() { $mimeType = $this->info->getMimetype(); // PROPFIND needs to return the correct mime type, for consistency with the web UI if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PROPFIND') { return $mimeType; } return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType); } /** * @return array|false */ public function getDirectDownload() { if (\OCP\App::isEnabled('encryption')) { return []; } /** @var \OCP\Files\Storage $storage */ list($storage, $internalPath) = $this->fileView->resolvePath($this->path); if (is_null($storage)) { return []; } return $storage->getDirectDownload($internalPath); } /** * @param resource $data * @return null|string * @throws Exception * @throws BadRequest * @throws NotImplemented * @throws ServiceUnavailable */ private function createFileChunked($data) { list($path, $name) = \Sabre\HTTP\URLUtil::splitPath($this->path); $info = \OC_FileChunking::decodeName($name); if (empty($info)) { throw new NotImplemented('Invalid chunk name'); } $chunk_handler = new \OC_FileChunking($info); $bytesWritten = $chunk_handler->store($info['index'], $data); //detect aborted upload if (isset ($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') { if (isset($_SERVER['CONTENT_LENGTH'])) { $expected = $_SERVER['CONTENT_LENGTH']; if ($bytesWritten != $expected) { $chunk_handler->remove($info['index']); throw new BadRequest( 'expected filesize ' . $expected . ' got ' . $bytesWritten); } } } if ($chunk_handler->isComplete()) { list($storage,) = $this->fileView->resolvePath($path); $needsPartFile = $this->needsPartFile($storage); $partFile = null; $targetPath = $path . '/' . $info['name']; /** @var \OC\Files\Storage\Storage $targetStorage */ list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath); $exists = $this->fileView->file_exists($targetPath); try { $this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED); $this->emitPreHooks($exists, $targetPath); $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE); /** @var \OC\Files\Storage\Storage $targetStorage */ list($targetStorage, $targetInternalPath) = $this->fileView->resolvePath($targetPath); if ($needsPartFile) { // we first assembly the target file as a part file $partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part'; /** @var \OC\Files\Storage\Storage $targetStorage */ list($partStorage, $partInternalPath) = $this->fileView->resolvePath($partFile); $chunk_handler->file_assemble($partStorage, $partInternalPath); // here is the final atomic rename $renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath); $fileExists = $targetStorage->file_exists($targetInternalPath); if ($renameOkay === false || $fileExists === false) { \OCP\Util::writeLog('webdav', '\OC\Files\Filesystem::rename() failed', \OCP\Util::ERROR); // only delete if an error occurred and the target file was already created if ($fileExists) { // set to null to avoid double-deletion when handling exception // stray part file $partFile = null; $targetStorage->unlink($targetInternalPath); } $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED); throw new Exception('Could not rename part file assembled from chunks'); } } else { // assemble directly into the final file $chunk_handler->file_assemble($targetStorage, $targetInternalPath); } // allow sync clients to send the mtime along in a header $request = \OC::$server->getRequest(); if (isset($request->server['HTTP_X_OC_MTIME'])) { if ($targetStorage->touch($targetInternalPath, $request->server['HTTP_X_OC_MTIME'])) { header('X-OC-MTime: accepted'); } } // since we skipped the view we need to scan and emit the hooks ourselves $targetStorage->getUpdater()->update($targetInternalPath); $this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED); $this->emitPostHooks($exists, $targetPath); // FIXME: should call refreshInfo but can't because $this->path is not the of the final file $info = $this->fileView->getFileInfo($targetPath); if (isset($request->server['HTTP_OC_CHECKSUM'])) { $checksum = trim($request->server['HTTP_OC_CHECKSUM']); $this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]); } else if ($info->getChecksum() !== null && $info->getChecksum() !== '') { $this->fileView->putFileInfo($this->path, ['checksum' => '']); } $this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED); return $info->getEtag(); } catch (\Exception $e) { if ($partFile !== null) { $targetStorage->unlink($targetInternalPath); } $this->convertToSabreException($e); } } return null; } /** * Returns whether a part file is needed for the given storage * or whether the file can be assembled/uploaded directly on the * target storage. * * @param \OCP\Files\Storage $storage * @return bool true if the storage needs part file handling */ private function needsPartFile($storage) { // TODO: in the future use ChunkHandler provided by storage return !$storage->instanceOfStorage('OCA\Files_Sharing\External\Storage') && !$storage->instanceOfStorage('OC\Files\Storage\OwnCloud') && $storage->needsPartFile(); } /** * Convert the given exception to a SabreException instance * * @param \Exception $e * * @throws \Sabre\DAV\Exception */ private function convertToSabreException(\Exception $e) { if ($e instanceof \Sabre\DAV\Exception) { throw $e; } if ($e instanceof NotPermittedException) { // a more general case - due to whatever reason the content could not be written throw new Forbidden($e->getMessage(), 0, $e); } if ($e instanceof ForbiddenException) { // the path for the file was forbidden throw new DAVForbiddenException($e->getMessage(), $e->getRetry(), $e); } if ($e instanceof EntityTooLargeException) { // the file is too big to be stored throw new EntityTooLarge($e->getMessage(), 0, $e); } if ($e instanceof InvalidContentException) { // the file content is not permitted throw new UnsupportedMediaType($e->getMessage(), 0, $e); } if ($e instanceof InvalidPathException) { // the path for the file was not valid // TODO: find proper http status code for this case throw new Forbidden($e->getMessage(), 0, $e); } if ($e instanceof LockedException || $e instanceof LockNotAcquiredException) { // the file is currently being written to by another process throw new FileLocked($e->getMessage(), $e->getCode(), $e); } if ($e instanceof GenericEncryptionException) { // returning 503 will allow retry of the operation at a later point in time throw new ServiceUnavailable('Encryption not ready: ' . $e->getMessage(), 0, $e); } if ($e instanceof StorageNotAvailableException) { throw new ServiceUnavailable('Failed to write file contents: ' . $e->getMessage(), 0, $e); } throw new \Sabre\DAV\Exception($e->getMessage(), 0, $e); } /** * Get the checksum for this file * * @return string */ public function getChecksum() { return $this->info->getChecksum(); } } Sabre/DummyGetResponsePlugin.php 0000604 00000004224 15247115714 0012747 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use Sabre\HTTP\ResponseInterface; use Sabre\HTTP\RequestInterface; /** * Class DummyGetResponsePlugin is a plugin used to not show a "Not implemented" * error to clients that rely on verifying the functionality of the Nextcloud * WebDAV backend using a simple GET to /. * * This is considered a legacy behaviour and implementers should consider sending * a PROPFIND request instead to verify whether the WebDAV component is working * properly. * * FIXME: Remove once clients are all compliant. * * @package OCA\DAV\Connector\Sabre */ class DummyGetResponsePlugin extends \Sabre\DAV\ServerPlugin { /** @var \Sabre\DAV\Server */ protected $server; /** * @param \Sabre\DAV\Server $server * @return void */ function initialize(\Sabre\DAV\Server $server) { $this->server = $server; $this->server->on('method:GET', [$this, 'httpGet'], 200); } /** * @param RequestInterface $request * @param ResponseInterface $response * @return false */ function httpGet(RequestInterface $request, ResponseInterface $response) { $string = 'This is the WebDAV interface. It can only be accessed by ' . 'WebDAV clients such as the Nextcloud desktop sync client.'; $stream = fopen('php://memory','r+'); fwrite($stream, $string); rewind($stream); $response->setStatus(200); $response->setBody($stream); return false; } } Sabre/Node.php 0000604 00000020440 15247115714 0007201 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Bart Visscher <bartv@thisnet.nl> * @author Björn Schießle <bjoern@schiessle.org> * @author Jakob Sack <mail@jakobsack.de> * @author Jörn Friedrich Dreyer <jfd@butonic.de> * @author Klaas Freitag <freitag@owncloud.com> * @author Markus Goetz <markus@woboq.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OC\Files\Mount\MoveableMount; use OC\Files\View; use OCA\DAV\Connector\Sabre\Exception\InvalidPath; use OCP\Files\FileInfo; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager; abstract class Node implements \Sabre\DAV\INode { /** * @var \OC\Files\View */ protected $fileView; /** * The path to the current node * * @var string */ protected $path; /** * node properties cache * * @var array */ protected $property_cache = null; /** * @var \OCP\Files\FileInfo */ protected $info; /** * @var IManager */ protected $shareManager; /** * Sets up the node, expects a full path name * * @param \OC\Files\View $view * @param \OCP\Files\FileInfo $info * @param IManager $shareManager */ public function __construct(View $view, FileInfo $info, IManager $shareManager = null) { $this->fileView = $view; $this->path = $this->fileView->getRelativePath($info->getPath()); $this->info = $info; if ($shareManager) { $this->shareManager = $shareManager; } else { $this->shareManager = \OC::$server->getShareManager(); } } protected function refreshInfo() { $this->info = $this->fileView->getFileInfo($this->path); } /** * Returns the name of the node * * @return string */ public function getName() { return $this->info->getName(); } /** * Returns the full path * * @return string */ public function getPath() { return $this->path; } /** * Renames the node * * @param string $name The new name * @throws \Sabre\DAV\Exception\BadRequest * @throws \Sabre\DAV\Exception\Forbidden */ public function setName($name) { // rename is only allowed if the update privilege is granted if (!$this->info->isUpdateable()) { throw new \Sabre\DAV\Exception\Forbidden(); } list($parentPath,) = \Sabre\HTTP\URLUtil::splitPath($this->path); list(, $newName) = \Sabre\HTTP\URLUtil::splitPath($name); // verify path of the target $this->verifyPath(); $newPath = $parentPath . '/' . $newName; $this->fileView->rename($this->path, $newPath); $this->path = $newPath; $this->refreshInfo(); } public function setPropertyCache($property_cache) { $this->property_cache = $property_cache; } /** * Returns the last modification time, as a unix timestamp * * @return int timestamp as integer */ public function getLastModified() { $timestamp = $this->info->getMtime(); if (!empty($timestamp)) { return (int)$timestamp; } return $timestamp; } /** * sets the last modification time of the file (mtime) to the value given * in the second parameter or to now if the second param is empty. * Even if the modification time is set to a custom value the access time is set to now. */ public function touch($mtime) { $this->fileView->touch($this->path, $mtime); $this->refreshInfo(); } /** * Returns the ETag for a file * * An ETag is a unique identifier representing the current version of the * file. If the file changes, the ETag MUST change. The ETag is an * arbitrary string, but MUST be surrounded by double-quotes. * * Return null if the ETag can not effectively be determined * * @return string */ public function getETag() { return '"' . $this->info->getEtag() . '"'; } /** * Sets the ETag * * @param string $etag * * @return int file id of updated file or -1 on failure */ public function setETag($etag) { return $this->fileView->putFileInfo($this->path, array('etag' => $etag)); } /** * Returns the size of the node, in bytes * * @return integer */ public function getSize() { return $this->info->getSize(); } /** * Returns the cache's file id * * @return int */ public function getId() { return $this->info->getId(); } /** * @return string|null */ public function getFileId() { if ($this->info->getId()) { $instanceId = \OC_Util::getInstanceId(); $id = sprintf('%08d', $this->info->getId()); return $id . $instanceId; } return null; } /** * @return integer */ public function getInternalFileId() { return $this->info->getId(); } /** * @param string $user * @return int */ public function getSharePermissions($user) { // check of we access a federated share if ($user !== null) { try { $share = $this->shareManager->getShareByToken($user); return $share->getPermissions(); } catch (ShareNotFound $e) { // ignore } } $storage = $this->info->getStorage(); $path = $this->info->getInternalPath(); if ($storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) { /** @var \OCA\Files_Sharing\SharedStorage $storage */ $permissions = (int)$storage->getShare()->getPermissions(); } else { $permissions = $storage->getPermissions($path); } /* * We can always share non moveable mount points with DELETE and UPDATE * Eventually we need to do this properly */ $mountpoint = $this->info->getMountPoint(); if (!($mountpoint instanceof MoveableMount)) { $mountpointpath = $mountpoint->getMountPoint(); if (substr($mountpointpath, -1) === '/') { $mountpointpath = substr($mountpointpath, 0, -1); } if ($mountpointpath === $this->info->getPath()) { $permissions |= \OCP\Constants::PERMISSION_DELETE | \OCP\Constants::PERMISSION_UPDATE; } } /* * Files can't have create or delete permissions */ if ($this->info->getType() === \OCP\Files\FileInfo::TYPE_FILE) { $permissions &= ~(\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_DELETE); } return $permissions; } /** * @return string */ public function getDavPermissions() { $p = ''; if ($this->info->isShared()) { $p .= 'S'; } if ($this->info->isShareable()) { $p .= 'R'; } if ($this->info->isMounted()) { $p .= 'M'; } if ($this->info->isDeletable()) { $p .= 'D'; } if ($this->info->isUpdateable()) { $p .= 'NV'; // Renameable, Moveable } if ($this->info->getType() === \OCP\Files\FileInfo::TYPE_FILE) { if ($this->info->isUpdateable()) { $p .= 'W'; } } else { if ($this->info->isCreatable()) { $p .= 'CK'; } } return $p; } public function getOwner() { return $this->info->getOwner(); } protected function verifyPath() { try { $fileName = basename($this->info->getPath()); $this->fileView->verifyPath($this->path, $fileName); } catch (\OCP\Files\InvalidPathException $ex) { throw new InvalidPath($ex->getMessage()); } } /** * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE */ public function acquireLock($type) { $this->fileView->lockFile($this->path, $type); } /** * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE */ public function releaseLock($type) { $this->fileView->unlockFile($this->path, $type); } /** * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE */ public function changeLock($type) { $this->fileView->changeLock($this->path, $type); } public function getFileInfo() { return $this->info; } } Sabre/BlockLegacyClientPlugin.php 0000604 00000004671 15247115714 0013021 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OCP\IConfig; use Sabre\HTTP\RequestInterface; use Sabre\DAV\ServerPlugin; /** * Class BlockLegacyClientPlugin is used to detect old legacy sync clients and * returns a 403 status to those clients * * @package OCA\DAV\Connector\Sabre */ class BlockLegacyClientPlugin extends ServerPlugin { /** @var \Sabre\DAV\Server */ protected $server; /** @var IConfig */ protected $config; /** * @param IConfig $config */ public function __construct(IConfig $config) { $this->config = $config; } /** * @param \Sabre\DAV\Server $server * @return void */ public function initialize(\Sabre\DAV\Server $server) { $this->server = $server; $this->server->on('beforeMethod', [$this, 'beforeHandler'], 200); } /** * Detects all unsupported clients and throws a \Sabre\DAV\Exception\Forbidden * exception which will result in a 403 to them. * @param RequestInterface $request * @throws \Sabre\DAV\Exception\Forbidden If the client version is not supported */ public function beforeHandler(RequestInterface $request) { $userAgent = $request->getHeader('User-Agent'); if($userAgent === null) { return; } $minimumSupportedDesktopVersion = $this->config->getSystemValue('minimum.supported.desktop.version', '2.0.0'); // Match on the mirall version which is in scheme "Mozilla/5.0 (%1) mirall/%2" or // "mirall/%1" for older releases preg_match("/(?:mirall\\/)([\d.]+)/i", $userAgent, $versionMatches); if(isset($versionMatches[1]) && version_compare($versionMatches[1], $minimumSupportedDesktopVersion) === -1) { throw new \Sabre\DAV\Exception\Forbidden('Unsupported client version.'); } } } Sabre/Directory.php 0000604 00000033715 15247115714 0010271 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Arthur Schiwon <blizzz@arthur-schiwon.de> * @author Bart Visscher <bartv@thisnet.nl> * @author Björn Schießle <bjoern@schiessle.org> * @author Jakob Sack <mail@jakobsack.de> * @author Joas Schilling <coding@schilljs.com> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OC\Files\View; use OCA\DAV\Connector\Sabre\Exception\Forbidden; use OCA\DAV\Connector\Sabre\Exception\InvalidPath; use OCA\DAV\Connector\Sabre\Exception\FileLocked; use OCP\Files\FileInfo; use OCP\Files\ForbiddenException; use OCP\Files\InvalidPathException; use OCP\Files\StorageNotAvailableException; use OCP\Lock\ILockingProvider; use OCP\Lock\LockedException; use Sabre\DAV\Exception\Locked; use Sabre\DAV\Exception\ServiceUnavailable; use Sabre\DAV\INode; use Sabre\DAV\Exception\BadRequest; use OC\Files\Mount\MoveableMount; use Sabre\DAV\IFile; use Sabre\DAV\Exception\NotFound; class Directory extends \OCA\DAV\Connector\Sabre\Node implements \Sabre\DAV\ICollection, \Sabre\DAV\IQuota, \Sabre\DAV\IMoveTarget { /** * Cached directory content * * @var \OCP\Files\FileInfo[] */ private $dirContent; /** * Cached quota info * * @var array */ private $quotaInfo; /** * @var ObjectTree|null */ private $tree; /** * Sets up the node, expects a full path name * * @param \OC\Files\View $view * @param \OCP\Files\FileInfo $info * @param ObjectTree|null $tree * @param \OCP\Share\IManager $shareManager */ public function __construct(View $view, FileInfo $info, $tree = null, $shareManager = null) { parent::__construct($view, $info, $shareManager); $this->tree = $tree; } /** * Creates a new file in the directory * * Data will either be supplied as a stream resource, or in certain cases * as a string. Keep in mind that you may have to support either. * * After successful creation of the file, you may choose to return the ETag * of the new file here. * * The returned ETag must be surrounded by double-quotes (The quotes should * be part of the actual string). * * If you cannot accurately determine the ETag, you should not return it. * If you don't store the file exactly as-is (you're transforming it * somehow) you should also not return an ETag. * * This means that if a subsequent GET to this new file does not exactly * return the same contents of what was submitted here, you are strongly * recommended to omit the ETag. * * @param string $name Name of the file * @param resource|string $data Initial payload * @return null|string * @throws Exception\EntityTooLarge * @throws Exception\UnsupportedMediaType * @throws FileLocked * @throws InvalidPath * @throws \Sabre\DAV\Exception * @throws \Sabre\DAV\Exception\BadRequest * @throws \Sabre\DAV\Exception\Forbidden * @throws \Sabre\DAV\Exception\ServiceUnavailable */ public function createFile($name, $data = null) { try { // for chunked upload also updating a existing file is a "createFile" // because we create all the chunks before re-assemble them to the existing file. if (isset($_SERVER['HTTP_OC_CHUNKED'])) { // exit if we can't create a new file and we don't updatable existing file $chunkInfo = \OC_FileChunking::decodeName($name); if (!$this->fileView->isCreatable($this->path) && !$this->fileView->isUpdatable($this->path . '/' . $chunkInfo['name']) ) { throw new \Sabre\DAV\Exception\Forbidden(); } } else { // For non-chunked upload it is enough to check if we can create a new file if (!$this->fileView->isCreatable($this->path)) { throw new \Sabre\DAV\Exception\Forbidden(); } } $this->fileView->verifyPath($this->path, $name); $path = $this->fileView->getAbsolutePath($this->path) . '/' . $name; // in case the file already exists/overwriting $info = $this->fileView->getFileInfo($this->path . '/' . $name); if (!$info) { // use a dummy FileInfo which is acceptable here since it will be refreshed after the put is complete $info = new \OC\Files\FileInfo($path, null, null, [], null); } $node = new \OCA\DAV\Connector\Sabre\File($this->fileView, $info); $node->acquireLock(ILockingProvider::LOCK_SHARED); return $node->put($data); } catch (\OCP\Files\StorageNotAvailableException $e) { throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage()); } catch (InvalidPathException $ex) { throw new InvalidPath($ex->getMessage()); } catch (ForbiddenException $ex) { throw new Forbidden($ex->getMessage(), $ex->getRetry()); } catch (LockedException $e) { throw new FileLocked($e->getMessage(), $e->getCode(), $e); } } /** * Creates a new subdirectory * * @param string $name * @throws FileLocked * @throws InvalidPath * @throws \Sabre\DAV\Exception\Forbidden * @throws \Sabre\DAV\Exception\ServiceUnavailable */ public function createDirectory($name) { try { if (!$this->info->isCreatable()) { throw new \Sabre\DAV\Exception\Forbidden(); } $this->fileView->verifyPath($this->path, $name); $newPath = $this->path . '/' . $name; if (!$this->fileView->mkdir($newPath)) { throw new \Sabre\DAV\Exception\Forbidden('Could not create directory ' . $newPath); } } catch (\OCP\Files\StorageNotAvailableException $e) { throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage()); } catch (InvalidPathException $ex) { throw new InvalidPath($ex->getMessage()); } catch (ForbiddenException $ex) { throw new Forbidden($ex->getMessage(), $ex->getRetry()); } catch (LockedException $e) { throw new FileLocked($e->getMessage(), $e->getCode(), $e); } } /** * Returns a specific child node, referenced by its name * * @param string $name * @param \OCP\Files\FileInfo $info * @return \Sabre\DAV\INode * @throws InvalidPath * @throws \Sabre\DAV\Exception\NotFound * @throws \Sabre\DAV\Exception\ServiceUnavailable */ public function getChild($name, $info = null) { if (!$this->info->isReadable()) { // avoid detecting files through this way throw new NotFound(); } $path = $this->path . '/' . $name; if (is_null($info)) { try { $this->fileView->verifyPath($this->path, $name); $info = $this->fileView->getFileInfo($path); } catch (\OCP\Files\StorageNotAvailableException $e) { throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage()); } catch (InvalidPathException $ex) { throw new InvalidPath($ex->getMessage()); } catch (ForbiddenException $e) { throw new \Sabre\DAV\Exception\Forbidden(); } } if (!$info) { throw new \Sabre\DAV\Exception\NotFound('File with name ' . $path . ' could not be located'); } if ($info['mimetype'] == 'httpd/unix-directory') { $node = new \OCA\DAV\Connector\Sabre\Directory($this->fileView, $info, $this->tree, $this->shareManager); } else { $node = new \OCA\DAV\Connector\Sabre\File($this->fileView, $info, $this->shareManager); } if ($this->tree) { $this->tree->cacheNode($node); } return $node; } /** * Returns an array with all the child nodes * * @return \Sabre\DAV\INode[] * @throws \Sabre\DAV\Exception\Locked * @throws \OCA\DAV\Connector\Sabre\Exception\Forbidden */ public function getChildren() { if (!is_null($this->dirContent)) { return $this->dirContent; } try { if (!$this->info->isReadable()) { // return 403 instead of 404 because a 404 would make // the caller believe that the collection itself does not exist throw new Forbidden('No read permissions'); } $folderContent = $this->fileView->getDirectoryContent($this->path); } catch (LockedException $e) { throw new Locked(); } $nodes = array(); foreach ($folderContent as $info) { $node = $this->getChild($info->getName(), $info); $nodes[] = $node; } $this->dirContent = $nodes; return $this->dirContent; } /** * Checks if a child exists. * * @param string $name * @return bool */ public function childExists($name) { // note: here we do NOT resolve the chunk file name to the real file name // to make sure we return false when checking for file existence with a chunk // file name. // This is to make sure that "createFile" is still triggered // (required old code) instead of "updateFile". // // TODO: resolve chunk file name here and implement "updateFile" $path = $this->path . '/' . $name; return $this->fileView->file_exists($path); } /** * Deletes all files in this directory, and then itself * * @return void * @throws FileLocked * @throws \Sabre\DAV\Exception\Forbidden */ public function delete() { if ($this->path === '' || $this->path === '/' || !$this->info->isDeletable()) { throw new \Sabre\DAV\Exception\Forbidden(); } try { if (!$this->fileView->rmdir($this->path)) { // assume it wasn't possible to remove due to permission issue throw new \Sabre\DAV\Exception\Forbidden(); } } catch (ForbiddenException $ex) { throw new Forbidden($ex->getMessage(), $ex->getRetry()); } catch (LockedException $e) { throw new FileLocked($e->getMessage(), $e->getCode(), $e); } } /** * Returns available diskspace information * * @return array */ public function getQuotaInfo() { if ($this->quotaInfo) { return $this->quotaInfo; } try { $storageInfo = \OC_Helper::getStorageInfo($this->info->getPath(), $this->info); if ($storageInfo['quota'] === \OCP\Files\FileInfo::SPACE_UNLIMITED) { $free = \OCP\Files\FileInfo::SPACE_UNLIMITED; } else { $free = $storageInfo['free']; } $this->quotaInfo = array( $storageInfo['used'], $free ); return $this->quotaInfo; } catch (\OCP\Files\StorageNotAvailableException $e) { return array(0, 0); } } /** * Moves a node into this collection. * * It is up to the implementors to: * 1. Create the new resource. * 2. Remove the old resource. * 3. Transfer any properties or other data. * * Generally you should make very sure that your collection can easily move * the move. * * If you don't, just return false, which will trigger sabre/dav to handle * the move itself. If you return true from this function, the assumption * is that the move was successful. * * @param string $targetName New local file/collection name. * @param string $fullSourcePath Full path to source node * @param INode $sourceNode Source node itself * @return bool * @throws BadRequest * @throws ServiceUnavailable * @throws Forbidden * @throws FileLocked * @throws \Sabre\DAV\Exception\Forbidden */ public function moveInto($targetName, $fullSourcePath, INode $sourceNode) { if (!$sourceNode instanceof Node) { // it's a file of another kind, like FutureFile if ($sourceNode instanceof IFile) { // fallback to default copy+delete handling return false; } throw new BadRequest('Incompatible node types'); } if (!$this->fileView) { throw new ServiceUnavailable('filesystem not setup'); } $destinationPath = $this->getPath() . '/' . $targetName; $targetNodeExists = $this->childExists($targetName); // at getNodeForPath we also check the path for isForbiddenFileOrDir // with that we have covered both source and destination if ($sourceNode instanceof Directory && $targetNodeExists) { throw new \Sabre\DAV\Exception\Forbidden('Could not copy directory ' . $sourceNode->getName() . ', target exists'); } list($sourceDir,) = \Sabre\HTTP\URLUtil::splitPath($sourceNode->getPath()); $destinationDir = $this->getPath(); $sourcePath = $sourceNode->getPath(); $isMovableMount = false; $sourceMount = \OC::$server->getMountManager()->find($this->fileView->getAbsolutePath($sourcePath)); $internalPath = $sourceMount->getInternalPath($this->fileView->getAbsolutePath($sourcePath)); if ($sourceMount instanceof MoveableMount && $internalPath === '') { $isMovableMount = true; } try { $sameFolder = ($sourceDir === $destinationDir); // if we're overwriting or same folder if ($targetNodeExists || $sameFolder) { // note that renaming a share mount point is always allowed if (!$this->fileView->isUpdatable($destinationDir) && !$isMovableMount) { throw new \Sabre\DAV\Exception\Forbidden(); } } else { if (!$this->fileView->isCreatable($destinationDir)) { throw new \Sabre\DAV\Exception\Forbidden(); } } if (!$sameFolder) { // moving to a different folder, source will be gone, like a deletion // note that moving a share mount point is always allowed if (!$this->fileView->isDeletable($sourcePath) && !$isMovableMount) { throw new \Sabre\DAV\Exception\Forbidden(); } } $fileName = basename($destinationPath); try { $this->fileView->verifyPath($destinationDir, $fileName); } catch (InvalidPathException $ex) { throw new InvalidPath($ex->getMessage()); } $renameOkay = $this->fileView->rename($sourcePath, $destinationPath); if (!$renameOkay) { throw new \Sabre\DAV\Exception\Forbidden(''); } } catch (StorageNotAvailableException $e) { throw new ServiceUnavailable($e->getMessage()); } catch (ForbiddenException $ex) { throw new Forbidden($ex->getMessage(), $ex->getRetry()); } catch (LockedException $e) { throw new FileLocked($e->getMessage(), $e->getCode(), $e); } return true; } } Sabre/DavAclPlugin.php 0000604 00000005306 15247115714 0010631 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use Sabre\CalDAV\Principal\User; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\INode; use \Sabre\DAV\PropFind; /** * Class DavAclPlugin is a wrapper around \Sabre\DAVACL\Plugin that returns 404 * responses in case the resource to a response has been forbidden instead of * a 403. This is used to prevent enumeration of valid resources. * * @see https://github.com/owncloud/core/issues/22578 * @package OCA\DAV\Connector\Sabre */ class DavAclPlugin extends \Sabre\DAVACL\Plugin { public function __construct() { $this->hideNodesFromListings = true; $this->allowUnauthenticatedAccess = false; } function checkPrivileges($uri, $privileges, $recursion = self::R_PARENT, $throwExceptions = true) { $access = parent::checkPrivileges($uri, $privileges, $recursion, false); if($access === false && $throwExceptions) { /** @var INode $node */ $node = $this->server->tree->getNodeForPath($uri); switch(get_class($node)) { case 'OCA\DAV\CardDAV\AddressBook': $type = 'Addressbook'; break; default: $type = 'Node'; break; } throw new NotFound( sprintf( "%s with name '%s' could not be found", $type, $node->getName() ) ); } return $access; } public function propFind(PropFind $propFind, INode $node) { // If the node is neither readable nor writable then fail unless its of // the standard user-principal if(!($node instanceof User)) { $path = $propFind->getPath(); $readPermissions = $this->checkPrivileges($path, '{DAV:}read', self::R_PARENT, false); $writePermissions = $this->checkPrivileges($path, '{DAV:}write', self::R_PARENT, false); if ($readPermissions === false && $writePermissions === false) { $this->checkPrivileges($path, '{DAV:}read', self::R_PARENT, true); $this->checkPrivileges($path, '{DAV:}write', self::R_PARENT, true); } } return parent::propFind($propFind, $node); } } Sabre/CustomPropertiesBackend.php 0000604 00000021710 15247115714 0013114 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Aaron Wood <aaronjwood@gmail.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OCP\IDBConnection; use OCP\IUser; use Sabre\DAV\PropertyStorage\Backend\BackendInterface; use Sabre\DAV\PropFind; use Sabre\DAV\PropPatch; use Sabre\DAV\Tree; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\Exception\ServiceUnavailable; class CustomPropertiesBackend implements BackendInterface { /** * Ignored properties * * @var array */ private $ignoredProperties = array( '{DAV:}getcontentlength', '{DAV:}getcontenttype', '{DAV:}getetag', '{DAV:}quota-used-bytes', '{DAV:}quota-available-bytes', '{DAV:}quota-available-bytes', '{http://owncloud.org/ns}permissions', '{http://owncloud.org/ns}downloadURL', '{http://owncloud.org/ns}dDC', '{http://owncloud.org/ns}size', ); /** * @var Tree */ private $tree; /** * @var IDBConnection */ private $connection; /** * @var IUser */ private $user; /** * Properties cache * * @var array */ private $cache = []; /** * @param Tree $tree node tree * @param IDBConnection $connection database connection * @param IUser $user owner of the tree and properties */ public function __construct( Tree $tree, IDBConnection $connection, IUser $user) { $this->tree = $tree; $this->connection = $connection; $this->user = $user->getUID(); } /** * Fetches properties for a path. * * @param string $path * @param PropFind $propFind * @return void */ public function propFind($path, PropFind $propFind) { try { $node = $this->tree->getNodeForPath($path); if (!($node instanceof Node)) { return; } } catch (ServiceUnavailable $e) { // might happen for unavailable mount points, skip return; } catch (NotFound $e) { // in some rare (buggy) cases the node might not be found, // we catch the exception to prevent breaking the whole list with a 404 // (soft fail) \OC::$server->getLogger()->warning( 'Could not get node for path: \"' . $path . '\" : ' . $e->getMessage(), array('app' => 'files') ); return; } $requestedProps = $propFind->get404Properties(); // these might appear $requestedProps = array_diff( $requestedProps, $this->ignoredProperties ); if (empty($requestedProps)) { return; } if ($node instanceof Directory && $propFind->getDepth() !== 0 ) { // note: pre-fetching only supported for depth <= 1 $this->loadChildrenProperties($node, $requestedProps); } $props = $this->getProperties($node, $requestedProps); foreach ($props as $propName => $propValue) { $propFind->set($propName, $propValue); } } /** * Updates properties for a path * * @param string $path * @param PropPatch $propPatch * * @return void */ public function propPatch($path, PropPatch $propPatch) { $node = $this->tree->getNodeForPath($path); if (!($node instanceof Node)) { return; } $propPatch->handleRemaining(function($changedProps) use ($node) { return $this->updateProperties($node, $changedProps); }); } /** * This method is called after a node is deleted. * * @param string $path path of node for which to delete properties */ public function delete($path) { $statement = $this->connection->prepare( 'DELETE FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?' ); $statement->execute(array($this->user, '/' . $path)); $statement->closeCursor(); unset($this->cache[$path]); } /** * This method is called after a successful MOVE * * @param string $source * @param string $destination * * @return void */ public function move($source, $destination) { $statement = $this->connection->prepare( 'UPDATE `*PREFIX*properties` SET `propertypath` = ?' . ' WHERE `userid` = ? AND `propertypath` = ?' ); $statement->execute(array('/' . $destination, $this->user, '/' . $source)); $statement->closeCursor(); } /** * Returns a list of properties for this nodes.; * @param Node $node * @param array $requestedProperties requested properties or empty array for "all" * @return array * @note The properties list is a list of propertynames the client * requested, encoded as xmlnamespace#tagName, for example: * http://www.example.org/namespace#author If the array is empty, all * properties should be returned */ private function getProperties(Node $node, array $requestedProperties) { $path = $node->getPath(); if (isset($this->cache[$path])) { return $this->cache[$path]; } // TODO: chunking if more than 1000 properties $sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?'; $whereValues = array($this->user, $path); $whereTypes = array(null, null); if (!empty($requestedProperties)) { // request only a subset $sql .= ' AND `propertyname` in (?)'; $whereValues[] = $requestedProperties; $whereTypes[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY; } $result = $this->connection->executeQuery( $sql, $whereValues, $whereTypes ); $props = []; while ($row = $result->fetch()) { $props[$row['propertyname']] = $row['propertyvalue']; } $result->closeCursor(); $this->cache[$path] = $props; return $props; } /** * Update properties * * @param Node $node node for which to update properties * @param array $properties array of properties to update * * @return bool */ private function updateProperties($node, $properties) { $path = $node->getPath(); $deleteStatement = 'DELETE FROM `*PREFIX*properties`' . ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?'; $insertStatement = 'INSERT INTO `*PREFIX*properties`' . ' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)'; $updateStatement = 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' . ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?'; // TODO: use "insert or update" strategy ? $existing = $this->getProperties($node, array()); $this->connection->beginTransaction(); foreach ($properties as $propertyName => $propertyValue) { // If it was null, we need to delete the property if (is_null($propertyValue)) { if (array_key_exists($propertyName, $existing)) { $this->connection->executeUpdate($deleteStatement, array( $this->user, $path, $propertyName ) ); } } else { if (!array_key_exists($propertyName, $existing)) { $this->connection->executeUpdate($insertStatement, array( $this->user, $path, $propertyName, $propertyValue ) ); } else { $this->connection->executeUpdate($updateStatement, array( $propertyValue, $this->user, $path, $propertyName ) ); } } } $this->connection->commit(); unset($this->cache[$path]); return true; } /** * Bulk load properties for directory children * * @param Directory $node * @param array $requestedProperties requested properties * * @return void */ private function loadChildrenProperties(Directory $node, $requestedProperties) { $path = $node->getPath(); if (isset($this->cache[$path])) { // we already loaded them at some point return; } $childNodes = $node->getChildren(); // pre-fill cache foreach ($childNodes as $childNode) { $this->cache[$childNode->getPath()] = []; } $sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` LIKE ?'; $sql .= ' AND `propertyname` in (?) ORDER BY `propertypath`, `propertyname`'; $result = $this->connection->executeQuery( $sql, array($this->user, $this->connection->escapeLikeParameter(rtrim($path, '/')) . '/%', $requestedProperties), array(null, null, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY) ); $oldPath = null; $props = []; while ($row = $result->fetch()) { $path = $row['propertypath']; if ($oldPath !== $path) { // save previously gathered props $this->cache[$oldPath] = $props; $oldPath = $path; // prepare props for next path $props = []; } $props[$row['propertyname']] = $row['propertyvalue']; } if (!is_null($oldPath)) { // save props from last run $this->cache[$oldPath] = $props; } $result->closeCursor(); } } Sabre/FakeLockerPlugin.php 0000604 00000010165 15247115714 0011504 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use Sabre\DAV\Locks\LockInfo; use Sabre\DAV\ServerPlugin; use Sabre\DAV\Xml\Property\LockDiscovery; use Sabre\DAV\Xml\Property\SupportedLock; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; use Sabre\DAV\PropFind; use Sabre\DAV\INode; /** * Class FakeLockerPlugin is a plugin only used when connections come in from * OS X via Finder. The fake locking plugin does emulate Class 2 WebDAV support * (locking of files) which allows Finder to access the storage in write mode as * well. * * No real locking is performed, instead the plugin just returns always positive * responses. * * @see https://github.com/owncloud/core/issues/17732 * @package OCA\DAV\Connector\Sabre */ class FakeLockerPlugin extends ServerPlugin { /** @var \Sabre\DAV\Server */ private $server; /** {@inheritDoc} */ public function initialize(\Sabre\DAV\Server $server) { $this->server = $server; $this->server->on('method:LOCK', [$this, 'fakeLockProvider'], 1); $this->server->on('method:UNLOCK', [$this, 'fakeUnlockProvider'], 1); $server->on('propFind', [$this, 'propFind']); $server->on('validateTokens', [$this, 'validateTokens']); } /** * Indicate that we support LOCK and UNLOCK * * @param string $path * @return string[] */ public function getHTTPMethods($path) { return [ 'LOCK', 'UNLOCK', ]; } /** * Indicate that we support locking * * @return integer[] */ function getFeatures() { return [2]; } /** * Return some dummy response for PROPFIND requests with regard to locking * * @param PropFind $propFind * @param INode $node * @return void */ function propFind(PropFind $propFind, INode $node) { $propFind->handle('{DAV:}supportedlock', function() { return new SupportedLock(true); }); $propFind->handle('{DAV:}lockdiscovery', function() use ($propFind) { return new LockDiscovery([]); }); } /** * Mark a locking token always as valid * * @param RequestInterface $request * @param array $conditions */ public function validateTokens(RequestInterface $request, &$conditions) { foreach($conditions as &$fileCondition) { if(isset($fileCondition['tokens'])) { foreach($fileCondition['tokens'] as &$token) { if(isset($token['token'])) { if(substr($token['token'], 0, 16) === 'opaquelocktoken:') { $token['validToken'] = true; } } } } } } /** * Fakes a successful LOCK * * @param RequestInterface $request * @param ResponseInterface $response * @return bool */ public function fakeLockProvider(RequestInterface $request, ResponseInterface $response) { $lockInfo = new LockInfo(); $lockInfo->token = md5($request->getPath()); $lockInfo->uri = $request->getPath(); $lockInfo->depth = \Sabre\DAV\Server::DEPTH_INFINITY; $lockInfo->timeout = 1800; $body = $this->server->xml->write('{DAV:}prop', [ '{DAV:}lockdiscovery' => new LockDiscovery([$lockInfo]) ]); $response->setStatus(200); $response->setBody($body); return false; } /** * Fakes a successful LOCK * * @param RequestInterface $request * @param ResponseInterface $response * @return bool */ public function fakeUnlockProvider(RequestInterface $request, ResponseInterface $response) { $response->setStatus(204); $response->setHeader('Content-Length', '0'); return false; } } Sabre/AppEnabledPlugin.php 0000604 00000004322 15247115714 0011467 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector\Sabre; use OCP\App\IAppManager; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\ServerPlugin; /** * Plugin to check if an app is enabled for the current user */ class AppEnabledPlugin extends ServerPlugin { /** * Reference to main server object * * @var \Sabre\DAV\Server */ private $server; /** * @var string */ private $app; /** * @var \OCP\App\IAppManager */ private $appManager; /** * @param string $app * @param \OCP\App\IAppManager $appManager */ public function __construct($app, IAppManager $appManager) { $this->app = $app; $this->appManager = $appManager; } /** * This initializes the plugin. * * This function is called by \Sabre\DAV\Server, after * addPlugin is called. * * This method should set up the required event subscriptions. * * @param \Sabre\DAV\Server $server * @return void */ public function initialize(\Sabre\DAV\Server $server) { $this->server = $server; $this->server->on('beforeMethod', array($this, 'checkAppEnabled'), 30); } /** * This method is called before any HTTP after auth and checks if the user has access to the app * * @throws \Sabre\DAV\Exception\Forbidden * @return bool */ public function checkAppEnabled() { if (!$this->appManager->isEnabledForUser($this->app)) { throw new Forbidden(); } } } PublicAuth.php 0000604 00000007051 15247115714 0007323 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Björn Schießle <bjoern@schiessle.org> * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Morris Jobke <hey@morrisjobke.de> * @author Robin Appelman <robin@icewind.nl> * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Thomas Müller <thomas.mueller@tmit.eu> * @author Vincent Petry <pvince81@owncloud.com> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector; use OCP\IRequest; use OCP\ISession; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager; use Sabre\DAV\Auth\Backend\AbstractBasic; /** * Class PublicAuth * * @package OCA\DAV\Connector */ class PublicAuth extends AbstractBasic { /** @var \OCP\Share\IShare */ private $share; /** @var IManager */ private $shareManager; /** @var ISession */ private $session; /** @var IRequest */ private $request; /** * @param IRequest $request * @param IManager $shareManager * @param ISession $session */ public function __construct(IRequest $request, IManager $shareManager, ISession $session) { $this->request = $request; $this->shareManager = $shareManager; $this->session = $session; // setup realm $defaults = new \OCP\Defaults(); $this->realm = $defaults->getName(); } /** * Validates a username and password * * This method should return true or false depending on if login * succeeded. * * @param string $username * @param string $password * * @return bool * @throws \Sabre\DAV\Exception\NotAuthenticated */ protected function validateUserPass($username, $password) { try { $share = $this->shareManager->getShareByToken($username); } catch (ShareNotFound $e) { return false; } $this->share = $share; \OC_User::setIncognitoMode(true); // check if the share is password protected if ($share->getPassword() !== null) { if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK || $share->getShareType() === \OCP\Share::SHARE_TYPE_EMAIL) { if ($this->shareManager->checkPassword($share, $password)) { return true; } else if ($this->session->exists('public_link_authenticated') && $this->session->get('public_link_authenticated') === (string)$share->getId()) { return true; } else { if (in_array('XMLHttpRequest', explode(',', $this->request->getHeader('X-Requested-With')))) { // do not re-authenticate over ajax, use dummy auth name to prevent browser popup http_response_code(401); header('WWW-Authenticate','DummyBasic realm="' . $this->realm . '"'); throw new \Sabre\DAV\Exception\NotAuthenticated('Cannot authenticate over ajax calls'); } return false; } } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_REMOTE) { return true; } else { return false; } } else { return true; } } /** * @return \OCP\Share\IShare */ public function getShare() { return $this->share; } } LegacyDAVACL.php 0000604 00000003753 15247115714 0007347 0 ustar 00 <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. * * @author Joas Schilling <coding@schilljs.com> * @author Lukas Reschke <lukas@statuscode.ch> * @author Thomas Müller <thomas.mueller@tmit.eu> * * @license AGPL-3.0 * * This code is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License, version 3, * along with this program. If not, see <http://www.gnu.org/licenses/> * */ namespace OCA\DAV\Connector; use OCA\DAV\Connector\Sabre\DavAclPlugin; use Sabre\DAV\INode; use Sabre\DAV\PropFind; use Sabre\HTTP\URLUtil; use Sabre\DAVACL\Xml\Property\Principal; class LegacyDAVACL extends DavAclPlugin { /** * @inheritdoc */ public function getCurrentUserPrincipals() { $principalV2 = $this->getCurrentUserPrincipal(); if (is_null($principalV2)) return []; $principalV1 = $this->convertPrincipal($principalV2, false); return array_merge( [ $principalV2, $principalV1 ], $this->getPrincipalMembership($principalV1) ); } private function convertPrincipal($principal, $toV2) { list(, $name) = URLUtil::splitPath($principal); if ($toV2) { return "principals/users/$name"; } return "principals/$name"; } public function propFind(PropFind $propFind, INode $node) { /* Overload current-user-principal */ $propFind->handle('{DAV:}current-user-principal', function () { if ($url = parent::getCurrentUserPrincipal()) { return new Principal(Principal::HREF, $url . '/'); } else { return new Principal(Principal::UNAUTHENTICATED); } }); return parent::propFind($propFind, $node); } }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0 |
proxy
|
phpinfo
|
Settings