diff options
Diffstat (limited to 'lib/private')
79 files changed, 1990 insertions, 637 deletions
diff --git a/lib/private/AllConfig.php b/lib/private/AllConfig.php index 52d77bf3f52..af26d30d8e9 100644 --- a/lib/private/AllConfig.php +++ b/lib/private/AllConfig.php @@ -215,6 +215,25 @@ class AllConfig implements \OCP\IConfig { // TODO - FIXME $this->fixDIInit(); + if (isset($this->userCache[$userId][$appName][$key])) { + if ($this->userCache[$userId][$appName][$key] === (string)$value) { + return; + } else if ($preCondition !== null && $this->userCache[$userId][$appName][$key] !== (string)$preCondition) { + return; + } else { + $qb = $this->connection->getQueryBuilder(); + $qb->update('preferences') + ->set('configvalue', $qb->createNamedParameter($value)) + ->where($qb->expr()->eq('userid', $qb->createNamedParameter($userId))) + ->andWhere($qb->expr()->eq('appid', $qb->createNamedParameter($appName))) + ->andWhere($qb->expr()->eq('configkey', $qb->createNamedParameter($key))); + $qb->execute(); + + $this->userCache[$userId][$appName][$key] = $value; + return; + } + } + $preconditionArray = []; if (isset($preCondition)) { $preconditionArray = [ diff --git a/lib/private/App/DependencyAnalyzer.php b/lib/private/App/DependencyAnalyzer.php index 7adb5d1c574..67268981e99 100644 --- a/lib/private/App/DependencyAnalyzer.php +++ b/lib/private/App/DependencyAnalyzer.php @@ -197,6 +197,9 @@ class DependencyAnalyzer { if (!is_array($commands)) { $commands = array($commands); } + if (isset($commands['@value'])) { + $commands = [$commands]; + } $os = $this->platform->getOS(); foreach ($commands as $command) { if (isset($command['@attributes']['os']) && $command['@attributes']['os'] !== $os) { @@ -224,6 +227,9 @@ class DependencyAnalyzer { if (!is_array($libs)) { $libs = array($libs); } + if (isset($libs['@value'])) { + $libs = [$libs]; + } foreach ($libs as $lib) { $libName = $this->getValue($lib); $libVersion = $this->platform->getLibraryVersion($libName); diff --git a/lib/private/App/InfoParser.php b/lib/private/App/InfoParser.php index e975ad6f096..44f495534c9 100644 --- a/lib/private/App/InfoParser.php +++ b/lib/private/App/InfoParser.php @@ -1,6 +1,7 @@ <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. + * @copyright Copyright (c) 2016, Lukas Reschke <lukas@statuscode.ch> * * @author Andreas Fischer <bantu@owncloud.com> * @author Christoph Wurst <christoph@owncloud.com> @@ -26,18 +27,17 @@ namespace OC\App; -use OCP\IURLGenerator; +use OCP\ICache; class InfoParser { - - /** @var IURLGenerator */ - private $urlGenerator; + /** @var \OCP\ICache|null */ + private $cache; /** - * @param IURLGenerator $urlGenerator + * @param ICache|null $cache */ - public function __construct(IURLGenerator $urlGenerator) { - $this->urlGenerator = $urlGenerator; + public function __construct(ICache $cache = null) { + $this->cache = $cache; } /** @@ -49,18 +49,28 @@ class InfoParser { return null; } + if(!is_null($this->cache)) { + $fileCacheKey = $file . filemtime($file); + if ($cachedValue = $this->cache->get($fileCacheKey)) { + return json_decode($cachedValue, true); + } + } + libxml_use_internal_errors(true); $loadEntities = libxml_disable_entity_loader(false); $xml = simplexml_load_file($file); + libxml_disable_entity_loader($loadEntities); - if ($xml == false) { + if ($xml === false) { libxml_clear_errors(); return null; } $array = $this->xmlToArray($xml); + if (is_null($array)) { return null; } + if (!array_key_exists('info', $array)) { $array['info'] = []; } @@ -97,18 +107,10 @@ class InfoParser { if (!array_key_exists('two-factor-providers', $array)) { $array['two-factor-providers'] = []; } - - if (array_key_exists('documentation', $array) && is_array($array['documentation'])) { - foreach ($array['documentation'] as $key => $url) { - // If it is not an absolute URL we assume it is a key - // i.e. admin-ldap will get converted to go.php?to=admin-ldap - if (!$this->isHTTPURL($url)) { - $url = $this->urlGenerator->linkToDocs($url); - } - - $array['documentation'][$key] = $url; - } + if (!array_key_exists('commands', $array)) { + $array['commands'] = []; } + if (array_key_exists('types', $array)) { if (is_array($array['types'])) { foreach ($array['types'] as $type => $v) { @@ -139,6 +141,13 @@ class InfoParser { if (isset($array['background-jobs']['job']) && is_array($array['background-jobs']['job'])) { $array['background-jobs'] = $array['background-jobs']['job']; } + if (isset($array['commands']['command']) && is_array($array['commands']['command'])) { + $array['commands'] = $array['commands']['command']; + } + + if(!is_null($this->cache)) { + $this->cache->set($fileCacheKey, json_encode($array)); + } return $array; } @@ -193,8 +202,4 @@ class InfoParser { return $array; } - - private function isHTTPURL($url) { - return stripos($url, 'https://') === 0 || stripos($url, 'http://') === 0; - } } diff --git a/lib/private/AppFramework/App.php b/lib/private/AppFramework/App.php index 427a850f396..e15e4a797ea 100644 --- a/lib/private/AppFramework/App.php +++ b/lib/private/AppFramework/App.php @@ -59,24 +59,11 @@ class App { return $topNamespace . self::$nameSpaceCache[$appId]; } - // first try to parse the app's appinfo/info.xml <namespace> tag - $appPath = OC_App::getAppPath($appId); - if ($appPath !== false) { - $filePath = "$appPath/appinfo/info.xml"; - if (is_file($filePath)) { - $loadEntities = libxml_disable_entity_loader(false); - $xml = @simplexml_load_file($filePath); - libxml_disable_entity_loader($loadEntities); - if ($xml) { - $result = $xml->xpath('/info/namespace'); - if ($result && count($result) > 0) { - self::$nameSpaceCache[$appId] = trim((string) $result[0]); - // take first namespace result - return $topNamespace . self::$nameSpaceCache[$appId]; - } - } - } + $appInfo = \OC_App::getAppInfo($appId); + if (isset($appInfo['namespace'])) { + return $topNamespace . trim($appInfo['namespace']); } + // if the tag is not found, fall back to uppercasing the first letter self::$nameSpaceCache[$appId] = ucfirst($appId); return $topNamespace . self::$nameSpaceCache[$appId]; diff --git a/lib/private/AppFramework/Db/Db.php b/lib/private/AppFramework/Db/Db.php index 5fea09747af..450549ffdbb 100644 --- a/lib/private/AppFramework/Db/Db.php +++ b/lib/private/AppFramework/Db/Db.php @@ -31,6 +31,7 @@ use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDb; use OCP\IDBConnection; use OCP\PreConditionNotMetException; +use Doctrine\DBAL\Platforms\MySqlPlatform; /** * @deprecated use IDBConnection directly, will be removed in ownCloud 10 @@ -300,4 +301,14 @@ class Db implements IDb { public function escapeLikeParameter($param) { return $this->connection->escapeLikeParameter($param); } + + /** + * Check whether or not the current database support 4byte wide unicode + * + * @return bool + * @since 9.2.0 + */ + public function supports4ByteText() { + return $this->connection->supports4ByteText(); + } } diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index 20351d1321c..8fe9b4dca03 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -46,7 +46,8 @@ use OC\AppFramework\Utility\SimpleContainer; use OC\Core\Middleware\TwoFactorMiddleware; use OCP\AppFramework\IApi; use OCP\AppFramework\IAppContainer; - +use OCP\Files\IAppData; +use OCP\Files\Mount\IMountManager; class DIContainer extends SimpleContainer implements IAppContainer { @@ -160,10 +161,18 @@ class DIContainer extends SimpleContainer implements IAppContainer { return $this->getServer()->getRootFolder(); }); + $this->registerService('OCP\\Files\\Folder', function() { + return $this->getServer()->getUserFolder(); + }); + $this->registerService('OCP\\Http\\Client\\IClientService', function($c) { return $this->getServer()->getHTTPClientService(); }); + $this->registerService(IAppData::class, function (SimpleContainer $c) { + return $this->getServer()->getAppDataDir($c->query('AppName')); + }); + $this->registerService('OCP\\IGroupManager', function($c) { return $this->getServer()->getGroupManager(); }); @@ -304,6 +313,9 @@ class DIContainer extends SimpleContainer implements IAppContainer { $this->registerService('OCP\\AppFramework\\IAppContainer', function ($c) { return $c; }); + $this->registerService(IMountManager::class, function () { + return $this->getServer()->getMountManager(); + }); // commonly used attributes $this->registerService('UserId', function ($c) { @@ -367,7 +379,9 @@ class DIContainer extends SimpleContainer implements IAppContainer { $c['AppName'], $app->isLoggedIn(), $app->isAdminUser(), - $app->getServer()->getContentSecurityPolicyManager() + $app->getServer()->getContentSecurityPolicyManager(), + $app->getServer()->getCsrfTokenManager(), + $app->getServer()->getContentSecurityPolicyNonceManager() ); }); diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php index 3bfef2df025..183e55740ea 100644 --- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php @@ -36,7 +36,10 @@ use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException; use OC\AppFramework\Middleware\Security\Exceptions\StrictCookieMissingException; use OC\AppFramework\Utility\ControllerMethodReflector; use OC\Security\CSP\ContentSecurityPolicyManager; +use OC\Security\CSP\ContentSecurityPolicyNonceManager; +use OC\Security\CSRF\CsrfTokenManager; use OCP\AppFramework\Http\ContentSecurityPolicy; +use OCP\AppFramework\Http\EmptyContentSecurityPolicy; use OCP\AppFramework\Http\RedirectResponse; use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Middleware; @@ -76,6 +79,10 @@ class SecurityMiddleware extends Middleware { private $isAdminUser; /** @var ContentSecurityPolicyManager */ private $contentSecurityPolicyManager; + /** @var CsrfTokenManager */ + private $csrfTokenManager; + /** @var ContentSecurityPolicyNonceManager */ + private $cspNonceManager; /** * @param IRequest $request @@ -87,6 +94,8 @@ class SecurityMiddleware extends Middleware { * @param bool $isLoggedIn * @param bool $isAdminUser * @param ContentSecurityPolicyManager $contentSecurityPolicyManager + * @param CSRFTokenManager $csrfTokenManager + * @param ContentSecurityPolicyNonceManager $cspNonceManager */ public function __construct(IRequest $request, ControllerMethodReflector $reflector, @@ -96,7 +105,9 @@ class SecurityMiddleware extends Middleware { $appName, $isLoggedIn, $isAdminUser, - ContentSecurityPolicyManager $contentSecurityPolicyManager) { + ContentSecurityPolicyManager $contentSecurityPolicyManager, + CsrfTokenManager $csrfTokenManager, + ContentSecurityPolicyNonceManager $cspNonceManager) { $this->navigationManager = $navigationManager; $this->request = $request; $this->reflector = $reflector; @@ -106,6 +117,8 @@ class SecurityMiddleware extends Middleware { $this->isLoggedIn = $isLoggedIn; $this->isAdminUser = $isAdminUser; $this->contentSecurityPolicyManager = $contentSecurityPolicyManager; + $this->csrfTokenManager = $csrfTokenManager; + $this->cspNonceManager = $cspNonceManager; } @@ -182,9 +195,17 @@ class SecurityMiddleware extends Middleware { public function afterController($controller, $methodName, Response $response) { $policy = !is_null($response->getContentSecurityPolicy()) ? $response->getContentSecurityPolicy() : new ContentSecurityPolicy(); + if (get_class($policy) === EmptyContentSecurityPolicy::class) { + return $response; + } + $defaultPolicy = $this->contentSecurityPolicyManager->getDefaultPolicy(); $defaultPolicy = $this->contentSecurityPolicyManager->mergePolicies($defaultPolicy, $policy); + if($this->cspNonceManager->browserSupportsCspV3()) { + $defaultPolicy->useJsNonce($this->csrfTokenManager->getToken()->getEncryptedValue()); + } + $response->setContentSecurityPolicy($defaultPolicy); return $response; diff --git a/lib/private/AppFramework/OCS/BaseResponse.php b/lib/private/AppFramework/OCS/BaseResponse.php index fa22498ac0f..59b8660a382 100644 --- a/lib/private/AppFramework/OCS/BaseResponse.php +++ b/lib/private/AppFramework/OCS/BaseResponse.php @@ -23,6 +23,7 @@ namespace OC\AppFramework\OCS; use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Http\EmptyContentSecurityPolicy; use OCP\AppFramework\Http\Response; abstract class BaseResponse extends Response { @@ -67,7 +68,7 @@ abstract class BaseResponse extends Response { $this->setETag($dataResponse->getETag()); $this->setLastModified($dataResponse->getLastModified()); $this->setCookies($dataResponse->getCookies()); - $this->setContentSecurityPolicy($dataResponse->getContentSecurityPolicy()); + $this->setContentSecurityPolicy(new EmptyContentSecurityPolicy()); if ($format === 'json') { $this->addHeader( diff --git a/lib/private/AppFramework/Routing/RouteConfig.php b/lib/private/AppFramework/Routing/RouteConfig.php index e94f2e50c1d..70208725f46 100644 --- a/lib/private/AppFramework/Routing/RouteConfig.php +++ b/lib/private/AppFramework/Routing/RouteConfig.php @@ -36,14 +36,25 @@ use OCP\Route\IRouter; * @package OC\AppFramework\routing */ class RouteConfig { + /** @var DIContainer */ private $container; + + /** @var IRouter */ private $router; + + /** @var array */ private $routes; + + /** @var string */ private $appName; + /** @var string[] */ + private $controllerNameCache = []; + /** * @param \OC\AppFramework\DependencyInjection\DIContainer $container * @param \OCP\Route\IRouter $router + * @param array $routes * @internal param $appName */ public function __construct(DIContainer $container, IRouter $router, $routes) { @@ -234,7 +245,10 @@ class RouteConfig { */ private function buildControllerName($controller) { - return $this->underScoreToCamelCase(ucfirst($controller)) . 'Controller'; + if (!isset($this->controllerNameCache[$controller])) { + $this->controllerNameCache[$controller] = $this->underScoreToCamelCase(ucfirst($controller)) . 'Controller'; + } + return $this->controllerNameCache[$controller]; } /** diff --git a/lib/private/AppFramework/Utility/SimpleContainer.php b/lib/private/AppFramework/Utility/SimpleContainer.php index 5f637518f4d..889f17cbc6a 100644 --- a/lib/private/AppFramework/Utility/SimpleContainer.php +++ b/lib/private/AppFramework/Utility/SimpleContainer.php @@ -62,7 +62,19 @@ class SimpleContainer extends Container implements IContainer { $resolveName = $parameterClass->name; } - $parameters[] = $this->query($resolveName); + try { + $parameters[] = $this->query($resolveName); + } catch (\Exception $e) { + // Service not found, use the default value when available + if ($parameter->isDefaultValueAvailable()) { + $parameters[] = $parameter->getDefaultValue(); + } else if ($parameterClass !== null) { + $resolveName = $parameter->getName(); + $parameters[] = $this->query($resolveName); + } else { + throw $e; + } + } } return $class->newInstanceArgs($parameters); } @@ -157,7 +169,10 @@ class SimpleContainer extends Container implements IContainer { * @return string */ protected function sanitizeName($name) { - return ltrim($name, '\\'); + if (isset($name[0]) && $name[0] === '\\') { + return ltrim($name, '\\'); + } + return $name; } } diff --git a/lib/private/Avatar.php b/lib/private/Avatar.php index 9e8bd0136c2..fc1909c3bda 100644 --- a/lib/private/Avatar.php +++ b/lib/private/Avatar.php @@ -29,10 +29,10 @@ namespace OC; use OC\User\User; -use OCP\Files\Folder; -use OCP\Files\File; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; +use OCP\Files\SimpleFS\ISimpleFile; +use OCP\Files\SimpleFS\ISimpleFolder; use OCP\IAvatar; use OCP\IConfig; use OCP\IImage; @@ -45,7 +45,7 @@ use OCP\ILogger; */ class Avatar implements IAvatar { - /** @var Folder */ + /** @var ISimpleFolder */ private $folder; /** @var IL10N */ private $l; @@ -59,13 +59,13 @@ class Avatar implements IAvatar { /** * constructor * - * @param Folder $folder The folder where the avatars are + * @param ISimpleFolder $folder The folder where the avatars are * @param IL10N $l * @param User $user * @param ILogger $logger * @param IConfig $config */ - public function __construct(Folder $folder, + public function __construct(ISimpleFolder $folder, IL10N $l, $user, ILogger $logger, @@ -98,7 +98,8 @@ class Avatar implements IAvatar { * @return bool */ public function exists() { - return $this->folder->nodeExists('avatar.jpg') || $this->folder->nodeExists('avatar.png'); + + return $this->folder->fileExists('avatar.jpg') || $this->folder->fileExists('avatar.png'); } /** @@ -130,7 +131,7 @@ class Avatar implements IAvatar { } if (!($img->height() === $img->width())) { - throw new NotSquareException(); + throw new NotSquareException($this->l->t("Avatar image is not square")); } $this->remove(); @@ -170,15 +171,15 @@ class Avatar implements IAvatar { } try { - $file = $this->folder->get($path); + $file = $this->folder->getFile($path); } catch (NotFoundException $e) { if ($size <= 0) { throw new NotFoundException; } $avatar = new OC_Image(); - /** @var File $file */ - $file = $this->folder->get('avatar.' . $ext); + /** @var ISimpleFile $file */ + $file = $this->folder->getFile('avatar.' . $ext); $avatar->loadFromData($file->getContent()); if ($size !== -1) { $avatar->resize($size); @@ -201,9 +202,9 @@ class Avatar implements IAvatar { * @throws NotFoundException */ private function getExtension() { - if ($this->folder->nodeExists('avatar.jpg')) { + if ($this->folder->fileExists('avatar.jpg')) { return 'jpg'; - } elseif ($this->folder->nodeExists('avatar.png')) { + } elseif ($this->folder->fileExists('avatar.png')) { return 'png'; } throw new NotFoundException; diff --git a/lib/private/AvatarManager.php b/lib/private/AvatarManager.php index 0eabc3a1754..b8c6c2a1eb6 100644 --- a/lib/private/AvatarManager.php +++ b/lib/private/AvatarManager.php @@ -27,13 +27,12 @@ namespace OC; -use OCP\Files\Folder; +use OCP\Files\IAppData; use OCP\Files\NotFoundException; use OCP\IAvatarManager; use OCP\IConfig; use OCP\ILogger; use OCP\IUserManager; -use OCP\Files\IRootFolder; use OCP\IL10N; /** @@ -44,8 +43,8 @@ class AvatarManager implements IAvatarManager { /** @var IUserManager */ private $userManager; - /** @var IRootFolder */ - private $rootFolder; + /** @var IAppData */ + private $appData; /** @var IL10N */ private $l; @@ -60,19 +59,19 @@ class AvatarManager implements IAvatarManager { * AvatarManager constructor. * * @param IUserManager $userManager - * @param IRootFolder $rootFolder + * @param IAppData $appData * @param IL10N $l * @param ILogger $logger * @param IConfig $config */ public function __construct( IUserManager $userManager, - IRootFolder $rootFolder, + IAppData $appData, IL10N $l, ILogger $logger, IConfig $config) { $this->userManager = $userManager; - $this->rootFolder = $rootFolder; + $this->appData = $appData; $this->l = $l; $this->logger = $logger; $this->config = $config; @@ -92,14 +91,14 @@ class AvatarManager implements IAvatarManager { throw new \Exception('user does not exist'); } - /* - * Fix for #22119 - * Basically we do not want to copy the skeleton folder - */ - \OC\Files\Filesystem::initMountPoints($userId); - $dir = '/' . $userId; - /** @var Folder $folder */ - $folder = $this->rootFolder->get($dir); + // sanitize userID - fixes casing issue (needed for the filesystem stuff that is done below) + $userId = $user->getUID(); + + try { + $folder = $this->appData->getFolder($userId); + } catch (NotFoundException $e) { + $folder = $this->appData->newFolder($userId); + } return new Avatar($folder, $this->l, $user, $this->logger, $this->config); } diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index 59678775d88..b3ecab731e1 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -26,6 +26,7 @@ namespace OC\Comments; use Doctrine\DBAL\Exception\DriverException; use OCP\Comments\CommentsEvent; use OCP\Comments\IComment; +use OCP\Comments\ICommentsEventHandler; use OCP\Comments\ICommentsManager; use OCP\Comments\NotFoundException; use OCP\DB\QueryBuilder\IQueryBuilder; @@ -33,7 +34,6 @@ use OCP\IDBConnection; use OCP\IConfig; use OCP\ILogger; use OCP\IUser; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; class Manager implements ICommentsManager { @@ -46,30 +46,30 @@ class Manager implements ICommentsManager { /** @var IConfig */ protected $config; - /** @var EventDispatcherInterface */ - protected $dispatcher; - /** @var IComment[] */ protected $commentsCache = []; + /** @var \Closure[] */ + protected $eventHandlerClosures = []; + + /** @var ICommentsEventHandler[] */ + protected $eventHandlers = []; + /** * Manager constructor. * * @param IDBConnection $dbConn * @param ILogger $logger * @param IConfig $config - * @param EventDispatcherInterface $dispatcher */ public function __construct( IDBConnection $dbConn, ILogger $logger, - IConfig $config, - EventDispatcherInterface $dispatcher + IConfig $config ) { $this->dbConn = $dbConn; $this->logger = $logger; $this->config = $config; - $this->dispatcher = $dispatcher; } /** @@ -455,10 +455,7 @@ class Manager implements ICommentsManager { } if ($affectedRows > 0 && $comment instanceof IComment) { - $this->dispatcher->dispatch(CommentsEvent::EVENT_DELETE, new CommentsEvent( - CommentsEvent::EVENT_DELETE, - $comment - )); + $this->sendEvent(CommentsEvent::EVENT_DELETE, $comment); } return ($affectedRows > 0); @@ -525,13 +522,9 @@ class Manager implements ICommentsManager { if ($affectedRows > 0) { $comment->setId(strval($qb->getLastInsertId())); + $this->sendEvent(CommentsEvent::EVENT_ADD, $comment); } - $this->dispatcher->dispatch(CommentsEvent::EVENT_ADD, new CommentsEvent( - CommentsEvent::EVENT_ADD, - $comment - )); - return $affectedRows > 0; } @@ -543,6 +536,12 @@ class Manager implements ICommentsManager { * @throws NotFoundException */ protected function update(IComment $comment) { + // for properly working preUpdate Events we need the old comments as is + // in the DB and overcome caching. Also avoid that outdated information stays. + $this->uncache($comment->getId()); + $this->sendEvent(CommentsEvent::EVENT_PRE_UPDATE, $this->get($comment->getId())); + $this->uncache($comment->getId()); + $qb = $this->dbConn->getQueryBuilder(); $affectedRows = $qb ->update('comments') @@ -565,10 +564,7 @@ class Manager implements ICommentsManager { throw new NotFoundException('Comment to update does ceased to exist'); } - $this->dispatcher->dispatch(CommentsEvent::EVENT_UPDATE, new CommentsEvent( - CommentsEvent::EVENT_UPDATE, - $comment - )); + $this->sendEvent(CommentsEvent::EVENT_UPDATE, $comment); return $affectedRows > 0; } @@ -751,4 +747,51 @@ class Manager implements ICommentsManager { } return ($affectedRows > 0); } + + /** + * registers an Entity to the manager, so event notifications can be send + * to consumers of the comments infrastructure + * + * @param \Closure $closure + */ + public function registerEventHandler(\Closure $closure) { + $this->eventHandlerClosures[] = $closure; + $this->eventHandlers = []; + } + + /** + * returns valid, registered entities + * + * @return \OCP\Comments\ICommentsEventHandler[] + */ + private function getEventHandlers() { + if(!empty($this->eventHandlers)) { + return $this->eventHandlers; + } + + $this->eventHandlers = []; + foreach ($this->eventHandlerClosures as $name => $closure) { + $entity = $closure(); + if (!($entity instanceof ICommentsEventHandler)) { + throw new \InvalidArgumentException('The given entity does not implement the ICommentsEntity interface'); + } + $this->eventHandlers[$name] = $entity; + } + + return $this->eventHandlers; + } + + /** + * sends notifications to the registered entities + * + * @param $eventType + * @param IComment $comment + */ + private function sendEvent($eventType, IComment $comment) { + $entities = $this->getEventHandlers(); + $event = new CommentsEvent($eventType, $comment); + foreach ($entities as $entity) { + $entity->handle($event); + } + } } diff --git a/lib/private/Console/Application.php b/lib/private/Console/Application.php index cad16a070c8..cd76b43f095 100644 --- a/lib/private/Console/Application.php +++ b/lib/private/Console/Application.php @@ -26,13 +26,13 @@ */ namespace OC\Console; +use OC\NeedsUpdateException; use OC_App; +use OCP\AppFramework\QueryException; use OCP\Console\ConsoleEvent; -use OCP\Defaults; use OCP\IConfig; use OCP\IRequest; use Symfony\Component\Console\Application as SymfonyApplication; -use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -85,31 +85,45 @@ class Application { if ($input->getOption('no-warnings')) { $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); } - require_once __DIR__ . '/../../../core/register_command.php'; - if ($this->config->getSystemValue('installed', false)) { - if (\OCP\Util::needUpgrade()) { - $output->writeln("Nextcloud or one of the apps require upgrade - only a limited number of commands are available"); - $output->writeln("You may use your browser or the occ upgrade command to do the upgrade"); - } elseif ($this->config->getSystemValue('maintenance', false)) { - $output->writeln("Nextcloud is in maintenance mode - no app have been loaded"); - } else { - OC_App::loadApps(); - foreach (\OC::$server->getAppManager()->getInstalledApps() as $app) { - $appPath = \OC_App::getAppPath($app); - if($appPath === false) { - continue; + try { + require_once __DIR__ . '/../../../core/register_command.php'; + if ($this->config->getSystemValue('installed', false)) { + if (\OCP\Util::needUpgrade()) { + throw new NeedsUpdateException(); + } elseif ($this->config->getSystemValue('maintenance', false)) { + if ($input->getArgument('command') !== '_completion') { + $output->writeln("Nextcloud is in maintenance mode - no apps have been loaded"); } - \OC_App::registerAutoloading($app, $appPath); - $file = $appPath . '/appinfo/register_command.php'; - if (file_exists($file)) { - require $file; + } else { + OC_App::loadApps(); + foreach (\OC::$server->getAppManager()->getInstalledApps() as $app) { + $appPath = \OC_App::getAppPath($app); + if ($appPath === false) { + continue; + } + // load commands using info.xml + $info = \OC_App::getAppInfo($app); + if (isset($info['commands'])) { + $this->loadCommandsFromInfoXml($info['commands']); + } + // load from register_command.php + \OC_App::registerAutoloading($app, $appPath); + $file = $appPath . '/appinfo/register_command.php'; + if (file_exists($file)) { + require $file; + } } } + } else if ($input->getArgument('command') !== '_completion') { + $output->writeln("Nextcloud is not installed - only a limited number of commands are available"); + } + } catch(NeedsUpdateException $e) { + if ($input->getArgument('command') !== '_completion') { + $output->writeln("Nextcloud or one of the apps require upgrade - only a limited number of commands are available"); + $output->writeln("You may use your browser or the occ upgrade command to do the upgrade"); } - } else { - $output->writeln("Nextcloud is not installed - only a limited number of commands are available"); } - $input = new ArgvInput(); + if ($input->getFirstArgument() !== 'check') { $errors = \OC_Util::checkServer(\OC::$server->getConfig()); if (!empty($errors)) { @@ -145,4 +159,20 @@ class Application { )); return $this->application->run($input, $output); } + + private function loadCommandsFromInfoXml($commands) { + foreach ($commands as $command) { + try { + $c = \OC::$server->query($command); + } catch (QueryException $e) { + if (class_exists($command)) { + $c = new $command(); + } else { + throw new \Exception("Console command '$command' is unknown and could not be loaded"); + } + } + + $this->application->add($c); + } + } } diff --git a/lib/private/DB/AdapterMySQL.php b/lib/private/DB/AdapterMySQL.php index 3e2fceda8db..aa784bb83dc 100644 --- a/lib/private/DB/AdapterMySQL.php +++ b/lib/private/DB/AdapterMySQL.php @@ -27,6 +27,9 @@ namespace OC\DB; class AdapterMySQL extends Adapter { + /** @var string */ + protected $charset; + /** * @param string $tableName */ @@ -39,7 +42,16 @@ class AdapterMySQL extends Adapter { } public function fixupStatement($statement) { - $statement = str_replace(' ILIKE ', ' COLLATE utf8_general_ci LIKE ', $statement); + $statement = str_replace(' ILIKE ', ' COLLATE ' . $this->getCharset() . '_general_ci LIKE ', $statement); return $statement; } + + protected function getCharset() { + if (!$this->charset) { + $params = $this->conn->getParams(); + $this->charset = isset($params['charset']) ? $params['charset'] : 'utf8'; + } + + return $this->charset; + } } diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php index 4fa25aae08d..dfe2e86b617 100644 --- a/lib/private/DB/Connection.php +++ b/lib/private/DB/Connection.php @@ -33,6 +33,7 @@ use Doctrine\DBAL\Driver; use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Cache\QueryCacheProfile; use Doctrine\Common\EventManager; +use Doctrine\DBAL\Platforms\MySqlPlatform; use OC\DB\QueryBuilder\QueryBuilder; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; @@ -402,4 +403,14 @@ class Connection extends \Doctrine\DBAL\Connection implements IDBConnection { public function escapeLikeParameter($param) { return addcslashes($param, '\\_%'); } + + /** + * Check whether or not the current database support 4byte wide unicode + * + * @return bool + * @since 9.2.0 + */ + public function supports4ByteText() { + return ! ($this->getDatabasePlatform() instanceof MySqlPlatform && $this->getParams()['charset'] !== 'utf8mb4'); + } } diff --git a/lib/private/DB/ConnectionFactory.php b/lib/private/DB/ConnectionFactory.php index b2c356edef7..adb180da0c0 100644 --- a/lib/private/DB/ConnectionFactory.php +++ b/lib/private/DB/ConnectionFactory.php @@ -28,6 +28,7 @@ namespace OC\DB; use Doctrine\DBAL\Event\Listeners\OracleSessionInit; use Doctrine\DBAL\Event\Listeners\SQLSessionInit; use Doctrine\DBAL\Event\Listeners\MysqlSessionInit; +use OCP\IConfig; /** * Takes care of creating and configuring Doctrine connections. @@ -64,6 +65,12 @@ class ConnectionFactory { ), ); + public function __construct(IConfig $config) { + if($config->getSystemValue('mysql.utf8mb4', false)) { + $this->defaultConnectionParams['mysql']['charset'] = 'utf8mb4'; + } + } + /** * @brief Get default connection parameters for a given DBMS. * @param string $type DBMS type @@ -99,7 +106,9 @@ class ConnectionFactory { case 'mysql': // Send "SET NAMES utf8". Only required on PHP 5.3 below 5.3.6. // See http://stackoverflow.com/questions/4361459/php-pdo-charset-set-names#4361485 - $eventManager->addEventSubscriber(new MysqlSessionInit); + $eventManager->addEventSubscriber(new MysqlSessionInit( + $this->defaultConnectionParams['mysql']['charset'] + )); $eventManager->addEventSubscriber( new SQLSessionInit("SET SESSION AUTOCOMMIT=1")); break; diff --git a/lib/private/DB/MDB2SchemaManager.php b/lib/private/DB/MDB2SchemaManager.php index 494c8fd53f1..f209991eb84 100644 --- a/lib/private/DB/MDB2SchemaManager.php +++ b/lib/private/DB/MDB2SchemaManager.php @@ -122,17 +122,6 @@ class MDB2SchemaManager { } /** - * update the database scheme - * @param string $file file to read structure from - * @return boolean - */ - public function simulateUpdateDbFromStructure($file) { - $toSchema = $this->readSchemaFromFile($file); - $this->getMigrator()->checkMigrate($toSchema); - return true; - } - - /** * @param \Doctrine\DBAL\Schema\Schema $schema * @return string */ diff --git a/lib/private/DB/MDB2SchemaReader.php b/lib/private/DB/MDB2SchemaReader.php index 3f183c9723a..c198bb31e00 100644 --- a/lib/private/DB/MDB2SchemaReader.php +++ b/lib/private/DB/MDB2SchemaReader.php @@ -33,6 +33,7 @@ namespace OC\DB; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Schema\SchemaConfig; +use Doctrine\DBAL\Platforms\MySqlPlatform; use OCP\IConfig; class MDB2SchemaReader { @@ -54,12 +55,16 @@ class MDB2SchemaReader { /** @var \Doctrine\DBAL\Schema\SchemaConfig $schemaConfig */ protected $schemaConfig; + /** @var IConfig */ + protected $config; + /** * @param \OCP\IConfig $config * @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform */ public function __construct(IConfig $config, AbstractPlatform $platform) { $this->platform = $platform; + $this->config = $config; $this->DBNAME = $config->getSystemValue('dbname', 'owncloud'); $this->DBTABLEPREFIX = $config->getSystemValue('dbtableprefix', 'oc_'); @@ -118,8 +123,15 @@ class MDB2SchemaReader { $name = str_replace('*dbprefix*', $this->DBTABLEPREFIX, $name); $name = $this->platform->quoteIdentifier($name); $table = $schema->createTable($name); - $table->addOption('collate', 'utf8_bin'); $table->setSchemaConfig($this->schemaConfig); + + if($this->platform instanceof MySqlPlatform && $this->config->getSystemValue('mysql.utf8mb4', false)) { + $table->addOption('charset', 'utf8mb4'); + $table->addOption('collate', 'utf8mb4_bin'); + $table->addOption('row_format', 'compressed'); + } else { + $table->addOption('collate', 'utf8_bin'); + } break; case 'create': case 'overwrite': diff --git a/lib/private/DB/MDB2SchemaWriter.php b/lib/private/DB/MDB2SchemaWriter.php index 26e32b036fe..7664b4359ab 100644 --- a/lib/private/DB/MDB2SchemaWriter.php +++ b/lib/private/DB/MDB2SchemaWriter.php @@ -43,7 +43,11 @@ class MDB2SchemaWriter { $xml->addChild('name', $config->getSystemValue('dbname', 'owncloud')); $xml->addChild('create', 'true'); $xml->addChild('overwrite', 'false'); - $xml->addChild('charset', 'utf8'); + if($config->getSystemValue('dbtype', 'sqlite') === 'mysql' && $config->getSystemValue('mysql.utf8mb4', false)) { + $xml->addChild('charset', 'utf8mb4'); + } else { + $xml->addChild('charset', 'utf8'); + } // FIX ME: bloody work around if ($config->getSystemValue('dbtype', 'sqlite') === 'oci') { diff --git a/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php b/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php index 66d8851632f..17f7fd5aa47 100644 --- a/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php +++ b/lib/private/DB/QueryBuilder/ExpressionBuilder/MySqlExpressionBuilder.php @@ -24,18 +24,31 @@ namespace OC\DB\QueryBuilder\ExpressionBuilder; -use OC\DB\QueryBuilder\QueryFunction; -use OCP\DB\QueryBuilder\IQueryBuilder; +use OC\DB\Connection; +use OCP\IDBConnection; class MySqlExpressionBuilder extends ExpressionBuilder { + /** @var string */ + protected $charset; + + /** + * @param \OCP\IDBConnection|Connection $connection + */ + public function __construct(IDBConnection $connection) { + parent::__construct($connection); + + $params = $connection->getParams(); + $this->charset = isset($params['charset']) ? $params['charset'] : 'utf8'; + } + /** * @inheritdoc */ public function iLike($x, $y, $type = null) { $x = $this->helper->quoteColumnName($x); $y = $this->helper->quoteColumnName($y); - return $this->expressionBuilder->comparison($x, ' COLLATE utf8_general_ci LIKE', $y); + return $this->expressionBuilder->comparison($x, ' COLLATE ' . $this->charset . '_general_ci LIKE', $y); } } diff --git a/lib/private/Encryption/DecryptAll.php b/lib/private/Encryption/DecryptAll.php index b84395b9e17..caf4237ab9c 100644 --- a/lib/private/Encryption/DecryptAll.php +++ b/lib/private/Encryption/DecryptAll.php @@ -211,7 +211,7 @@ class DecryptAll { $content = $this->rootView->getDirectoryContent($root); foreach ($content as $file) { // only decrypt files owned by the user - if($file->getStorage()->instanceOfStorage('OC\Files\Storage\Shared')) { + if($file->getStorage()->instanceOfStorage('OCA\Files_Sharing\SharedStorage')) { continue; } $path = $root . '/' . $file['name']; diff --git a/lib/private/Encryption/EncryptionWrapper.php b/lib/private/Encryption/EncryptionWrapper.php index 233390f8739..573fe0159ea 100644 --- a/lib/private/Encryption/EncryptionWrapper.php +++ b/lib/private/Encryption/EncryptionWrapper.php @@ -81,7 +81,7 @@ class EncryptionWrapper { 'mount' => $mount ]; - if (!$storage->instanceOfStorage('OC\Files\Storage\Shared') + if (!$storage->instanceOfStorage('OCA\Files_Sharing\SharedStorage') && !$storage->instanceOfStorage('OCA\Files_Sharing\External\Storage') && !$storage->instanceOfStorage('OC\Files\Storage\OwnCloud')) { diff --git a/lib/private/Files/AppData/AppData.php b/lib/private/Files/AppData/AppData.php new file mode 100644 index 00000000000..270e834b8e5 --- /dev/null +++ b/lib/private/Files/AppData/AppData.php @@ -0,0 +1,131 @@ +<?php +/** + * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @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 OC\Files\AppData; + +use OC\Files\SimpleFS\SimpleFolder; +use OCP\Files\IAppData; +use OCP\Files\IRootFolder; +use OCP\Files\Folder; +use OC\SystemConfig; +use OCP\Files\Node; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; + +class AppData implements IAppData { + + /** @var IRootFolder */ + private $rootFolder; + + /** @var SystemConfig */ + private $config; + + /** @var string */ + private $appId; + + /** @var Folder */ + private $folder; + + /** + * AppData constructor. + * + * @param IRootFolder $rootFolder + * @param SystemConfig $systemConfig + * @param string $appId + */ + public function __construct(IRootFolder $rootFolder, + SystemConfig $systemConfig, + $appId) { + + $this->rootFolder = $rootFolder; + $this->config = $systemConfig; + $this->appId = $appId; + } + + /** + * @return Folder + * @throws \RuntimeException + */ + private function getAppDataFolder() { + if ($this->folder === null) { + $instanceId = $this->config->getValue('instanceid', null); + if ($instanceId === null) { + throw new \RuntimeException('no instance id!'); + } + + $name = 'appdata_' . $instanceId; + + try { + $appDataFolder = $this->rootFolder->get($name); + } catch (NotFoundException $e) { + try { + $appDataFolder = $this->rootFolder->newFolder($name); + } catch (NotPermittedException $e) { + throw new \RuntimeException('Could not get appdata folder'); + } + } + + try { + $appDataFolder = $appDataFolder->get($this->appId); + } catch (NotFoundException $e) { + try { + $appDataFolder = $appDataFolder->newFolder($this->appId); + } catch (NotPermittedException $e) { + throw new \RuntimeException('Could not get appdata folder for ' . $this->appId); + } + } + + $this->folder = $appDataFolder; + } + + return $this->folder; + } + + public function getFolder($name) { + $node = $this->getAppDataFolder()->get($name); + + /** @var Folder $node */ + return new SimpleFolder($node); + } + + public function newFolder($name) { + $folder = $this->getAppDataFolder()->newFolder($name); + + return new SimpleFolder($folder); + } + + public function getDirectoryListing() { + $listing = $this->getAppDataFolder()->getDirectoryListing(); + + $fileListing = array_map(function(Node $folder) { + if ($folder instanceof Folder) { + return new SimpleFolder($folder); + } + return null; + }, $listing); + + $fileListing = array_filter($fileListing); + + return array_values($fileListing); + } +} diff --git a/lib/private/Files/AppData/Factory.php b/lib/private/Files/AppData/Factory.php new file mode 100644 index 00000000000..85c75733796 --- /dev/null +++ b/lib/private/Files/AppData/Factory.php @@ -0,0 +1,50 @@ +<?php +/** + * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @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 OC\Files\AppData; + +use OC\SystemConfig; +use OCP\Files\IRootFolder; + +class Factory { + + /** @var IRootFolder */ + private $rootFolder; + + /** @var SystemConfig */ + private $config; + + public function __construct(IRootFolder $rootFolder, + SystemConfig $systemConfig) { + + $this->rootFolder = $rootFolder; + $this->config = $systemConfig; + } + + /** + * @param string $appId + * @return AppData + */ + public function get($appId) { + return new AppData($this->rootFolder, $this->config, $appId); + } +} diff --git a/lib/private/Files/Config/CachedMountInfo.php b/lib/private/Files/Config/CachedMountInfo.php index f68949f8d55..c4132a34431 100644 --- a/lib/private/Files/Config/CachedMountInfo.php +++ b/lib/private/Files/Config/CachedMountInfo.php @@ -54,6 +54,11 @@ class CachedMountInfo implements ICachedMountInfo { protected $mountId; /** + * @var string + */ + protected $rootInternalPath; + + /** * CachedMountInfo constructor. * * @param IUser $user @@ -61,13 +66,15 @@ class CachedMountInfo implements ICachedMountInfo { * @param int $rootId * @param string $mountPoint * @param int|null $mountId + * @param string $rootInternalPath */ - public function __construct(IUser $user, $storageId, $rootId, $mountPoint, $mountId = null) { + public function __construct(IUser $user, $storageId, $rootId, $mountPoint, $mountId = null, $rootInternalPath = '') { $this->user = $user; $this->storageId = $storageId; $this->rootId = $rootId; $this->mountPoint = $mountPoint; $this->mountId = $mountId; + $this->rootInternalPath = $rootInternalPath; } /** @@ -98,7 +105,7 @@ class CachedMountInfo implements ICachedMountInfo { // TODO injection etc Filesystem::initMountPoints($this->getUser()->getUID()); $userNode = \OC::$server->getUserFolder($this->getUser()->getUID()); - $nodes = $userNode->getById($this->getRootId()); + $nodes = $userNode->getParent()->getById($this->getRootId()); if (count($nodes) > 0) { return $nodes[0]; } else { @@ -122,4 +129,13 @@ class CachedMountInfo implements ICachedMountInfo { public function getMountId() { return $this->mountId; } + + /** + * Get the internal path (within the storage) of the root of the mount + * + * @return string + */ + public function getRootInternalPath() { + return $this->rootInternalPath; + } } diff --git a/lib/private/Files/Config/LazyStorageMountInfo.php b/lib/private/Files/Config/LazyStorageMountInfo.php index 4df813d57d0..40d463c5103 100644 --- a/lib/private/Files/Config/LazyStorageMountInfo.php +++ b/lib/private/Files/Config/LazyStorageMountInfo.php @@ -76,4 +76,13 @@ class LazyStorageMountInfo extends CachedMountInfo { public function getMountId() { return $this->mount->getMountId(); } + + /** + * Get the internal path (within the storage) of the root of the mount + * + * @return string + */ + public function getRootInternalPath() { + return $this->mount->getInternalPath($this->mount->getMountPoint()); + } } diff --git a/lib/private/Files/Config/UserMountCache.php b/lib/private/Files/Config/UserMountCache.php index bd8343fa440..e9e142d0d4b 100644 --- a/lib/private/Files/Config/UserMountCache.php +++ b/lib/private/Files/Config/UserMountCache.php @@ -31,6 +31,7 @@ use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Files\Config\ICachedMountInfo; use OCP\Files\Config\IUserMountCache; use OCP\Files\Mount\IMountPoint; +use OCP\Files\Node; use OCP\Files\NotFoundException; use OCP\ICache; use OCP\IDBConnection; @@ -187,7 +188,7 @@ class UserMountCache implements IUserMountCache { private function dbRowToMountInfo(array $row) { $user = $this->userManager->get($row['user_id']); - return new CachedMountInfo($user, (int)$row['storage_id'], (int)$row['root_id'], $row['mount_point'], $row['mount_id']); + return new CachedMountInfo($user, (int)$row['storage_id'], (int)$row['root_id'], $row['mount_point'], $row['mount_id'], isset($row['path'])? $row['path']:''); } /** @@ -197,8 +198,9 @@ class UserMountCache implements IUserMountCache { public function getMountsForUser(IUser $user) { if (!isset($this->mountsForUsers[$user->getUID()])) { $builder = $this->connection->getQueryBuilder(); - $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id') - ->from('mounts') + $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path') + ->from('mounts', 'm') + ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid')) ->where($builder->expr()->eq('user_id', $builder->createPositionalParameter($user->getUID()))); $rows = $query->execute()->fetchAll(); @@ -214,8 +216,9 @@ class UserMountCache implements IUserMountCache { */ public function getMountsForStorageId($numericStorageId) { $builder = $this->connection->getQueryBuilder(); - $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id') - ->from('mounts') + $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path') + ->from('mounts', 'm') + ->innerJoin('m', 'filecache', 'f' , $builder->expr()->eq('m.root_id', 'f.fileid')) ->where($builder->expr()->eq('storage_id', $builder->createPositionalParameter($numericStorageId, IQueryBuilder::PARAM_INT))); $rows = $query->execute()->fetchAll(); @@ -229,8 +232,9 @@ class UserMountCache implements IUserMountCache { */ public function getMountsForRootId($rootFileId) { $builder = $this->connection->getQueryBuilder(); - $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id') - ->from('mounts') + $query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path') + ->from('mounts', 'm') + ->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid')) ->where($builder->expr()->eq('root_id', $builder->createPositionalParameter($rootFileId, IQueryBuilder::PARAM_INT))); $rows = $query->execute()->fetchAll(); @@ -246,7 +250,7 @@ class UserMountCache implements IUserMountCache { private function getCacheInfoFromFileId($fileId) { if (!isset($this->cacheInfoCache[$fileId])) { $builder = $this->connection->getQueryBuilder(); - $query = $builder->select('storage', 'path') + $query = $builder->select('storage', 'path', 'mimetype') ->from('filecache') ->where($builder->expr()->eq('fileid', $builder->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))); @@ -254,7 +258,8 @@ class UserMountCache implements IUserMountCache { if (is_array($row)) { $this->cacheInfoCache[$fileId] = [ (int)$row['storage'], - $row['path'] + $row['path'], + (int)$row['mimetype'] ]; } else { throw new NotFoundException('File with id "' . $fileId . '" not found'); @@ -281,15 +286,10 @@ class UserMountCache implements IUserMountCache { if ($fileId === $mount->getRootId()) { return true; } - try { - list(, $internalMountPath) = $this->getCacheInfoFromFileId($mount->getRootId()); - } catch (NotFoundException $e) { - return false; - } + $internalMountPath = $mount->getRootInternalPath(); return $internalMountPath === '' || substr($internalPath, 0, strlen($internalMountPath) + 1) === $internalMountPath . '/'; }); - } /** diff --git a/lib/private/Files/Filesystem.php b/lib/private/Files/Filesystem.php index d835fea7f4c..113bb5a85c6 100644 --- a/lib/private/Files/Filesystem.php +++ b/lib/private/Files/Filesystem.php @@ -225,7 +225,7 @@ class Filesystem { * @param int $priority */ public static function addStorageWrapper($wrapperName, $wrapper, $priority = 50) { - if (self::$logWarningWhenAddingStorageWrapper) { + if (self::$logWarningWhenAddingStorageWrapper && $wrapperName !== 'readonly') { \OC::$server->getLogger()->warning("Storage wrapper '{wrapper}' was not registered via the 'OC_Filesystem - preSetup' hook which could cause potential problems.", [ 'wrapper' => $wrapperName, 'app' => 'filesystem', diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index e67e4817e2a..288a02ef207 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -28,6 +28,7 @@ namespace OC\Files\Node; use OC\DB\QueryBuilder\Literal; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\Files\Config\ICachedMountInfo; use OCP\Files\FileInfo; use OCP\Files\Mount\IMountPoint; use OCP\Files\NotFoundException; @@ -41,7 +42,7 @@ class Folder extends Node implements \OCP\Files\Folder { */ public function getFullPath($path) { if (!$this->isValidPath($path)) { - throw new NotPermittedException(); + throw new NotPermittedException('Invalid path'); } return $this->path . $this->normalizePath($path); } @@ -83,7 +84,7 @@ class Folder extends Node implements \OCP\Files\Folder { public function getDirectoryListing() { $folderContent = $this->view->getDirectoryContent($this->path); - return array_map(function(FileInfo $info) { + return array_map(function (FileInfo $info) { if ($info->getMimetype() === 'httpd/unix-directory') { return new Folder($this->root, $this->view, $info->getPath(), $info); } else { @@ -151,7 +152,7 @@ class Folder extends Node implements \OCP\Files\Folder { $this->root->emit('\OC\Files', 'postCreate', array($node)); return $node; } else { - throw new NotPermittedException(); + throw new NotPermittedException('No create permission for folder'); } } @@ -172,7 +173,7 @@ class Folder extends Node implements \OCP\Files\Folder { $this->root->emit('\OC\Files', 'postCreate', array($node)); return $node; } else { - throw new NotPermittedException(); + throw new NotPermittedException('No create permission for path'); } } @@ -253,7 +254,7 @@ class Folder extends Node implements \OCP\Files\Folder { } } - return array_map(function(FileInfo $file) { + return array_map(function (FileInfo $file) { return $this->createNode($file->getPath(), $file); }, $files); } @@ -263,29 +264,48 @@ class Folder extends Node implements \OCP\Files\Folder { * @return \OC\Files\Node\Node[] */ public function getById($id) { + $mountCache = $this->root->getUserMountCache(); + $mountsContainingFile = $mountCache->getMountsForFileId((int)$id); $mounts = $this->root->getMountsIn($this->path); $mounts[] = $this->root->getMount($this->path); - // reverse the array so we start with the storage this view is in - // which is the most likely to contain the file we're looking for - $mounts = array_reverse($mounts); + /** @var IMountPoint[] $folderMounts */ + $folderMounts = array_combine(array_map(function (IMountPoint $mountPoint) { + return $mountPoint->getMountPoint(); + }, $mounts), $mounts); + + /** @var ICachedMountInfo[] $mountsContainingFile */ + $mountsContainingFile = array_values(array_filter($mountsContainingFile, function (ICachedMountInfo $cachedMountInfo) use ($folderMounts) { + return isset($folderMounts[$cachedMountInfo->getMountPoint()]); + })); - $nodes = array(); - foreach ($mounts as $mount) { - /** - * @var \OC\Files\Mount\MountPoint $mount - */ - if ($mount->getStorage()) { - $cache = $mount->getStorage()->getCache(); - $internalPath = $cache->getPathById($id); - if (is_string($internalPath)) { - $fullPath = $mount->getMountPoint() . $internalPath; - if (!is_null($path = $this->getRelativePath($fullPath))) { - $nodes[] = $this->get($path); - } - } - } + if (count($mountsContainingFile) === 0) { + return []; } - return $nodes; + + // we only need to get the cache info once, since all mounts we found point to the same storage + + $mount = $folderMounts[$mountsContainingFile[0]->getMountPoint()]; + $cacheEntry = $mount->getStorage()->getCache()->get((int)$id); + if (!$cacheEntry) { + return []; + } + // cache jails will hide the "true" internal path + $internalPath = ltrim($mountsContainingFile[0]->getRootInternalPath() . '/' . $cacheEntry->getPath(), '/'); + + $nodes = array_map(function (ICachedMountInfo $cachedMountInfo) use ($cacheEntry, $folderMounts, $internalPath) { + $mount = $folderMounts[$cachedMountInfo->getMountPoint()]; + $pathRelativeToMount = substr($internalPath, strlen($cachedMountInfo->getRootInternalPath())); + $pathRelativeToMount = ltrim($pathRelativeToMount, '/'); + $absolutePath = $cachedMountInfo->getMountPoint() . $pathRelativeToMount; + return $this->root->createNode($absolutePath, new \OC\Files\FileInfo( + $absolutePath, $mount->getStorage(), $cacheEntry->getPath(), $cacheEntry, $mount, + \OC::$server->getUserManager()->get($mount->getStorage()->getOwner($pathRelativeToMount)) + )); + }, $mountsContainingFile); + + return array_filter($nodes, function (Node $node) { + return $this->getRelativePath($node->getPath()); + }); } public function getFreeSpace() { @@ -301,7 +321,7 @@ class Folder extends Node implements \OCP\Files\Folder { $this->root->emit('\OC\Files', 'postDelete', array($nonExisting)); $this->exists = false; } else { - throw new NotPermittedException(); + throw new NotPermittedException('No delete permission for path'); } } @@ -323,7 +343,7 @@ class Folder extends Node implements \OCP\Files\Folder { $this->root->emit('\OC\Files', 'postWrite', array($targetNode)); return $targetNode; } else { - throw new NotPermittedException(); + throw new NotPermittedException('No permission to copy to path'); } } @@ -346,7 +366,7 @@ class Folder extends Node implements \OCP\Files\Folder { $this->path = $targetPath; return $targetNode; } else { - throw new NotPermittedException(); + throw new NotPermittedException('No permission to move to path'); } } diff --git a/lib/private/Files/Node/Root.php b/lib/private/Files/Node/Root.php index 007847fb513..0cda2c8b822 100644 --- a/lib/private/Files/Node/Root.php +++ b/lib/private/Files/Node/Root.php @@ -31,6 +31,7 @@ namespace OC\Files\Node; use OC\Cache\CappedMemoryCache; use OC\Files\Mount\Manager; use OC\Files\Mount\MountPoint; +use OCP\Files\Config\IUserMountCache; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; use OC\Hooks\PublicEmitter; @@ -75,16 +76,23 @@ class Root extends Folder implements IRootFolder { private $userFolderCache; /** + * @var IUserMountCache + */ + private $userMountCache; + + /** * @param \OC\Files\Mount\Manager $manager * @param \OC\Files\View $view * @param \OC\User\User|null $user + * @param IUserMountCache $userMountCache */ - public function __construct($manager, $view, $user) { + public function __construct($manager, $view, $user, IUserMountCache $userMountCache) { parent::__construct($this, $view, ''); $this->mountManager = $manager; $this->user = $user; $this->emitter = new PublicEmitter(); $this->userFolderCache = new CappedMemoryCache(); + $this->userMountCache = $userMountCache; } /** @@ -361,4 +369,8 @@ class Root extends Folder implements IRootFolder { public function clearCache() { $this->userFolderCache = new CappedMemoryCache(); } + + public function getUserMountCache() { + return $this->userMountCache; + } } diff --git a/lib/private/Files/SimpleFS/SimpleFile.php b/lib/private/Files/SimpleFS/SimpleFile.php new file mode 100644 index 00000000000..5eadfd98b60 --- /dev/null +++ b/lib/private/Files/SimpleFS/SimpleFile.php @@ -0,0 +1,115 @@ +<?php +/** + * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @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 OC\Files\SimpleFS; + +use OCP\Files\File; +use OCP\Files\NotPermittedException; +use OCP\Files\SimpleFS\ISimpleFile; + +class SimpleFile implements ISimpleFile { + + /** @var File $file */ + private $file; + + /** + * File constructor. + * + * @param File $file + */ + public function __construct(File $file) { + $this->file = $file; + } + + /** + * Get the name + * + * @return string + */ + public function getName() { + return $this->file->getName(); + } + + /** + * Get the size in bytes + * + * @return int + */ + public function getSize() { + return $this->file->getSize(); + } + + /** + * Get the ETag + * + * @return string + */ + public function getETag() { + return $this->file->getEtag(); + } + + /** + * Get the last modification time + * + * @return int + */ + public function getMTime() { + return $this->file->getMTime(); + } + + /** + * Get the content + * + * @return string + */ + public function getContent() { + return $this->file->getContent(); + } + + /** + * Overwrite the file + * + * @param string $data + * @throws NotPermittedException + */ + public function putContent($data) { + $this->file->putContent($data); + } + + /** + * Delete the file + * + * @throws NotPermittedException + */ + public function delete() { + $this->file->delete(); + } + + /** + * Get the MimeType + * + * @return string + */ + public function getMimeType() { + return $this->file->getMimeType(); + } +} diff --git a/lib/private/Files/SimpleFS/SimpleFolder.php b/lib/private/Files/SimpleFS/SimpleFolder.php new file mode 100644 index 00000000000..5b55fe0f157 --- /dev/null +++ b/lib/private/Files/SimpleFS/SimpleFolder.php @@ -0,0 +1,87 @@ +<?php +/** + * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @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 OC\Files\SimpleFS; + +use OCP\Files\File; +use OCP\Files\Folder; +use OCP\Files\Node; +use OCP\Files\NotFoundException; +use OCP\Files\SimpleFS\ISimpleFolder; + +class SimpleFolder implements ISimpleFolder { + + /** @var Folder */ + private $folder; + + /** + * Folder constructor. + * + * @param Folder $folder + */ + public function __construct(Folder $folder) { + $this->folder = $folder; + } + + public function getName() { + return $this->folder->getName(); + } + + public function getDirectoryListing() { + $listing = $this->folder->getDirectoryListing(); + + $fileListing = array_map(function(Node $file) { + if ($file instanceof File) { + return new SimpleFile($file); + } + return null; + }, $listing); + + $fileListing = array_filter($fileListing); + + return array_values($fileListing); + } + + public function delete() { + $this->folder->delete(); + } + + public function fileExists($name) { + return $this->folder->nodeExists($name); + } + + public function getFile($name) { + $file = $this->folder->get($name); + + if (!($file instanceof File)) { + throw new NotFoundException(); + } + + return new SimpleFile($file); + } + + public function newFile($name) { + $file = $this->folder->newFile($name); + + return new SimpleFile($file); + } +} diff --git a/lib/private/Files/Storage/Common.php b/lib/private/Files/Storage/Common.php index 63d3c004fd2..c975791295d 100644 --- a/lib/private/Files/Storage/Common.php +++ b/lib/private/Files/Storage/Common.php @@ -466,6 +466,10 @@ abstract class Common implements Storage, ILockingStorage { * @return bool */ public function instanceOfStorage($class) { + if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') { + // FIXME Temporary fix to keep existing checks working + $class = '\OCA\Files_Sharing\SharedStorage'; + } return is_a($this, $class); } diff --git a/lib/private/Files/Storage/Local.php b/lib/private/Files/Storage/Local.php index 19674fc9413..4fe7dcafbbf 100644 --- a/lib/private/Files/Storage/Local.php +++ b/lib/private/Files/Storage/Local.php @@ -54,7 +54,12 @@ class Local extends \OC\Files\Storage\Common { throw new \InvalidArgumentException('No data directory set for local storage'); } $this->datadir = $arguments['datadir']; - $this->realDataDir = rtrim(realpath($this->datadir), '/') . '/'; + // some crazy code uses a local storage on root... + if ($this->datadir === '/') { + $this->realDataDir = $this->datadir; + } else { + $this->realDataDir = rtrim(realpath($this->datadir), '/') . '/'; + } if (substr($this->datadir, -1) !== '/') { $this->datadir .= '/'; } @@ -150,7 +155,7 @@ class Local extends \OC\Files\Storage\Common { $fullPath = $this->getSourcePath($path); if (PHP_INT_SIZE === 4) { $helper = new \OC\LargeFileHelper; - return $helper->getFilesize($fullPath); + return $helper->getFileSize($fullPath); } return filesize($fullPath); } @@ -168,8 +173,16 @@ class Local extends \OC\Files\Storage\Common { } public function filemtime($path) { - clearstatcache($this->getSourcePath($path)); - return $this->file_exists($path) ? filemtime($this->getSourcePath($path)) : false; + $fullPath = $this->getSourcePath($path); + clearstatcache($fullPath); + if (!$this->file_exists($path)) { + return false; + } + if (PHP_INT_SIZE === 4) { + $helper = new \OC\LargeFileHelper(); + return $helper->getFileMtime($fullPath); + } + return filemtime($fullPath); } public function touch($path, $mtime = null) { diff --git a/lib/private/Files/Storage/Wrapper/PermissionsMask.php b/lib/private/Files/Storage/Wrapper/PermissionsMask.php index 39375602c34..7bcb1087fef 100644 --- a/lib/private/Files/Storage/Wrapper/PermissionsMask.php +++ b/lib/private/Files/Storage/Wrapper/PermissionsMask.php @@ -78,6 +78,14 @@ class PermissionsMask extends Wrapper { } public function rename($path1, $path2) { + $p = strpos($path1, $path2); + if ($p === 0) { + $part = substr($path1, strlen($path2)); + //This is a rename of the transfer file to the original file + if (strpos($part, '.ocTransferId') === 0) { + return $this->checkMask(Constants::PERMISSION_CREATE) and parent::rename($path1, $path2); + } + } return $this->checkMask(Constants::PERMISSION_UPDATE) and parent::rename($path1, $path2); } diff --git a/lib/private/Files/Storage/Wrapper/Wrapper.php b/lib/private/Files/Storage/Wrapper/Wrapper.php index c52b3394832..71b64d8c82c 100644 --- a/lib/private/Files/Storage/Wrapper/Wrapper.php +++ b/lib/private/Files/Storage/Wrapper/Wrapper.php @@ -483,6 +483,10 @@ class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage { * @return bool */ public function instanceOfStorage($class) { + if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') { + // FIXME Temporary fix to keep existing checks working + $class = '\OCA\Files_Sharing\SharedStorage'; + } return is_a($this, $class) or $this->getWrapperStorage()->instanceOfStorage($class); } diff --git a/lib/private/Files/Stream/OC.php b/lib/private/Files/Stream/OC.php deleted file mode 100644 index f415bc13b15..00000000000 --- a/lib/private/Files/Stream/OC.php +++ /dev/null @@ -1,154 +0,0 @@ -<?php -/** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> - * @author Robin McCorkell <robin@mccorkell.me.uk> - * - * @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 OC\Files\Stream; - -/** - * a stream wrappers for ownCloud's virtual filesystem - */ -class OC { - /** - * @var \OC\Files\View - */ - static private $rootView; - - private $path; - - /** - * @var resource - */ - private $dirSource; - - /** - * @var resource - */ - private $fileSource; - private $meta; - - private function setup(){ - if (!self::$rootView) { - self::$rootView = new \OC\Files\View(''); - } - } - - public function stream_open($path, $mode, $options, &$opened_path) { - $this->setup(); - $path = substr($path, strlen('oc://')); - $this->path = $path; - $this->fileSource = self::$rootView->fopen($path, $mode); - if (is_resource($this->fileSource)) { - $this->meta = stream_get_meta_data($this->fileSource); - } - return is_resource($this->fileSource); - } - - public function stream_seek($offset, $whence = SEEK_SET) { - return fseek($this->fileSource, $offset, $whence) === 0; - } - - public function stream_tell() { - return ftell($this->fileSource); - } - - public function stream_read($count) { - return fread($this->fileSource, $count); - } - - public function stream_write($data) { - return fwrite($this->fileSource, $data); - } - - public function stream_set_option($option, $arg1, $arg2) { - switch ($option) { - case STREAM_OPTION_BLOCKING: - stream_set_blocking($this->fileSource, $arg1); - break; - case STREAM_OPTION_READ_TIMEOUT: - stream_set_timeout($this->fileSource, $arg1, $arg2); - break; - case STREAM_OPTION_WRITE_BUFFER: - stream_set_write_buffer($this->fileSource, $arg1, $arg2); - } - } - - public function stream_stat() { - return fstat($this->fileSource); - } - - public function stream_lock($mode) { - flock($this->fileSource, $mode); - } - - public function stream_flush() { - return fflush($this->fileSource); - } - - public function stream_eof() { - return feof($this->fileSource); - } - - public function url_stat($path) { - $this->setup(); - $path = substr($path, strlen('oc://')); - if (self::$rootView->file_exists($path)) { - return self::$rootView->stat($path); - } else { - return false; - } - } - - public function stream_close() { - fclose($this->fileSource); - } - - public function unlink($path) { - $this->setup(); - $path = substr($path, strlen('oc://')); - return self::$rootView->unlink($path); - } - - public function dir_opendir($path, $options) { - $this->setup(); - $path = substr($path, strlen('oc://')); - $this->path = $path; - $this->dirSource = self::$rootView->opendir($path); - if (is_resource($this->dirSource)) { - $this->meta = stream_get_meta_data($this->dirSource); - } - return is_resource($this->dirSource); - } - - public function dir_readdir() { - return readdir($this->dirSource); - } - - public function dir_closedir() { - closedir($this->dirSource); - } - - public function dir_rewinddir() { - rewinddir($this->dirSource); - } -} diff --git a/lib/private/Files/Type/Detection.php b/lib/private/Files/Type/Detection.php index 66ef0dd2aab..84d727ebb0e 100644 --- a/lib/private/Files/Type/Detection.php +++ b/lib/private/Files/Type/Detection.php @@ -166,9 +166,11 @@ class Detection implements IMimeTypeDetector { public function detectPath($path) { $this->loadMappings(); - if (strpos($path, '.')) { + $fileName = basename($path); + // note: leading dot doesn't qualify as extension + if (strpos($fileName, '.') > 0) { //try to guess the type by the file extension - $extension = strtolower(strrchr(basename($path), ".")); + $extension = strtolower(strrchr($fileName, '.')); $extension = substr($extension, 1); //remove leading . return (isset($this->mimetypes[$extension]) && isset($this->mimetypes[$extension][0])) ? $this->mimetypes[$extension][0] diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php index fa6ba20c342..f36e2c2c64f 100644 --- a/lib/private/Files/View.php +++ b/lib/private/Files/View.php @@ -1357,7 +1357,7 @@ class View { $subStorage = $mount->getStorage(); if ($subStorage) { // exclude shared storage ? - if ($extOnly && $subStorage instanceof \OC\Files\Storage\Shared) { + if ($extOnly && $subStorage instanceof \OCA\Files_Sharing\SharedStorage) { continue; } $subCache = $subStorage->getCache(''); @@ -1806,13 +1806,15 @@ class View { throw new InvalidPathException($l10n->t('Dot files are not allowed')); } - // verify database - e.g. mysql only 3-byte chars - if (preg_match('%(?: + if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) { + // verify database - e.g. mysql only 3-byte chars + if (preg_match('%(?: \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 )%xs', $fileName)) { - throw new InvalidPathException($l10n->t('4-byte characters are not supported in file names')); + throw new InvalidPathException($l10n->t('4-byte characters are not supported in file names')); + } } try { diff --git a/lib/private/Installer.php b/lib/private/Installer.php index 3d8a923417a..009df790585 100644 --- a/lib/private/Installer.php +++ b/lib/private/Installer.php @@ -614,7 +614,7 @@ class Installer { } $codeChecker = new CodeChecker(new PrivateCheck(new EmptyCheck())); - $errors = $codeChecker->analyseFolder($folder); + $errors = $codeChecker->analyseFolder(basename($folder), $folder); return empty($errors); } diff --git a/lib/private/IntegrityCheck/Checker.php b/lib/private/IntegrityCheck/Checker.php index 1db20772b4e..102fe42a99d 100644 --- a/lib/private/IntegrityCheck/Checker.php +++ b/lib/private/IntegrityCheck/Checker.php @@ -195,7 +195,7 @@ class Checker { copy($this->environmentHelper->getServerRoot() . '/.htaccess', $tmpFolder . '/.htaccess'); copy($this->environmentHelper->getServerRoot() . '/.user.ini', $tmpFolder . '/.user.ini'); \OC_Files::setUploadLimit( - \OCP\Util::computerFileSize('513MB'), + \OCP\Util::computerFileSize('511MB'), [ '.htaccess' => $tmpFolder . '/.htaccess', '.user.ini' => $tmpFolder . '/.user.ini', diff --git a/lib/private/L10N/Factory.php b/lib/private/L10N/Factory.php index 91233a0c4a7..8aad395065c 100644 --- a/lib/private/L10N/Factory.php +++ b/lib/private/L10N/Factory.php @@ -2,6 +2,7 @@ /** * @copyright Copyright (c) 2016, ownCloud, Inc. * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl> + * @copyright 2016 Lukas Reschke <lukas@statuscode.ch> * * @author Bart Visscher <bartv@thisnet.nl> * @author Joas Schilling <coding@schilljs.com> @@ -288,6 +289,27 @@ class Factory implements IFactory { } /** + * Checks if $sub is a subdirectory of $parent + * + * @param string $sub + * @param string $parent + * @return bool + */ + private function isSubDirectory($sub, $parent) { + // Check whether $sub contains no ".." + if(strpos($sub, '..') !== false) { + return false; + } + + // Check whether $sub is a subdirectory of $parent + if (strpos($sub, $parent) === 0) { + return true; + } + + return false; + } + + /** * Get a list of language files that should be loaded * * @param string $app @@ -302,10 +324,10 @@ class Factory implements IFactory { $i18nDir = $this->findL10nDir($app); $transFile = strip_tags($i18nDir) . strip_tags($lang) . '.json'; - if ((\OC_Helper::isSubDirectory($transFile, $this->serverRoot . '/core/l10n/') - || \OC_Helper::isSubDirectory($transFile, $this->serverRoot . '/lib/l10n/') - || \OC_Helper::isSubDirectory($transFile, $this->serverRoot . '/settings/l10n/') - || \OC_Helper::isSubDirectory($transFile, \OC_App::getAppPath($app) . '/l10n/') + if (($this->isSubDirectory($transFile, $this->serverRoot . '/core/l10n/') + || $this->isSubDirectory($transFile, $this->serverRoot . '/lib/l10n/') + || $this->isSubDirectory($transFile, $this->serverRoot . '/settings/l10n/') + || $this->isSubDirectory($transFile, \OC_App::getAppPath($app) . '/l10n/') ) && file_exists($transFile)) { // load the translations file diff --git a/lib/private/LargeFileHelper.php b/lib/private/LargeFileHelper.php index 9d0fe864033..9f18a6acd6b 100644 --- a/lib/private/LargeFileHelper.php +++ b/lib/private/LargeFileHelper.php @@ -1,6 +1,7 @@ <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. + * @copyright Copyright (c) 2016, Lukas Reschke <lukas@statuscode.ch> * * @author Andreas Fischer <bantu@owncloud.com> * @author Lukas Reschke <lukas@statuscode.ch> @@ -51,7 +52,7 @@ class LargeFileHelper { public function __construct() { $pow_2_53 = floatval(self::POW_2_53_MINUS_1) + 1.0; if ($this->formatUnsignedInteger($pow_2_53) !== self::POW_2_53) { - throw new \RunTimeException( + throw new \RuntimeException( 'This class assumes floats to be double precision or "better".' ); } @@ -98,10 +99,6 @@ class LargeFileHelper { if (!is_null($fileSize)) { return $fileSize; } - $fileSize = $this->getFileSizeViaCOM($filename); - if (!is_null($fileSize)) { - return $fileSize; - } $fileSize = $this->getFileSizeViaExec($filename); if (!is_null($fileSize)) { return $fileSize; @@ -154,12 +151,6 @@ class LargeFileHelper { $result = $this->exec("stat -c %s $arg"); } else if (strpos($os, 'bsd') !== false || strpos($os, 'darwin') !== false) { $result = $this->exec("stat -f %z $arg"); - } else if (strpos($os, 'win') !== false) { - $result = $this->exec("for %F in ($arg) do @echo %~zF"); - if (is_null($result)) { - // PowerShell - $result = $this->exec("(Get-Item $arg).length"); - } } return $result; } @@ -187,6 +178,23 @@ class LargeFileHelper { return $result; } + /** + * Returns the current mtime for $fullPath + * + * @param string $fullPath + * @return int + */ + public function getFileMtime($fullPath) { + if (\OC_Helper::is_function_enabled('exec')) { + $os = strtolower(php_uname('s')); + if (strpos($os, 'linux') !== false) { + return $this->exec('stat -c %Y ' . escapeshellarg($fullPath)); + } + } + + return filemtime($fullPath); + } + protected function exec($cmd) { $result = trim(exec($cmd)); return ctype_digit($result) ? 0 + $result : null; diff --git a/lib/private/Log.php b/lib/private/Log.php index 3e0734965b0..ef1b70d3cb9 100644 --- a/lib/private/Log.php +++ b/lib/private/Log.php @@ -233,7 +233,7 @@ class Log implements ILogger { * @return void */ public function log($level, $message, array $context = array()) { - $minLevel = min($this->config->getValue('loglevel', Util::WARN), Util::ERROR); + $minLevel = min($this->config->getValue('loglevel', Util::WARN), Util::FATAL); $logCondition = $this->config->getValue('log.condition', []); array_walk($context, [$this->normalizer, 'format']); @@ -277,7 +277,7 @@ class Log implements ILogger { $request = \OC::$server->getRequest(); // if token is found in the request change set the log condition to satisfied - if($request && hash_equals($logCondition['shared_secret'], $request->getParam('log_secret'))) { + if($request && hash_equals($logCondition['shared_secret'], $request->getParam('log_secret', ''))) { $this->logConditionSatisfied = true; } } diff --git a/lib/private/Log/File.php b/lib/private/Log/File.php index a406dd83952..904aa1d93f1 100644 --- a/lib/private/Log/File.php +++ b/lib/private/Log/File.php @@ -96,11 +96,12 @@ class File { $time = $time->format($format); $url = ($request->getRequestUri() !== '') ? $request->getRequestUri() : '--'; $method = is_string($request->getMethod()) ? $request->getMethod() : '--'; - if(\OC::$server->getConfig()->getSystemValue('installed', false)) { + if($config->getValue('installed', false)) { $user = (\OC_User::getUser()) ? \OC_User::getUser() : '--'; } else { $user = '--'; } + $version = $config->getValue('version', ''); $entry = compact( 'reqId', 'remoteAddr', @@ -110,7 +111,8 @@ class File { 'time', 'method', 'url', - 'user' + 'user', + 'version' ); $entry = json_encode($entry); $handle = @fopen(self::$logFile, 'a'); diff --git a/lib/private/Notification/Notification.php b/lib/private/Notification/Notification.php index 9b5877a3058..7bf4b9a74cf 100644 --- a/lib/private/Notification/Notification.php +++ b/lib/private/Notification/Notification.php @@ -242,7 +242,7 @@ class Notification implements INotification { /** * @param string $subject * @return $this - * @throws \InvalidArgumentException if the subject are invalid + * @throws \InvalidArgumentException if the subject is invalid * @since 8.2.0 */ public function setParsedSubject($subject) { @@ -300,7 +300,7 @@ class Notification implements INotification { /** * @param string $message * @return $this - * @throws \InvalidArgumentException if the message are invalid + * @throws \InvalidArgumentException if the message is invalid * @since 8.2.0 */ public function setParsedMessage($message) { @@ -322,7 +322,7 @@ class Notification implements INotification { /** * @param string $link * @return $this - * @throws \InvalidArgumentException if the link are invalid + * @throws \InvalidArgumentException if the link is invalid * @since 8.2.0 */ public function setLink($link) { @@ -342,6 +342,28 @@ class Notification implements INotification { } /** + * @param string $icon + * @return $this + * @throws \InvalidArgumentException if the icon is invalid + * @since 9.2.0 + */ + public function setIcon($icon) { + if (!is_string($icon) || $icon === '' || isset($icon[4000])) { + throw new \InvalidArgumentException('The given icon is invalid'); + } + $this->icon = $icon; + return $this; + } + + /** + * @return string + * @since 9.2.0 + */ + public function getIcon() { + return $this->icon; + } + + /** * @return IAction * @since 8.2.0 */ @@ -352,7 +374,7 @@ class Notification implements INotification { /** * @param IAction $action * @return $this - * @throws \InvalidArgumentException if the action are invalid + * @throws \InvalidArgumentException if the action is invalid * @since 8.2.0 */ public function addAction(IAction $action) { @@ -383,7 +405,7 @@ class Notification implements INotification { /** * @param IAction $action * @return $this - * @throws \InvalidArgumentException if the action are invalid + * @throws \InvalidArgumentException if the action is invalid * @since 8.2.0 */ public function addParsedAction(IAction $action) { diff --git a/lib/private/Preview.php b/lib/private/Preview.php index 67838a8d4a3..ccaec738caf 100644 --- a/lib/private/Preview.php +++ b/lib/private/Preview.php @@ -131,7 +131,7 @@ class Preview { $this->setFile($file); $this->setMaxX((int)$maxX); $this->setMaxY((int)$maxY); - $this->setScalingUp($scalingUp); + $this->setScalingup($scalingUp); $this->preview = null; @@ -791,6 +791,7 @@ class Preview { * @param null|string $mimeTypeForHeaders the media type to use when sending back the reply * * @throws NotFoundException + * @throws PreviewNotAvailableException */ public function showPreview($mimeTypeForHeaders = null) { // Check if file is valid @@ -1172,6 +1173,7 @@ class Preview { /** * Defines the media icon, for the media type of the original file, as the preview + * @throws PreviewNotAvailableException */ private function getMimeIcon() { $image = new \OC_Image(); @@ -1181,6 +1183,10 @@ class Preview { } else { $mimeIconServerPath = str_replace(\OC::$WEBROOT, \OC::$SERVERROOT, $mimeIconWebPath); } + // we can't load SVGs into an image + if (substr($mimeIconWebPath, -4) === '.svg') { + throw new PreviewNotAvailableException('SVG mimetype cannot be rendered'); + } $image->loadFromFile($mimeIconServerPath); $this->preview = $image; diff --git a/lib/private/PreviewNotAvailableException.php b/lib/private/PreviewNotAvailableException.php new file mode 100644 index 00000000000..7d92e860629 --- /dev/null +++ b/lib/private/PreviewNotAvailableException.php @@ -0,0 +1,27 @@ +<?php +/** + * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de> + * + * @author Morris Jobke <hey@morrisjobke.de> + * + * @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 OC; + +class PreviewNotAvailableException extends \Exception { +} diff --git a/lib/private/Repair.php b/lib/private/Repair.php index cd23f5cb806..7a5ef9fbd9e 100644 --- a/lib/private/Repair.php +++ b/lib/private/Repair.php @@ -35,6 +35,8 @@ use OC\Repair\AvatarPermissions; use OC\Repair\CleanTags; use OC\Repair\Collation; use OC\Repair\DropOldJobs; +use OC\Repair\MoveUpdaterStepFile; +use OC\Repair\NC11\MoveAvatars; use OC\Repair\OldGroupMembershipShares; use OC\Repair\RemoveGetETagEntries; use OC\Repair\RemoveOldShares; @@ -127,6 +129,7 @@ class Repair implements IOutput{ */ public static function getRepairSteps() { return [ + new Collation(\OC::$server->getConfig(), \OC::$server->getLogger(), \OC::$server->getDatabaseConnection(), false), new RepairMimeTypes(\OC::$server->getConfig()), new RepairLegacyStorages(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection()), new AssetCache(), @@ -147,6 +150,11 @@ class Repair implements IOutput{ \OC::$server->getUserManager(), \OC::$server->getGroupManager() ), + new MoveUpdaterStepFile(\OC::$server->getConfig()), + new MoveAvatars( + \OC::$server->getJobList(), + \OC::$server->getSystemConfig() + ), ]; } @@ -172,7 +180,7 @@ class Repair implements IOutput{ $connection = \OC::$server->getDatabaseConnection(); $steps = [ new InnoDB(), - new Collation(\OC::$server->getConfig(), $connection), + new Collation(\OC::$server->getConfig(), \OC::$server->getLogger(), $connection, true), new SqliteAutoincrement($connection), new SearchLuceneTables(), ]; diff --git a/lib/private/Repair/Collation.php b/lib/private/Repair/Collation.php index 74c4ca2e6ac..54de1a719bd 100644 --- a/lib/private/Repair/Collation.php +++ b/lib/private/Repair/Collation.php @@ -24,28 +24,38 @@ namespace OC\Repair; +use Doctrine\DBAL\Exception\DriverException; use Doctrine\DBAL\Platforms\MySqlPlatform; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\ILogger; use OCP\Migration\IOutput; use OCP\Migration\IRepairStep; class Collation implements IRepairStep { - /** - * @var \OCP\IConfig - */ + /** @var IConfig */ protected $config; - /** - * @var \OC\DB\Connection - */ + /** @var ILogger */ + protected $logger; + + /** @var IDBConnection */ protected $connection; + /** @var bool */ + protected $ignoreFailures; + /** - * @param \OCP\IConfig $config - * @param \OC\DB\Connection $connection + * @param IConfig $config + * @param ILogger $logger + * @param IDBConnection $connection + * @param bool $ignoreFailures */ - public function __construct($config, $connection) { + public function __construct(IConfig $config, ILogger $logger, IDBConnection $connection, $ignoreFailures) { $this->connection = $connection; $this->config = $config; + $this->logger = $logger; + $this->ignoreFailures = $ignoreFailures; } public function getName() { @@ -61,11 +71,21 @@ class Collation implements IRepairStep { return; } + $characterSet = $this->config->getSystemValue('mysql.utf8mb4', false) ? 'utf8mb4' : 'utf8'; + $tables = $this->getAllNonUTF8BinTables($this->connection); foreach ($tables as $table) { $output->info("Change collation for $table ..."); - $query = $this->connection->prepare('ALTER TABLE `' . $table . '` CONVERT TO CHARACTER SET utf8 COLLATE utf8_bin;'); - $query->execute(); + $query = $this->connection->prepare('ALTER TABLE `' . $table . '` CONVERT TO CHARACTER SET ' . $characterSet . ' COLLATE ' . $characterSet . '_bin;'); + try { + $query->execute(); + } catch (DriverException $e) { + // Just log this + $this->logger->logException($e); + if (!$this->ignoreFailures) { + throw $e; + } + } } } @@ -75,11 +95,12 @@ class Collation implements IRepairStep { */ protected function getAllNonUTF8BinTables($connection) { $dbName = $this->config->getSystemValue("dbname"); + $characterSet = $this->config->getSystemValue('mysql.utf8mb4', false) ? 'utf8mb4' : 'utf8'; $rows = $connection->fetchAll( "SELECT DISTINCT(TABLE_NAME) AS `table`" . " FROM INFORMATION_SCHEMA . COLUMNS" . " WHERE TABLE_SCHEMA = ?" . - " AND (COLLATION_NAME <> 'utf8_bin' OR CHARACTER_SET_NAME <> 'utf8')" . + " AND (COLLATION_NAME <> '" . $characterSet . "_bin' OR CHARACTER_SET_NAME <> '" . $characterSet . "')" . " AND TABLE_NAME LIKE \"*PREFIX*%\"", array($dbName) ); diff --git a/lib/private/Repair/MoveUpdaterStepFile.php b/lib/private/Repair/MoveUpdaterStepFile.php new file mode 100644 index 00000000000..fabaff40d24 --- /dev/null +++ b/lib/private/Repair/MoveUpdaterStepFile.php @@ -0,0 +1,80 @@ +<?php +/** + * @copyright Copyright (c) 2016 Morris Jobke <hey@morrisjobke.de> + * + * @author Morris Jobke <hey@morrisjobke.de> + * + * @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 OC\Repair; + +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class MoveUpdaterStepFile implements IRepairStep { + + /** @var \OCP\IConfig */ + protected $config; + + /** + * @param \OCP\IConfig $config + */ + public function __construct($config) { + $this->config = $config; + } + + public function getName() { + return 'Move .step file of updater to backup location'; + } + + public function run(IOutput $output) { + + $dataDir = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT); + $instanceId = $this->config->getSystemValue('instanceid', null); + + if(!is_string($instanceId) || empty($instanceId)) { + return; + } + + $updaterFolderPath = $dataDir . '/updater-' . $instanceId; + $stepFile = $updaterFolderPath . '/.step'; + if(file_exists($stepFile)) { + $output->info('.step file exists'); + + $previousStepFile = $updaterFolderPath . '/.step-previous-update'; + + // cleanup + if(file_exists($previousStepFile)) { + if(\OC_Helper::rmdirr($previousStepFile)) { + $output->info('.step-previous-update removed'); + } else { + $output->info('.step-previous-update can\'t be removed - abort move of .step file'); + return; + } + } + + // move step file + if(rename($stepFile, $previousStepFile)) { + $output->info('.step file moved to .step-previous-update'); + } else { + $output->warning('.step file can\'t be moved'); + } + } + } +} + diff --git a/lib/private/Repair/NC11/MoveAvatarBackgroundJob.php b/lib/private/Repair/NC11/MoveAvatarBackgroundJob.php new file mode 100644 index 00000000000..993235146c9 --- /dev/null +++ b/lib/private/Repair/NC11/MoveAvatarBackgroundJob.php @@ -0,0 +1,104 @@ +<?php +/** + * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @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 OC\Repair\NC11; + +use OC\BackgroundJob\QueuedJob; +use OCP\Files\File; +use OCP\Files\Folder; +use OCP\Files\IAppData; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\ILogger; +use OCP\IUser; +use OCP\IUserManager; + +class MoveAvatarsBackgroundJob extends QueuedJob { + + /** @var IUserManager */ + private $userManager; + + /** @var IRootFolder */ + private $rootFolder; + + /** @var IAppData */ + private $appData; + + /** @var ILogger */ + private $logger; + + /** + * MoveAvatars constructor. + */ + public function __construct() { + $this->userManager = \OC::$server->getUserManager(); + $this->rootFolder = \OC::$server->getRootFolder(); + $this->logger = \OC::$server->getLogger(); + $this->appData = \OC::$server->getAppDataDir('avatar'); + } + + public function run($arguments) { + $this->logger->info('Started migrating avatars to AppData folder'); + $this->moveAvatars(); + $this->logger->info('All avatars migrated to AppData folder'); + } + + private function moveAvatars() { + $counter = 0; + $this->userManager->callForAllUsers(function (IUser $user) use ($counter) { + if ($user->getLastLogin() !== 0) { + $uid = $user->getUID(); + + \OC\Files\Filesystem::initMountPoints($uid); + /** @var Folder $userFolder */ + $userFolder = $this->rootFolder->get($uid); + + try { + $userData = $this->appData->getFolder($uid); + } catch (NotFoundException $e) { + $userData = $this->appData->newFolder($uid); + } + + + $regex = '/^avatar\.([0-9]+\.)?(jpg|png)$/'; + $avatars = $userFolder->getDirectoryListing(); + + foreach ($avatars as $avatar) { + /** @var File $avatar */ + if (preg_match($regex, $avatar->getName())) { + /* + * This is not the most effective but it is the most abstract way + * to handle this. Avatars should be small anyways. + */ + $newAvatar = $userData->newFile($avatar->getName()); + $newAvatar->putContent($avatar->getContent()); + $avatar->delete(); + } + } + } + $counter++; + if ($counter % 100) { + $this->logger->info('{amount} avatars migrated', ['amount' => $counter]); + } + }); + } +} diff --git a/lib/private/Repair/NC11/MoveAvatars.php b/lib/private/Repair/NC11/MoveAvatars.php new file mode 100644 index 00000000000..44402b1be4f --- /dev/null +++ b/lib/private/Repair/NC11/MoveAvatars.php @@ -0,0 +1,64 @@ +<?php +/** + * @copyright 2016 Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @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 OC\Repair\NC11; + +use OC\SystemConfig; +use OCP\BackgroundJob\IJobList; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class MoveAvatars implements IRepairStep { + + /** @var IJobList */ + private $jobList; + + /** @var SystemConfig */ + private $systemConfig; + + /** + * MoveAvatars constructor. + * + * @param IJobList $jobList + * @param SystemConfig $systemConfig + */ + public function __construct(IJobList $jobList, + SystemConfig $systemConfig) { + $this->jobList = $jobList; + $this->systemConfig = $systemConfig; + } + + /** + * @return string + */ + public function getName() { + return 'Add mover avatar background job'; + } + + public function run(IOutput $output) { + if ($this->systemConfig->getValue('enable_avatars', true) === false) { + $output->info('Avatars are disabled'); + } else { + $this->jobList->add(MoveAvatarsBackgroundJob::class); + } + } +} diff --git a/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php b/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php new file mode 100644 index 00000000000..e6a39b12a42 --- /dev/null +++ b/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php @@ -0,0 +1,82 @@ +<?php +/** + * @copyright Copyright (c) 2016 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 OC\Security\CSP; + +use OC\Security\CSRF\CsrfTokenManager; +use OCP\IRequest; + +/** + * @package OC\Security\CSP + */ +class ContentSecurityPolicyNonceManager { + /** @var CsrfTokenManager */ + private $csrfTokenManager; + /** @var IRequest */ + private $request; + /** @var string */ + private $nonce = ''; + + /** + * @param CsrfTokenManager $csrfTokenManager + * @param IRequest $request + */ + public function __construct(CsrfTokenManager $csrfTokenManager, + IRequest $request) { + $this->csrfTokenManager = $csrfTokenManager; + $this->request = $request; + } + + /** + * Returns the current CSP nounce + * + * @return string + */ + public function getNonce() { + if($this->nonce === '') { + $this->nonce = base64_encode($this->csrfTokenManager->getToken()->getEncryptedValue()); + } + + return $this->nonce; + } + + /** + * Check if the browser supports CSP v3 + * + * @return bool + */ + public function browserSupportsCspV3() { + $browserWhitelist = [ + // Chrome 40+ + '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[4-9][0-9].[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+$/', + // Firefox 45+ + '/^Mozilla\/5\.0 \([^)]+\) Gecko\/[0-9.]+ Firefox\/(4[5-9]|[5-9][0-9])\.[0-9.]+$/', + // Safari 10+ + '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/1[0-9.]+ Safari\/[0-9.A-Z]+$/', + ]; + + if($this->request->isUserAgent($browserWhitelist)) { + return true; + } + + return false; + } +} diff --git a/lib/private/Security/CSRF/CsrfToken.php b/lib/private/Security/CSRF/CsrfToken.php index bf61e339f77..dce9a83b727 100644 --- a/lib/private/Security/CSRF/CsrfToken.php +++ b/lib/private/Security/CSRF/CsrfToken.php @@ -33,6 +33,8 @@ namespace OC\Security\CSRF; class CsrfToken { /** @var string */ private $value; + /** @var string */ + private $encryptedValue = ''; /** * @param string $value Value of the token. Can be encrypted or not encrypted. @@ -48,8 +50,12 @@ class CsrfToken { * @return string */ public function getEncryptedValue() { - $sharedSecret = base64_encode(random_bytes(strlen($this->value))); - return base64_encode($this->value ^ $sharedSecret) .':'.$sharedSecret; + if($this->encryptedValue === '') { + $sharedSecret = base64_encode(random_bytes(strlen($this->value))); + $this->encryptedValue = base64_encode($this->value ^ $sharedSecret) . ':' . $sharedSecret; + } + + return $this->encryptedValue; } /** diff --git a/lib/private/Security/CSRF/CsrfTokenManager.php b/lib/private/Security/CSRF/CsrfTokenManager.php index d621cc2c29f..b43ca3d3679 100644 --- a/lib/private/Security/CSRF/CsrfTokenManager.php +++ b/lib/private/Security/CSRF/CsrfTokenManager.php @@ -34,6 +34,8 @@ class CsrfTokenManager { private $tokenGenerator; /** @var SessionStorage */ private $sessionStorage; + /** @var CsrfToken|null */ + private $csrfToken = null; /** * @param CsrfTokenGenerator $tokenGenerator @@ -51,6 +53,10 @@ class CsrfTokenManager { * @return CsrfToken */ public function getToken() { + if(!is_null($this->csrfToken)) { + return $this->csrfToken; + } + if($this->sessionStorage->hasToken()) { $value = $this->sessionStorage->getToken(); } else { @@ -58,7 +64,8 @@ class CsrfTokenManager { $this->sessionStorage->setToken($value); } - return new CsrfToken($value); + $this->csrfToken = new CsrfToken($value); + return $this->csrfToken; } /** @@ -69,13 +76,15 @@ class CsrfTokenManager { public function refreshToken() { $value = $this->tokenGenerator->generateToken(); $this->sessionStorage->setToken($value); - return new CsrfToken($value); + $this->csrfToken = new CsrfToken($value); + return $this->csrfToken; } /** * Remove the current token from the storage. */ public function removeToken() { + $this->csrfToken = null; $this->sessionStorage->removeToken(); } diff --git a/lib/private/Server.php b/lib/private/Server.php index 494387ab6ca..21ec311401d 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -73,6 +73,7 @@ use OC\Security\Bruteforce\Throttler; use OC\Security\CertificateManager; use OC\Security\CSP\ContentSecurityPolicyManager; use OC\Security\Crypto; +use OC\Security\CSP\ContentSecurityPolicyNonceManager; use OC\Security\CSRF\CsrfTokenGenerator; use OC\Security\CSRF\CsrfTokenManager; use OC\Security\CSRF\TokenStorage\SessionStorage; @@ -175,10 +176,10 @@ class Server extends ServerContainer implements IServerContainer { $this->registerService('SystemTagObjectMapper', function (Server $c) { return $c->query('SystemTagManagerFactory')->getObjectMapper(); }); - $this->registerService('RootFolder', function () { + $this->registerService('RootFolder', function (Server $c) { $manager = \OC\Files\Filesystem::getMountManager(null); $view = new View(); - $root = new Root($manager, $view, null); + $root = new Root($manager, $view, null, $c->getUserMountCache()); $connector = new HookConnector($root, $view); $connector->viewToNode(); return $root; @@ -359,7 +360,7 @@ class Server extends ServerContainer implements IServerContainer { $this->registerService('AvatarManager', function (Server $c) { return new AvatarManager( $c->getUserManager(), - $c->getRootFolder(), + $c->getAppDataDir('avatar'), $c->getL10N('lib'), $c->getLogger(), $c->getConfig() @@ -407,8 +408,8 @@ class Server extends ServerContainer implements IServerContainer { return new CredentialsManager($c->getCrypto(), $c->getDatabaseConnection()); }); $this->registerService('DatabaseConnection', function (Server $c) { - $factory = new \OC\DB\ConnectionFactory(); $systemConfig = $c->getSystemConfig(); + $factory = new \OC\DB\ConnectionFactory($c->getConfig()); $type = $systemConfig->getValue('dbtype', 'sqlite'); if (!$factory->isValidType($type)) { throw new \OC\DatabaseException('Invalid database type'); @@ -708,6 +709,12 @@ class Server extends ServerContainer implements IServerContainer { $this->registerService('ContentSecurityPolicyManager', function (Server $c) { return new ContentSecurityPolicyManager(); }); + $this->registerService('ContentSecurityPolicyNonceManager', function(Server $c) { + return new ContentSecurityPolicyNonceManager( + $c->getCsrfTokenManager(), + $c->getRequest() + ); + }); $this->registerService('ShareManager', function(Server $c) { $config = $c->getConfig(); $factoryClass = $config->getSystemValue('sharing.managerFactory', '\OC\Share20\ProviderFactory'); @@ -742,6 +749,12 @@ class Server extends ServerContainer implements IServerContainer { ); return $manager; }); + $this->registerService(\OC\Files\AppData\Factory::class, function (Server $c) { + return new \OC\Files\AppData\Factory( + $c->getRootFolder(), + $c->getSystemConfig() + ); + }); } /** @@ -876,6 +889,7 @@ class Server extends ServerContainer implements IServerContainer { * Returns an app-specific view in ownClouds data directory * * @return \OCP\Files\Folder + * @deprecated since 9.2.0 use IAppData */ public function getAppFolder() { $dir = '/' . \OC_App::getCurrentApp(); @@ -1399,6 +1413,13 @@ class Server extends ServerContainer implements IServerContainer { } /** + * @return ContentSecurityPolicyNonceManager + */ + public function getContentSecurityPolicyNonceManager() { + return $this->query('ContentSecurityPolicyNonceManager'); + } + + /** * Not a public API as of 8.2, wait for 9.0 * * @return \OCA\Files_External\Service\BackendService @@ -1456,4 +1477,13 @@ class Server extends ServerContainer implements IServerContainer { public function getSettingsManager() { return $this->query('SettingsManager'); } + + /** + * @return \OCP\Files\IAppData + */ + public function getAppDataDir($app) { + /** @var \OC\Files\AppData\Factory $factory */ + $factory = $this->query(\OC\Files\AppData\Factory::class); + return $factory->get($app); + } } diff --git a/lib/private/Setup.php b/lib/private/Setup.php index 3b3a57c3e96..4c72fbc9623 100644 --- a/lib/private/Setup.php +++ b/lib/private/Setup.php @@ -410,6 +410,7 @@ class Setup { /** * Append the correct ErrorDocument path for Apache hosts + * @return bool True when success, False otherwise */ public static function updateHtaccess() { $config = \OC::$server->getConfig(); @@ -418,7 +419,7 @@ class Setup { if(\OC::$CLI) { $webRoot = $config->getSystemValue('overwrite.cli.url', ''); if($webRoot === '') { - return; + return false; } $webRoot = parse_url($webRoot, PHP_URL_PATH); $webRoot = rtrim($webRoot, '/'); @@ -472,9 +473,10 @@ class Setup { if ($content !== '') { //suppress errors in case we don't have permissions for it - @file_put_contents($setupHelper->pathToHtaccess(), $htaccessContent.$content . "\n"); + return (bool) @file_put_contents($setupHelper->pathToHtaccess(), $htaccessContent.$content . "\n"); } + return false; } public static function protectDataDirectory() { diff --git a/lib/private/Setup/AbstractDatabase.php b/lib/private/Setup/AbstractDatabase.php index 310f74d4c0c..47c3e5ee1c6 100644 --- a/lib/private/Setup/AbstractDatabase.php +++ b/lib/private/Setup/AbstractDatabase.php @@ -134,7 +134,7 @@ abstract class AbstractDatabase { } $connectionParams = array_merge($connectionParams, $configOverwrite); - $cf = new ConnectionFactory(); + $cf = new ConnectionFactory($this->config); return $cf->getConnection($this->config->getSystemValue('dbtype', 'sqlite'), $connectionParams); } diff --git a/lib/private/Setup/MySQL.php b/lib/private/Setup/MySQL.php index 4ad6926c2d7..c022616d8b3 100644 --- a/lib/private/Setup/MySQL.php +++ b/lib/private/Setup/MySQL.php @@ -58,8 +58,9 @@ class MySQL extends AbstractDatabase { try{ $name = $this->dbName; $user = $this->dbUser; - //we can't use OC_BD functions here because we need to connect as the administrative user. - $query = "CREATE DATABASE IF NOT EXISTS `$name` CHARACTER SET utf8 COLLATE utf8_bin;"; + //we can't use OC_DB functions here because we need to connect as the administrative user. + $characterSet = \OC::$server->getSystemConfig()->getValue('mysql.utf8mb4', false) ? 'utf8mb4' : 'utf8'; + $query = "CREATE DATABASE IF NOT EXISTS `$name` CHARACTER SET $characterSet COLLATE ${characterSet}_bin;"; $connection->executeUpdate($query); } catch (\Exception $ex) { $this->logger->error('Database creation failed: {error}', [ diff --git a/lib/private/Share/Share.php b/lib/private/Share/Share.php index 9210dfd1fd1..33801cd6347 100644 --- a/lib/private/Share/Share.php +++ b/lib/private/Share/Share.php @@ -1059,7 +1059,7 @@ class Share extends Constants { if (isset($groupShare['file_target'])) { $shareTmp['fileTarget'] = $groupShare['file_target']; } - $listOfUnsharedItems = array_merge($listOfUnsharedItems, array($groupShare)); + $listOfUnsharedItems = array_merge($listOfUnsharedItems, [$shareTmp]); $itemUnshared = true; } elseif (!$itemUnshared && isset($uniqueGroupShare)) { $query = \OC_DB::prepare('UPDATE `*PREFIX*share` SET `permissions` = ? WHERE `id` = ?'); @@ -1074,7 +1074,7 @@ class Share extends Constants { if (isset($uniqueGroupShare['file_target'])) { $shareTmp['fileTarget'] = $uniqueGroupShare['file_target']; } - $listOfUnsharedItems = array_merge($listOfUnsharedItems, array($uniqueGroupShare)); + $listOfUnsharedItems = array_merge($listOfUnsharedItems, [$shareTmp]); $itemUnshared = true; } diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php index f33c297b02d..56b9d5b1ee8 100644 --- a/lib/private/Share20/DefaultShareProvider.php +++ b/lib/private/Share20/DefaultShareProvider.php @@ -543,7 +543,7 @@ class DefaultShareProvider implements IShareProvider { // If the recipient is set for a group share resolve to that user if ($recipientId !== null && $share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) { - $share = $this->resolveGroupShare($share, $recipientId); + $share = $this->resolveGroupShares([$share], $recipientId)[0]; } return $share; @@ -583,6 +583,25 @@ class DefaultShareProvider implements IShareProvider { } /** + * Returns whether the given database result can be interpreted as + * a share with accessible file (not trashed, not deleted) + */ + private function isAccessibleResult($data) { + // exclude shares leading to deleted file entries + if ($data['fileid'] === null) { + return false; + } + + // exclude shares leading to trashbin on home storages + $pathSections = explode('/', $data['path'], 2); + // FIXME: would not detect rare md5'd home storage case properly + if ($pathSections[0] !== 'files' && explode(':', $data['storage_string_id'], 2)[0] === 'home') { + return false; + } + return true; + } + + /** * @inheritdoc */ public function getSharedWith($userId, $shareType, $node, $limit, $offset) { @@ -592,11 +611,14 @@ class DefaultShareProvider implements IShareProvider { if ($shareType === \OCP\Share::SHARE_TYPE_USER) { //Get shares directly with this user $qb = $this->dbConn->getQueryBuilder(); - $qb->select('*') - ->from('share'); + $qb->select('s.*', 'f.fileid', 'f.path') + ->selectAlias('st.id', 'storage_string_id') + ->from('share', 's') + ->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid')) + ->leftJoin('f', 'storages', 'st', $qb->expr()->eq('f.storage', 'st.numeric_id')); // Order by id - $qb->orderBy('id'); + $qb->orderBy('s.id'); // Set limit and offset if ($limit !== -1) { @@ -619,7 +641,9 @@ class DefaultShareProvider implements IShareProvider { $cursor = $qb->execute(); while($data = $cursor->fetch()) { - $shares[] = $this->createShare($data); + if ($this->isAccessibleResult($data)) { + $shares[] = $this->createShare($data); + } } $cursor->closeCursor(); @@ -640,9 +664,12 @@ class DefaultShareProvider implements IShareProvider { } $qb = $this->dbConn->getQueryBuilder(); - $qb->select('*') - ->from('share') - ->orderBy('id') + $qb->select('s.*', 'f.fileid', 'f.path') + ->selectAlias('st.id', 'storage_string_id') + ->from('share', 's') + ->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid')) + ->leftJoin('f', 'storages', 'st', $qb->expr()->eq('f.storage', 'st.numeric_id')) + ->orderBy('s.id') ->setFirstResult(0); if ($limit !== -1) { @@ -672,18 +699,18 @@ class DefaultShareProvider implements IShareProvider { $offset--; continue; } - $shares2[] = $this->createShare($data); + + if ($this->isAccessibleResult($data)) { + $shares2[] = $this->createShare($data); + } } $cursor->closeCursor(); } /* * Resolve all group shares to user specific shares - * TODO: Optmize this! */ - foreach($shares2 as $share) { - $shares[] = $this->resolveGroupShare($share, $userId); - } + $shares = $this->resolveGroupShares($shares2, $userId); } else { throw new BackendError('Invalid backend'); } @@ -772,37 +799,59 @@ class DefaultShareProvider implements IShareProvider { } /** - * Resolve a group share to a user specific share - * Thus if the user moved their group share make sure this is properly reflected here. - * - * @param \OCP\Share\IShare $share - * @param string $userId - * @return Share Returns the updated share if one was found else return the original share. + * @param Share[] $shares + * @param $userId + * @return Share[] The updates shares if no update is found for a share return the original */ - private function resolveGroupShare(\OCP\Share\IShare $share, $userId) { - $qb = $this->dbConn->getQueryBuilder(); + private function resolveGroupShares($shares, $userId) { + $result = []; - $stmt = $qb->select('*') - ->from('share') - ->where($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId()))) - ->andWhere($qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_USERGROUP))) - ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId))) - ->andWhere($qb->expr()->orX( - $qb->expr()->eq('item_type', $qb->createNamedParameter('file')), - $qb->expr()->eq('item_type', $qb->createNamedParameter('folder')) - )) - ->setMaxResults(1) - ->execute(); + $start = 0; + while(true) { + /** @var Share[] $shareSlice */ + $shareSlice = array_slice($shares, $start, 100); + $start += 100; - $data = $stmt->fetch(); - $stmt->closeCursor(); + if ($shareSlice === []) { + break; + } + + /** @var int[] $ids */ + $ids = []; + /** @var Share[] $shareMap */ + $shareMap = []; + + foreach ($shareSlice as $share) { + $ids[] = (int)$share->getId(); + $shareMap[$share->getId()] = $share; + } + + $qb = $this->dbConn->getQueryBuilder(); + + $query = $qb->select('*') + ->from('share') + ->where($qb->expr()->in('parent', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY))) + ->andWhere($qb->expr()->eq('share_with', $qb->createNamedParameter($userId))) + ->andWhere($qb->expr()->orX( + $qb->expr()->eq('item_type', $qb->createNamedParameter('file')), + $qb->expr()->eq('item_type', $qb->createNamedParameter('folder')) + )); + + $stmt = $query->execute(); + + while($data = $stmt->fetch()) { + $shareMap[$data['parent']]->setPermissions((int)$data['permissions']); + $shareMap[$data['parent']]->setTarget($data['file_target']); + } + + $stmt->closeCursor(); - if ($data !== false) { - $share->setPermissions((int)$data['permissions']); - $share->setTarget($data['file_target']); + foreach ($shareMap as $share) { + $result[] = $share; + } } - return $share; + return $result; } /** diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index b4fe69a83e2..22cf5a3f65a 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -614,8 +614,11 @@ class Manager implements IManager { throw new \Exception($error); } + $oldShare = $share; $provider = $this->factory->getProviderForType($share->getShareType()); $share = $provider->create($share); + //reuse the node we already have + $share->setNode($oldShare->getNode()); // Post share hook $postHookData = [ diff --git a/lib/private/Share20/Share.php b/lib/private/Share20/Share.php index c565809c078..e3e8482f4e1 100644 --- a/lib/private/Share20/Share.php +++ b/lib/private/Share20/Share.php @@ -160,7 +160,7 @@ class Share implements \OCP\Share\IShare { $nodes = $userFolder->getById($this->fileId); if (empty($nodes)) { - throw new NotFoundException(); + throw new NotFoundException('Node for share not found, fileid: ' . $this->fileId); } $this->node = $nodes[0]; diff --git a/lib/private/Template/JSConfigHelper.php b/lib/private/Template/JSConfigHelper.php new file mode 100644 index 00000000000..a7f8c251cee --- /dev/null +++ b/lib/private/Template/JSConfigHelper.php @@ -0,0 +1,243 @@ +<?php +/** + * @copyright Copyright (c) 2016, Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @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 OC\Template; + +use bantu\IniGetWrapper\IniGetWrapper; +use OCP\App\IAppManager; +use OCP\IConfig; +use OCP\IGroupManager; +use OCP\IL10N; +use OCP\IURLGenerator; +use OCP\IUser; + +class JSConfigHelper { + + /** @var IL10N */ + private $l; + + /** @var \OC_Defaults */ + private $defaults; + + /** @var IAppManager */ + private $appManager; + + /** @var IUser */ + private $currentUser; + + /** @var IConfig */ + private $config; + + /** @var IGroupManager */ + private $groupManager; + + /** @var IniGetWrapper */ + private $iniWrapper; + + /** @var IURLGenerator */ + private $urlGenerator; + + /** + * @param IL10N $l + * @param \OC_Defaults $defaults + * @param IAppManager $appManager + * @param IUser|null $currentUser + * @param IConfig $config + * @param IGroupManager $groupManager + * @param IniGetWrapper $iniWrapper + * @param IURLGenerator $urlGenerator + */ + public function __construct(IL10N $l, + \OC_Defaults $defaults, + IAppManager $appManager, + $currentUser, + IConfig $config, + IGroupManager $groupManager, + IniGetWrapper $iniWrapper, + IURLGenerator $urlGenerator) { + $this->l = $l; + $this->defaults = $defaults; + $this->appManager = $appManager; + $this->currentUser = $currentUser; + $this->config = $config; + $this->groupManager = $groupManager; + $this->iniWrapper = $iniWrapper; + $this->urlGenerator = $urlGenerator; + } + + public function getConfig() { + + if ($this->currentUser !== null) { + $uid = $this->currentUser->getUID(); + } else { + $uid = null; + } + + // Get the config + $apps_paths = []; + + if ($this->currentUser === null) { + $apps = $this->appManager->getInstalledApps(); + } else { + $apps = $this->appManager->getEnabledAppsForUser($this->currentUser); + } + + foreach($apps as $app) { + $apps_paths[$app] = \OC_App::getAppWebPath($app); + } + + $defaultExpireDateEnabled = $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes'; + $defaultExpireDate = $enforceDefaultExpireDate = null; + if ($defaultExpireDateEnabled) { + $defaultExpireDate = (int) $this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7'); + $enforceDefaultExpireDate = $this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes'; + } + $outgoingServer2serverShareEnabled = $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes'; + + $countOfDataLocation = 0; + $dataLocation = str_replace(\OC::$SERVERROOT .'/', '', $this->config->getSystemValue('datadirectory', ''), $countOfDataLocation); + if($countOfDataLocation !== 1 || !$this->groupManager->isAdmin($uid)) { + $dataLocation = false; + } + + $array = [ + "oc_debug" => $this->config->getSystemValue('debug', false) ? 'true' : 'false', + "oc_isadmin" => $this->groupManager->isAdmin($uid) ? 'true' : 'false', + "oc_dataURL" => is_string($dataLocation) ? "\"".$dataLocation."\"" : 'false', + "oc_webroot" => "\"".\OC::$WEBROOT."\"", + "oc_appswebroots" => str_replace('\\/', '/', json_encode($apps_paths)), // Ugly unescape slashes waiting for better solution + "datepickerFormatDate" => json_encode($this->l->l('jsdate', null)), + "dayNames" => json_encode([ + (string)$this->l->t('Sunday'), + (string)$this->l->t('Monday'), + (string)$this->l->t('Tuesday'), + (string)$this->l->t('Wednesday'), + (string)$this->l->t('Thursday'), + (string)$this->l->t('Friday'), + (string)$this->l->t('Saturday') + ]), + "dayNamesShort" => json_encode([ + (string)$this->l->t('Sun.'), + (string)$this->l->t('Mon.'), + (string)$this->l->t('Tue.'), + (string)$this->l->t('Wed.'), + (string)$this->l->t('Thu.'), + (string)$this->l->t('Fri.'), + (string)$this->l->t('Sat.') + ]), + "dayNamesMin" => json_encode([ + (string)$this->l->t('Su'), + (string)$this->l->t('Mo'), + (string)$this->l->t('Tu'), + (string)$this->l->t('We'), + (string)$this->l->t('Th'), + (string)$this->l->t('Fr'), + (string)$this->l->t('Sa') + ]), + "monthNames" => json_encode([ + (string)$this->l->t('January'), + (string)$this->l->t('February'), + (string)$this->l->t('March'), + (string)$this->l->t('April'), + (string)$this->l->t('May'), + (string)$this->l->t('June'), + (string)$this->l->t('July'), + (string)$this->l->t('August'), + (string)$this->l->t('September'), + (string)$this->l->t('October'), + (string)$this->l->t('November'), + (string)$this->l->t('December') + ]), + "monthNamesShort" => json_encode([ + (string)$this->l->t('Jan.'), + (string)$this->l->t('Feb.'), + (string)$this->l->t('Mar.'), + (string)$this->l->t('Apr.'), + (string)$this->l->t('May.'), + (string)$this->l->t('Jun.'), + (string)$this->l->t('Jul.'), + (string)$this->l->t('Aug.'), + (string)$this->l->t('Sep.'), + (string)$this->l->t('Oct.'), + (string)$this->l->t('Nov.'), + (string)$this->l->t('Dec.') + ]), + "firstDay" => json_encode($this->l->l('firstday', null)) , + "oc_config" => json_encode([ + 'session_lifetime' => min($this->config->getSystemValue('session_lifetime', $this->iniWrapper->getNumeric('session.gc_maxlifetime')), $this->iniWrapper->getNumeric('session.gc_maxlifetime')), + 'session_keepalive' => $this->config->getSystemValue('session_keepalive', true), + 'version' => implode('.', \OCP\Util::getVersion()), + 'versionstring' => \OC_Util::getVersionString(), + 'enable_avatars' => $this->config->getSystemValue('enable_avatars', true) === true, + 'lost_password_link'=> $this->config->getSystemValue('lost_password_link', null), + 'modRewriteWorking' => (getenv('front_controller_active') === 'true'), + ]), + "oc_appconfig" => json_encode([ + 'core' => [ + 'defaultExpireDateEnabled' => $defaultExpireDateEnabled, + 'defaultExpireDate' => $defaultExpireDate, + 'defaultExpireDateEnforced' => $enforceDefaultExpireDate, + 'enforcePasswordForPublicLink' => \OCP\Util::isPublicLinkPasswordRequired(), + 'sharingDisabledForUser' => \OCP\Util::isSharingDisabledForUser(), + 'resharingAllowed' => \OCP\Share::isResharingAllowed(), + 'remoteShareAllowed' => $outgoingServer2serverShareEnabled, + 'federatedCloudShareDoc' => $this->urlGenerator->linkToDocs('user-sharing-federated'), + 'allowGroupSharing' => \OC::$server->getShareManager()->allowGroupSharing() + ] + ]), + "oc_defaults" => json_encode([ + 'entity' => $this->defaults->getEntity(), + 'name' => $this->defaults->getName(), + 'title' => $this->defaults->getTitle(), + 'baseUrl' => $this->defaults->getBaseUrl(), + 'syncClientUrl' => $this->defaults->getSyncClientUrl(), + 'docBaseUrl' => $this->defaults->getDocBaseUrl(), + 'docPlaceholderUrl' => $this->defaults->buildDocLinkToKey('PLACEHOLDER'), + 'slogan' => $this->defaults->getSlogan(), + 'logoClaim' => $this->defaults->getLogoClaim(), + 'shortFooter' => $this->defaults->getShortFooter(), + 'longFooter' => $this->defaults->getLongFooter(), + 'folder' => \OC_Util::getTheme(), + ]), + ]; + + if ($this->currentUser !== null) { + $array['oc_userconfig'] = json_encode([ + 'avatar' => [ + 'version' => (int)$this->config->getUserValue($uid, 'avatar', 'version', 0), + ] + ]); + } + + // Allow hooks to modify the output values + \OC_Hook::emit('\OCP\Config', 'js', array('array' => &$array)); + + $result = ''; + + // Echo it + foreach ($array as $setting => $value) { + $result .= 'var '. $setting . '='. $value . ';' . PHP_EOL; + } + + return $result; + } +} diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php index da845d80d04..9f89174e7f9 100644 --- a/lib/private/TemplateLayout.php +++ b/lib/private/TemplateLayout.php @@ -43,6 +43,7 @@ use Assetic\Filter\CssMinFilter; use Assetic\Filter\CssRewriteFilter; use Assetic\Filter\JSqueezeFilter; use Assetic\Filter\SeparatorFilter; +use OC\Template\JSConfigHelper; class TemplateLayout extends \OC_Template { @@ -142,7 +143,22 @@ class TemplateLayout extends \OC_Template { $jsFiles = self::findJavascriptFiles(\OC_Util::$scripts); $this->assign('jsfiles', array()); if ($this->config->getSystemValue('installed', false) && $renderAs != 'error') { - $this->append( 'jsfiles', \OC::$server->getURLGenerator()->linkToRoute('js_config', ['v' => self::$versionHash])); + if (\OC::$server->getContentSecurityPolicyNonceManager()->browserSupportsCspV3()) { + $jsConfigHelper = new JSConfigHelper( + \OC::$server->getL10N('core'), + \OC::$server->getThemingDefaults(), + \OC::$server->getAppManager(), + \OC::$server->getUserSession()->getUser(), + \OC::$server->getConfig(), + \OC::$server->getGroupManager(), + \OC::$server->getIniWrapper(), + \OC::$server->getURLGenerator() + ); + $this->assign('inline_ocjs', $jsConfigHelper->getConfig()); + $this->assign('foo', 'bar'); + } else { + $this->append('jsfiles', \OC::$server->getURLGenerator()->linkToRoute('core.OCJS.getConfig', ['v' => self::$versionHash])); + } } foreach($jsFiles as $info) { $web = $info[1]; diff --git a/lib/private/Updater.php b/lib/private/Updater.php index 609e965bfad..646fc031a83 100644 --- a/lib/private/Updater.php +++ b/lib/private/Updater.php @@ -35,8 +35,8 @@ use OC\Hooks\BasicEmitter; use OC\IntegrityCheck\Checker; use OC_App; use OCP\IConfig; -use OC\Setup; use OCP\ILogger; +use OCP\Util; use Symfony\Component\EventDispatcher\GenericEvent; /** @@ -60,12 +60,6 @@ class Updater extends BasicEmitter { private $checker; /** @var bool */ - private $simulateStepEnabled; - - /** @var bool */ - private $updateStepEnabled; - - /** @var bool */ private $skip3rdPartyAppsDisable; private $logLevelNames = [ @@ -87,29 +81,6 @@ class Updater extends BasicEmitter { $this->log = $log; $this->config = $config; $this->checker = $checker; - $this->simulateStepEnabled = true; - $this->updateStepEnabled = true; - } - - /** - * Sets whether the database migration simulation must - * be enabled. - * This can be set to false to skip this test. - * - * @param bool $flag true to enable simulation, false otherwise - */ - public function setSimulateStepEnabled($flag) { - $this->simulateStepEnabled = $flag; - } - - /** - * Sets whether the update must be performed. - * This can be set to false to skip the actual update. - * - * @param bool $flag true to enable update, false otherwise - */ - public function setUpdateStepEnabled($flag) { - $this->updateStepEnabled = $flag; } /** @@ -131,9 +102,9 @@ class Updater extends BasicEmitter { public function upgrade() { $this->emitRepairEvents(); - $logLevel = $this->config->getSystemValue('loglevel', \OCP\Util::WARN); + $logLevel = $this->config->getSystemValue('loglevel', Util::WARN); $this->emit('\OC\Updater', 'setDebugLogLevel', [ $logLevel, $this->logLevelNames[$logLevel] ]); - $this->config->setSystemValue('loglevel', \OCP\Util::DEBUG); + $this->config->setSystemValue('loglevel', Util::DEBUG); $wasMaintenanceModeEnabled = $this->config->getSystemValue('maintenance', false); @@ -254,68 +225,48 @@ class Updater extends BasicEmitter { $repair = new Repair(Repair::getBeforeUpgradeRepairSteps(), \OC::$server->getEventDispatcher()); $repair->run(); - // simulate DB upgrade - if ($this->simulateStepEnabled) { - $this->checkCoreUpgrade(); - - // simulate apps DB upgrade - $this->checkAppUpgrade($currentVersion); + $this->doCoreUpgrade(); + try { + // TODO: replace with the new repair step mechanism https://github.com/owncloud/core/pull/24378 + Setup::installBackgroundJobs(); + } catch (\Exception $e) { + throw new \Exception($e->getMessage()); } - if ($this->updateStepEnabled) { - $this->doCoreUpgrade(); - - try { - // TODO: replace with the new repair step mechanism https://github.com/owncloud/core/pull/24378 - Setup::installBackgroundJobs(); - } catch (\Exception $e) { - throw new \Exception($e->getMessage()); - } - - // update all shipped apps - $disabledApps = $this->checkAppsRequirements(); - $this->doAppUpgrade(); + // update all shipped apps + $disabledApps = $this->checkAppsRequirements(); + $this->doAppUpgrade(); - // upgrade appstore apps - $this->upgradeAppStoreApps($disabledApps); - - // install new shipped apps on upgrade - OC_App::loadApps('authentication'); - $errors = Installer::installShippedApps(true); - foreach ($errors as $appId => $exception) { - /** @var \Exception $exception */ - $this->log->logException($exception, ['app' => $appId]); - $this->emit('\OC\Updater', 'failure', [$appId . ': ' . $exception->getMessage()]); - } + // upgrade appstore apps + $this->upgradeAppStoreApps($disabledApps); - // post-upgrade repairs - $repair = new Repair(Repair::getRepairSteps(), \OC::$server->getEventDispatcher()); - $repair->run(); + // install new shipped apps on upgrade + OC_App::loadApps('authentication'); + $errors = Installer::installShippedApps(true); + foreach ($errors as $appId => $exception) { + /** @var \Exception $exception */ + $this->log->logException($exception, ['app' => $appId]); + $this->emit('\OC\Updater', 'failure', [$appId . ': ' . $exception->getMessage()]); + } - //Invalidate update feed - $this->config->setAppValue('core', 'lastupdatedat', 0); + // post-upgrade repairs + $repair = new Repair(Repair::getRepairSteps(), \OC::$server->getEventDispatcher()); + $repair->run(); - // Check for code integrity if not disabled - if(\OC::$server->getIntegrityCodeChecker()->isCodeCheckEnforced()) { - $this->emit('\OC\Updater', 'startCheckCodeIntegrity'); - $this->checker->runInstanceVerification(); - $this->emit('\OC\Updater', 'finishedCheckCodeIntegrity'); - } + //Invalidate update feed + $this->config->setAppValue('core', 'lastupdatedat', 0); - // only set the final version if everything went well - $this->config->setSystemValue('version', implode('.', \OCP\Util::getVersion())); - $this->config->setAppValue('core', 'vendor', $this->getVendor()); + // Check for code integrity if not disabled + if(\OC::$server->getIntegrityCodeChecker()->isCodeCheckEnforced()) { + $this->emit('\OC\Updater', 'startCheckCodeIntegrity'); + $this->checker->runInstanceVerification(); + $this->emit('\OC\Updater', 'finishedCheckCodeIntegrity'); } - } - protected function checkCoreUpgrade() { - $this->emit('\OC\Updater', 'dbSimulateUpgradeBefore'); - - // simulate core DB upgrade - \OC_DB::simulateUpdateDbFromStructure(\OC::$SERVERROOT . '/db_structure.xml'); - - $this->emit('\OC\Updater', 'dbSimulateUpgrade'); + // only set the final version if everything went well + $this->config->setSystemValue('version', implode('.', Util::getVersion())); + $this->config->setAppValue('core', 'vendor', $this->getVendor()); } protected function doCoreUpgrade() { @@ -424,7 +375,7 @@ class Updater extends BasicEmitter { private function checkAppsRequirements() { $isCoreUpgrade = $this->isCodeUpgrade(); $apps = OC_App::getEnabledApps(); - $version = \OCP\Util::getVersion(); + $version = Util::getVersion(); $disabledApps = []; foreach ($apps as $app) { // check if the app is compatible with this version of ownCloud @@ -461,7 +412,7 @@ class Updater extends BasicEmitter { */ private function isCodeUpgrade() { $installedVersion = $this->config->getSystemValue('version', '0.0.0'); - $currentVersion = implode('.', \OCP\Util::getVersion()); + $currentVersion = implode('.', Util::getVersion()); if (version_compare($currentVersion, $installedVersion, '>')) { return true; } diff --git a/lib/private/Updater/VersionCheck.php b/lib/private/Updater/VersionCheck.php index e846052a300..f66e109fd26 100644 --- a/lib/private/Updater/VersionCheck.php +++ b/lib/private/Updater/VersionCheck.php @@ -59,7 +59,7 @@ class VersionCheck { return json_decode($this->config->getAppValue('core', 'lastupdateResult'), true); } - $updaterUrl = $this->config->getSystemValue('updater.server.url', 'https://updates.nextcloud.com/update-server/'); + $updaterUrl = $this->config->getSystemValue('updater.server.url', 'https://updates.nextcloud.com/updater_server/'); $this->config->setAppValue('core', 'lastupdatedat', time()); @@ -89,6 +89,7 @@ class VersionCheck { $tmp['versionstring'] = (string)$data->versionstring; $tmp['url'] = (string)$data->url; $tmp['web'] = (string)$data->web; + $tmp['autoupdater'] = (string)$data->autoupdater; } else { libxml_clear_errors(); } diff --git a/lib/private/User/Database.php b/lib/private/User/Database.php index eba7beffeae..28cb3302858 100644 --- a/lib/private/User/Database.php +++ b/lib/private/User/Database.php @@ -94,6 +94,9 @@ class Database extends Backend implements IUserBackend { $query = \OC_DB::prepare('INSERT INTO `*PREFIX*users` ( `uid`, `password` ) VALUES( ?, ? )'); $result = $query->execute(array($uid, \OC::$server->getHasher()->hash($password))); + // Clear cache + unset($this->cache[$uid]); + return $result ? true : false; } @@ -234,7 +237,7 @@ class Database extends Backend implements IUserBackend { * @return boolean */ private function loadUser($uid) { - if (empty($this->cache[$uid])) { + if (!isset($this->cache[$uid])) { $query = \OC_DB::prepare('SELECT `uid`, `displayname` FROM `*PREFIX*users` WHERE LOWER(`uid`) = LOWER(?)'); $result = $query->execute(array($uid)); @@ -243,6 +246,8 @@ class Database extends Backend implements IUserBackend { return false; } + $this->cache[$uid] = false; + while ($row = $result->fetchRow()) { $this->cache[$uid]['uid'] = $row['uid']; $this->cache[$uid]['displayname'] = $row['displayname']; @@ -284,7 +289,7 @@ class Database extends Backend implements IUserBackend { */ public function userExists($uid) { $this->loadUser($uid); - return !empty($this->cache[$uid]); + return $this->cache[$uid] !== false; } /** diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index dec959820f8..a213ee48c2a 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -362,6 +362,9 @@ class Session implements IUserSession, Emitter { $user = $this->manager->get($username); if (is_null($user)) { $users = $this->manager->getByEmail($username); + if (empty($users)) { + return false; + } if (count($users) !== 1) { return true; } @@ -597,7 +600,6 @@ class Session implements IUserSession, Emitter { } $dbToken->setLastCheck($now); - $this->tokenProvider->updateToken($dbToken); return true; } @@ -608,7 +610,6 @@ class Session implements IUserSession, Emitter { return false; } $dbToken->setLastCheck($now); - $this->tokenProvider->updateToken($dbToken); return true; } diff --git a/lib/private/User/User.php b/lib/private/User/User.php index 94ac8c13621..68787ce60f8 100644 --- a/lib/private/User/User.php +++ b/lib/private/User/User.php @@ -214,10 +214,14 @@ class User implements IUser { \OC::$server->getCommentsManager()->deleteReferencesOfActor('users', $this->uid); \OC::$server->getCommentsManager()->deleteReadMarksFromUser($this); - } - if ($this->emitter) { - $this->emitter->emit('\OC\User', 'postDelete', array($this)); + $notification = \OC::$server->getNotificationManager()->createNotification(); + $notification->setUser($this->uid); + \OC::$server->getNotificationManager()->markProcessed($notification); + + if ($this->emitter) { + $this->emitter->emit('\OC\User', 'postDelete', array($this)); + } } return !($result === false); } diff --git a/lib/private/legacy/app.php b/lib/private/legacy/app.php index d964212f3bb..d25534aa822 100644 --- a/lib/private/legacy/app.php +++ b/lib/private/legacy/app.php @@ -47,6 +47,7 @@ * */ use OC\App\DependencyAnalyzer; +use OC\App\InfoParser; use OC\App\Platform; use OC\Installer; use OC\OCSClient; @@ -333,9 +334,16 @@ class OC_App { * This function set an app as enabled in appconfig. */ public static function enable($app, $groups = null) { - self::$enabledAppsCache = array(); // flush + self::$enabledAppsCache = []; // flush if (!Installer::isInstalled($app)) { $app = self::installApp($app); + } else { + // check for required dependencies + $config = \OC::$server->getConfig(); + $l = \OC::$server->getL10N('core'); + $info = self::getAppInfo($app); + + self::checkAppDependencies($config, $l, $info); } $appManager = \OC::$server->getAppManager(); @@ -662,15 +670,16 @@ class OC_App { * Read all app metadata from the info.xml file * * @param string $appId id of the app or the path of the info.xml file - * @param boolean $path (optional) + * @param bool $path + * @param string $lang * @return array|null * @note all data is read from info.xml, not just pre-defined fields */ - public static function getAppInfo($appId, $path = false) { + public static function getAppInfo($appId, $path = false, $lang = null) { if ($path) { $file = $appId; } else { - if (isset(self::$appInfo[$appId])) { + if ($lang === null && isset(self::$appInfo[$appId])) { return self::$appInfo[$appId]; } $appPath = self::getAppPath($appId); @@ -680,11 +689,11 @@ class OC_App { $file = $appPath . '/appinfo/info.xml'; } - $parser = new \OC\App\InfoParser(\OC::$server->getURLGenerator()); + $parser = new InfoParser(\OC::$server->getMemCacheFactory()->create('core.appinfo')); $data = $parser->parse($file); if (is_array($data)) { - $data = OC_App::parseAppInfo($data); + $data = OC_App::parseAppInfo($data, $lang); } if(isset($data['ocsid'])) { $storedId = \OC::$server->getConfig()->getAppValue($appId, 'ocsid'); @@ -693,7 +702,9 @@ class OC_App { } } - self::$appInfo[$appId] = $data; + if ($lang === null) { + self::$appInfo[$appId] = $data; + } return $data; } @@ -843,11 +854,13 @@ class OC_App { //we don't want to show configuration for these $blacklist = \OC::$server->getAppManager()->getAlwaysEnabledApps(); $appList = array(); + $langCode = \OC::$server->getL10N('core')->getLanguageCode(); + $urlGenerator = \OC::$server->getURLGenerator(); foreach ($installedApps as $app) { if (array_search($app, $blacklist) === false) { - $info = OC_App::getAppInfo($app); + $info = OC_App::getAppInfo($app, false, $langCode); if (!is_array($info)) { \OCP\Util::writeLog('core', 'Could not read app info file for app "' . $app . '"', \OCP\Util::ERROR); continue; @@ -896,6 +909,19 @@ class OC_App { } } } + // fix documentation + if (isset($info['documentation']) && is_array($info['documentation'])) { + foreach ($info['documentation'] as $key => $url) { + // If it is not an absolute URL we assume it is a key + // i.e. admin-ldap will get converted to go.php?to=admin-ldap + if (stripos($url, 'https://') !== 0 && stripos($url, 'http://') !== 0) { + $url = $urlGenerator->linkToDocs($url); + } + + $info['documentation'][$key] = $url; + } + } + $info['version'] = OC_App::getAppVersion($app); $appList[] = $info; } @@ -1167,16 +1193,7 @@ class OC_App { } // check for required dependencies - $dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l); - $missing = $dependencyAnalyzer->analyze($info); - if (!empty($missing)) { - $missingMsg = join(PHP_EOL, $missing); - throw new \Exception( - $l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s', - array($info['name'], $missingMsg) - ) - ); - } + self::checkAppDependencies($config, $l, $info); $config->setAppValue($app, 'enabled', 'yes'); if (isset($appData['id'])) { @@ -1327,13 +1344,69 @@ class OC_App { } } + protected static function findBestL10NOption($options, $lang) { + $fallback = $similarLangFallback = $englishFallback = false; + + $lang = strtolower($lang); + $similarLang = $lang; + if (strpos($similarLang, '_')) { + // For "de_DE" we want to find "de" and the other way around + $similarLang = substr($lang, 0, strpos($lang, '_')); + } + + foreach ($options as $option) { + if (is_array($option)) { + if ($fallback === false) { + $fallback = $option['@value']; + } + + if (!isset($option['@attributes']['lang'])) { + continue; + } + + $attributeLang = strtolower($option['@attributes']['lang']); + if ($attributeLang === $lang) { + return $option['@value']; + } + + if ($attributeLang === $similarLang) { + $similarLangFallback = $option['@value']; + } else if (strpos($attributeLang, $similarLang . '_') === 0) { + if ($similarLangFallback === false) { + $similarLangFallback = $option['@value']; + } + } + } else { + $englishFallback = $option; + } + } + + if ($similarLangFallback !== false) { + return $similarLangFallback; + } else if ($englishFallback !== false) { + return $englishFallback; + } + return (string) $fallback; + } + /** * parses the app data array and enhanced the 'description' value * * @param array $data the app data + * @param string $lang * @return array improved app data */ - public static function parseAppInfo(array $data) { + public static function parseAppInfo(array $data, $lang = null) { + + if ($lang && isset($data['name']) && is_array($data['name'])) { + $data['name'] = self::findBestL10NOption($data['name'], $lang); + } + if ($lang && isset($data['summary']) && is_array($data['summary'])) { + $data['summary'] = self::findBestL10NOption($data['summary'], $lang); + } + if ($lang && isset($data['description']) && is_array($data['description'])) { + $data['description'] = self::findBestL10NOption($data['description'], $lang); + } // just modify the description if it is available // otherwise this will create a $data element with an empty 'description' @@ -1363,4 +1436,23 @@ class OC_App { return $data; } + + /** + * @param $config + * @param $l + * @param $info + * @throws Exception + */ + protected static function checkAppDependencies($config, $l, $info) { + $dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l); + $missing = $dependencyAnalyzer->analyze($info); + if (!empty($missing)) { + $missingMsg = join(PHP_EOL, $missing); + throw new \Exception( + $l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s', + [$info['name'], $missingMsg] + ) + ); + } + } } diff --git a/lib/private/legacy/db.php b/lib/private/legacy/db.php index 2d48e830ec2..415701d4024 100644 --- a/lib/private/legacy/db.php +++ b/lib/private/legacy/db.php @@ -192,23 +192,6 @@ class OC_DB { } /** - * simulate the database schema update - * @param string $file file to read structure from - * @throws Exception - * @return string|boolean - */ - public static function simulateUpdateDbFromStructure($file) { - $schemaManager = self::getMDB2SchemaManager(); - try { - $result = $schemaManager->simulateUpdateDbFromStructure($file); - } catch (Exception $e) { - \OCP\Util::writeLog('core', 'Simulated database structure update failed ('.$e.')', \OCP\Util::FATAL); - throw $e; - } - return $result; - } - - /** * remove all tables defined in a database structure xml file * @param string $file the xml file describing the tables */ diff --git a/lib/private/legacy/helper.php b/lib/private/legacy/helper.php index b19e58a9e6c..9c4bc895fb9 100644 --- a/lib/private/legacy/helper.php +++ b/lib/private/legacy/helper.php @@ -254,16 +254,9 @@ class OC_Helper { if ($path === false) { $path = getenv("PATH"); } - // check method depends on operating system - if (!strncmp(PHP_OS, "WIN", 3)) { - // on Windows an appropriate COM or EXE file needs to exist - $exts = array(".exe", ".com"); - $check_fn = "file_exists"; - } else { - // anywhere else we look for an executable file of that name - $exts = array(""); - $check_fn = "is_executable"; - } + // we look for an executable file of that name + $exts = [""]; + $check_fn = "is_executable"; // Default check will be done with $path directories : $dirs = explode(PATH_SEPARATOR, $path); // WARNING : We have to check if open_basedir is enabled : @@ -378,32 +371,6 @@ class OC_Helper { } /** - * Checks if $sub is a subdirectory of $parent - * - * @param string $sub - * @param string $parent - * @return bool - */ - public static function isSubDirectory($sub, $parent) { - $realpathSub = realpath($sub); - $realpathParent = realpath($parent); - - // realpath() may return false in case the directory does not exist - // since we can not be sure how different PHP versions may behave here - // we do an additional check whether realpath returned false - if($realpathSub === false || $realpathParent === false) { - return false; - } - - // Check whether $sub is a subdirectory of $parent - if (strpos($realpathSub, $realpathParent) === 0) { - return true; - } - - return false; - } - - /** * Returns an array with all keys from input lowercased or uppercased. Numbered indices are left as is. * * @param array $input The array to work on @@ -524,7 +491,6 @@ class OC_Helper { /** * Try to find a program - * Note: currently windows is not supported * * @param string $program * @return null|string @@ -583,7 +549,7 @@ class OC_Helper { $quota = \OCP\Files\FileInfo::SPACE_UNLIMITED; $storage = $rootInfo->getStorage(); $sourceStorage = $storage; - if ($storage->instanceOfStorage('\OC\Files\Storage\Shared')) { + if ($storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) { $includeExtStorage = false; $sourceStorage = $storage->getSourceStorage(); } diff --git a/lib/private/legacy/ocs.php b/lib/private/legacy/ocs.php index 2ed17e7dae0..a03cba7bc1a 100644 --- a/lib/private/legacy/ocs.php +++ b/lib/private/legacy/ocs.php @@ -37,7 +37,7 @@ class OC_OCS { $format = \OC::$server->getRequest()->getParam('format', 'xml'); $txt='Invalid query, please check the syntax. API specifications are here:' .' http://www.freedesktop.org/wiki/Specifications/open-collaboration-services. DEBUG OUTPUT:'."\n"; - OC_API::respond(new OC_OCS_Result(null, API::RESPOND_UNKNOWN_ERROR, $txt), $format); + OC_API::respond(new OC_OCS_Result(null, API::RESPOND_NOT_FOUND, $txt), $format); } } diff --git a/lib/private/legacy/response.php b/lib/private/legacy/response.php index 0ec27251ba5..88725d5e30b 100644 --- a/lib/private/legacy/response.php +++ b/lib/private/legacy/response.php @@ -33,6 +33,7 @@ class OC_Response { const STATUS_NOT_MODIFIED = 304; const STATUS_TEMPORARY_REDIRECT = 307; const STATUS_BAD_REQUEST = 400; + const STATUS_FORBIDDEN = 403; const STATUS_NOT_FOUND = 404; const STATUS_INTERNAL_SERVER_ERROR = 500; const STATUS_SERVICE_UNAVAILABLE = 503; diff --git a/lib/private/legacy/template.php b/lib/private/legacy/template.php index 477fd624a9d..7c0b58db0c0 100644 --- a/lib/private/legacy/template.php +++ b/lib/private/legacy/template.php @@ -117,7 +117,7 @@ class OC_Template extends \OC\Template\Base { OC_Util::addStyle("fonts",null,true); OC_Util::addStyle("icons",null,true); OC_Util::addStyle("header",null,true); - OC_Util::addStyle("inputs",null,true); + OC_Util::addStyle("inputs"); OC_Util::addStyle("styles",null,true); // avatars @@ -126,6 +126,10 @@ class OC_Template extends \OC\Template\Base { \OC_Util::addScript('placeholder', null, true); } + OC_Util::addVendorScript('select2/select2'); + OC_Util::addVendorStyle('select2/select2', null, true); + OC_Util::addScript('select2-toggleselect'); + OC_Util::addScript('oc-backbone', null, true); OC_Util::addVendorScript('core', 'backbone/backbone', true); OC_Util::addVendorScript('snapjs/dist/latest/snap', null, true); @@ -135,6 +139,7 @@ class OC_Template extends \OC\Template\Base { OC_Util::addScript("oc-requesttoken", null, true); OC_Util::addScript('search', 'search', true); OC_Util::addScript("config", null, true); + OC_Util::addScript("public/appconfig", null, true); OC_Util::addScript("eventsource", null, true); OC_Util::addScript("octemplate", null, true); OC_Util::addTranslations("core", null, true); @@ -143,8 +148,6 @@ class OC_Template extends \OC\Template\Base { OC_Util::addScript("oc-dialogs", null, true); OC_Util::addScript("jquery.ocdialog", null, true); OC_Util::addStyle("jquery.ocdialog"); - OC_Util::addScript("compatibility", null, true); - OC_Util::addScript("placeholders", null, true); OC_Util::addScript('files/fileinfo'); OC_Util::addScript('files/client'); diff --git a/lib/private/legacy/util.php b/lib/private/legacy/util.php index 04dcb8fc896..42fd0ba7db3 100644 --- a/lib/private/legacy/util.php +++ b/lib/private/legacy/util.php @@ -165,15 +165,14 @@ class OC_Util { // install storage availability wrapper, before most other wrappers \OC\Files\Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, $storage) { - /** @var \OCP\Files\Storage $storage */ - if (!$storage->instanceOfStorage('\OC\Files\Storage\Shared') && !$storage->isLocal()) { + if (!$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) { return new \OC\Files\Storage\Wrapper\Availability(['storage' => $storage]); } return $storage; }); \OC\Files\Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) { - if ($mount->getOption('encoding_compatibility', false) && !$storage->instanceOfStorage('\OC\Files\Storage\Shared') && !$storage->isLocal()) { + if ($mount->getOption('encoding_compatibility', false) && !$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) { return new \OC\Files\Storage\Wrapper\Encoding(['storage' => $storage]); } return $storage; @@ -311,10 +310,20 @@ class OC_Util { * * @param String $userId * @param \OCP\Files\Folder $userDirectory + * @throws \RuntimeException */ public static function copySkeleton($userId, \OCP\Files\Folder $userDirectory) { - $skeletonDirectory = \OCP\Config::getSystemValue('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton'); + $skeletonDirectory = \OC::$server->getConfig()->getSystemValue('skeletondirectory', \OC::$SERVERROOT . '/core/skeleton'); + $instanceId = \OC::$server->getConfig()->getSystemValue('instanceid', ''); + + if ($instanceId === null) { + throw new \RuntimeException('no instance id!'); + } + $appdata = 'appdata_' . $instanceId; + if ($userId === $appdata) { + throw new \RuntimeException('username is reserved name: ' . $appdata); + } if (!empty($skeletonDirectory)) { \OCP\Util::writeLog( @@ -336,7 +345,16 @@ class OC_Util { * @return void */ public static function copyr($source, \OCP\Files\Folder $target) { + $logger = \OC::$server->getLogger(); + + // Verify if folder exists $dir = opendir($source); + if($dir === false) { + $logger->error(sprintf('Could not opendir "%s"', $source), ['app' => 'core']); + return; + } + + // Copy the files while (false !== ($file = readdir($dir))) { if (!\OC\Files\Filesystem::isIgnoredDir($file)) { if (is_dir($source . '/' . $file)) { @@ -344,7 +362,13 @@ class OC_Util { self::copyr($source . '/' . $file, $child); } else { $child = $target->newFile($file); - stream_copy_to_stream(fopen($source . '/' . $file,'r'), $child->fopen('w')); + $sourceStream = fopen($source . '/' . $file, 'r'); + if($sourceStream === false) { + $logger->error(sprintf('Could not fopen "%s"', $source . '/' . $file), ['app' => 'core']); + closedir($dir); + return; + } + stream_copy_to_stream($sourceStream, $child->fopen('w')); } } } @@ -643,15 +667,6 @@ class OC_Util { $webServerRestart = true; } - // Check if server running on Windows platform - if(OC_Util::runningOnWindows()) { - $errors[] = [ - 'error' => $l->t('Microsoft Windows Platform is not supported'), - 'hint' => $l->t('Running Nextcloud Server on the Microsoft Windows platform is not supported. We suggest you ' . - 'use a Linux server in a virtual machine if you have no option for migrating the server itself.') - ]; - } - // Check if config folder is writable. if(!OC_Helper::isReadOnlyConfigEnabled()) { if (!is_writable(OC::$configDir) or !is_readable(OC::$configDir)) { @@ -1244,15 +1259,6 @@ class OC_Util { } /** - * Checks whether the server is running on Windows - * - * @return bool true if running on Windows, false otherwise - */ - public static function runningOnWindows() { - return (substr(PHP_OS, 0, 3) === "WIN"); - } - - /** * Checks whether the server is running on Mac OS X * * @return bool true if running on Mac OS X, false otherwise |