diff options
Diffstat (limited to 'lib/private')
83 files changed, 3337 insertions, 905 deletions
diff --git a/lib/private/app.php b/lib/private/app.php index abf12264c58..500a60060e6 100644 --- a/lib/private/app.php +++ b/lib/private/app.php @@ -96,7 +96,7 @@ class OC_App { * if $types is set, only apps of those types will be loaded */ public static function loadApps($types = null) { - if (OC_Config::getValue('maintenance', false)) { + if (\OC::$server->getSystemConfig()->getValue('maintenance', false)) { return false; } // Load the enabled apps here @@ -239,7 +239,7 @@ class OC_App { * @return string[] */ public static function getEnabledApps($forceRefresh = false, $all = false) { - if (!OC_Config::getValue('installed', false)) { + if (!\OC::$server->getSystemConfig()->getValue('installed', false)) { return array(); } // in incognito mode or when logged out, $user will be false, @@ -318,8 +318,8 @@ class OC_App { \OC::$server->getConfig(), \OC::$server->getLogger() ); - $appData = $ocsClient->getApplication($app, \OC_Util::getVersion()); - $download= $ocsClient->getApplicationDownload($app, \OC_Util::getVersion()); + $appData = $ocsClient->getApplication($app, \OCP\Util::getVersion()); + $download= $ocsClient->getApplicationDownload($app, \OCP\Util::getVersion()); if(isset($download['downloadlink']) and $download['downloadlink']!='') { // Replace spaces in download link without encoding entire URL $download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']); @@ -374,13 +374,13 @@ class OC_App { $settings = array(); // by default, settings only contain the help menu if (OC_Util::getEditionString() === '' && - OC_Config::getValue('knowledgebaseenabled', true) == true + \OC::$server->getSystemConfig()->getValue('knowledgebaseenabled', true) == true ) { $settings = array( array( "id" => "help", "order" => 1000, - "href" => OC_Helper::linkToRoute("settings_help"), + "href" => \OC::$server->getURLGenerator()->linkToRoute('settings_help'), "name" => $l->t("Help"), "icon" => OC_Helper::imagePath("settings", "help.svg") ) @@ -393,7 +393,7 @@ class OC_App { $settings[] = array( "id" => "personal", "order" => 1, - "href" => OC_Helper::linkToRoute("settings_personal"), + "href" => \OC::$server->getURLGenerator()->linkToRoute('settings_personal'), "name" => $l->t("Personal"), "icon" => OC_Helper::imagePath("settings", "personal.svg") ); @@ -409,7 +409,7 @@ class OC_App { $settings[] = array( "id" => "core_users", "order" => 2, - "href" => OC_Helper::linkToRoute("settings_users"), + "href" => \OC::$server->getURLGenerator()->linkToRoute('settings_users'), "name" => $l->t("Users"), "icon" => OC_Helper::imagePath("settings", "users.svg") ); @@ -421,7 +421,7 @@ class OC_App { $settings[] = array( "id" => "admin", "order" => 1000, - "href" => OC_Helper::linkToRoute("settings_admin"), + "href" => \OC::$server->getURLGenerator()->linkToRoute('settings_admin'), "name" => $l->t("Admin"), "icon" => OC_Helper::imagePath("settings", "admin.svg") ); @@ -455,7 +455,7 @@ class OC_App { * @return string|false */ public static function getInstallPath() { - if (OC_Config::getValue('appstoreenabled', true) == false) { + if (\OC::$server->getSystemConfig()->getValue('appstoreenabled', true) == false) { return false; } @@ -760,9 +760,12 @@ class OC_App { * @param bool $onlyLocal * @param bool $includeUpdateInfo Should we check whether there is an update * in the app store? + * @param OCSClient $ocsClient * @return array */ - public static function listAllApps($onlyLocal = false, $includeUpdateInfo = true) { + public static function listAllApps($onlyLocal = false, + $includeUpdateInfo = true, + OCSClient $ocsClient) { $installedApps = OC_App::getAllApps(); //TODO which apps do we want to blacklist and how do we integrate @@ -825,7 +828,7 @@ class OC_App { if ($onlyLocal) { $remoteApps = []; } else { - $remoteApps = OC_App::getAppstoreApps(); + $remoteApps = OC_App::getAppstoreApps('approved', null, $ocsClient); } if ($remoteApps) { // Remove duplicates @@ -865,22 +868,18 @@ class OC_App { /** * Get a list of all apps on the appstore * @param string $filter - * @param string $category + * @param string|null $category + * @param OCSClient $ocsClient * @return array|bool multi-dimensional array of apps. * Keys: id, name, type, typename, personid, license, detailpage, preview, changed, description */ - public static function getAppstoreApps($filter = 'approved', $category = null) { + public static function getAppstoreApps($filter = 'approved', + $category = null, + OCSClient $ocsClient) { $categories = [$category]; - $ocsClient = new OCSClient( - \OC::$server->getHTTPClientService(), - \OC::$server->getConfig(), - \OC::$server->getLogger() - ); - - if (is_null($category)) { - $categoryNames = $ocsClient->getCategories(\OC_Util::getVersion()); + $categoryNames = $ocsClient->getCategories(\OCP\Util::getVersion()); if (is_array($categoryNames)) { // Check that categories of apps were retrieved correctly if (!$categories = array_keys($categoryNames)) { @@ -892,7 +891,7 @@ class OC_App { } $page = 0; - $remoteApps = $ocsClient->getApplications($categories, $page, $filter, \OC_Util::getVersion()); + $remoteApps = $ocsClient->getApplications($categories, $page, $filter, \OCP\Util::getVersion()); $apps = []; $i = 0; $l = \OC::$server->getL10N('core'); @@ -1050,7 +1049,7 @@ class OC_App { $config, \OC::$server->getLogger() ); - $appData = $ocsClient->getApplication($app, \OC_Util::getVersion()); + $appData = $ocsClient->getApplication($app, \OCP\Util::getVersion()); // check if app is a shipped app or not. OCS apps have an integer as id, shipped apps use a string if (!is_numeric($app)) { @@ -1080,7 +1079,7 @@ class OC_App { if ($app !== false) { // check if the app is compatible with this version of ownCloud $info = self::getAppInfo($app); - $version = OC_Util::getVersion(); + $version = \OCP\Util::getVersion(); if (!self::isAppCompatible($version, $info)) { throw new \Exception( $l->t('App "%s" cannot be installed because it is not compatible with this version of ownCloud.', diff --git a/lib/private/app/codechecker/infochecker.php b/lib/private/app/codechecker/infochecker.php index 24835d8148f..2589277118b 100644 --- a/lib/private/app/codechecker/infochecker.php +++ b/lib/private/app/codechecker/infochecker.php @@ -83,13 +83,18 @@ class InfoChecker extends BasicEmitter { 'type' => 'duplicateRequirement', 'field' => 'min', ]; + } else if (!isset($info['dependencies']['owncloud']['@attributes']['min-version'])) { + $this->emit('InfoChecker', 'missingRequirement', ['min']); } + if (isset($info['dependencies']['owncloud']['@attributes']['max-version']) && $info['requiremax']) { $this->emit('InfoChecker', 'duplicateRequirement', ['max']); $errors[] = [ 'type' => 'duplicateRequirement', 'field' => 'max', ]; + } else if (!isset($info['dependencies']['owncloud']['@attributes']['max-version'])) { + $this->emit('InfoChecker', 'missingRequirement', ['max']); } foreach ($info as $key => $value) { diff --git a/lib/private/app/infoparser.php b/lib/private/app/infoparser.php index 22f705884bc..a84163612e8 100644 --- a/lib/private/app/infoparser.php +++ b/lib/private/app/infoparser.php @@ -59,6 +59,7 @@ class InfoParser { $xml = simplexml_load_file($file); libxml_disable_entity_loader($loadEntities); if ($xml == false) { + libxml_clear_errors(); return null; } $array = $this->xmlToArray($xml); diff --git a/lib/private/app/platform.php b/lib/private/app/platform.php index f433ecd9f9e..c16f050e13c 100644 --- a/lib/private/app/platform.php +++ b/lib/private/app/platform.php @@ -52,7 +52,7 @@ class Platform { * @return string */ public function getOcVersion() { - $v = OC_Util::getVersion(); + $v = \OCP\Util::getVersion(); return join('.', $v); } diff --git a/lib/private/appframework/dependencyinjection/dicontainer.php b/lib/private/appframework/dependencyinjection/dicontainer.php index ce6523cc8a8..69476d84c9b 100644 --- a/lib/private/appframework/dependencyinjection/dicontainer.php +++ b/lib/private/appframework/dependencyinjection/dicontainer.php @@ -62,6 +62,10 @@ class DIContainer extends SimpleContainer implements IAppContainer { $this['AppName'] = $appName; $this['urlParams'] = $urlParams; + /** @var \OC\ServerContainer $server */ + $server = $this->getServer(); + $server->registerAppContainer($appName, $this); + // aliases $this->registerAlias('appName', 'AppName'); $this->registerAlias('webRoot', 'WebRoot'); @@ -161,6 +165,10 @@ class DIContainer extends SimpleContainer implements IAppContainer { $this->registerAlias('OCP\\AppFramework\\Utility\\IControllerMethodReflector', 'OC\AppFramework\Utility\ControllerMethodReflector'); $this->registerAlias('ControllerMethodReflector', 'OCP\\AppFramework\\Utility\\IControllerMethodReflector'); + $this->registerService('OCP\\Files\\IMimeTypeDetector', function($c) { + return $this->getServer()->getMimeTypeDetector(); + }); + $this->registerService('OCP\\INavigationManager', function($c) { return $this->getServer()->getNavigationManager(); }); diff --git a/lib/private/appframework/http/request.php b/lib/private/appframework/http/request.php index 2bbb70db0f8..6ba1d8f644d 100644 --- a/lib/private/appframework/http/request.php +++ b/lib/private/appframework/http/request.php @@ -447,7 +447,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { $deobfuscatedToken = base64_decode($obfuscatedToken) ^ $secret; // Check if the token is valid - if(\OCP\Security\StringUtils::equals($deobfuscatedToken, $this->items['requesttoken'])) { + if(hash_equals($deobfuscatedToken, $this->items['requesttoken'])) { return true; } else { return false; diff --git a/lib/private/appframework/middleware/security/securitymiddleware.php b/lib/private/appframework/middleware/security/securitymiddleware.php index d0b7202a360..725ce689b48 100644 --- a/lib/private/appframework/middleware/security/securitymiddleware.php +++ b/lib/private/appframework/middleware/security/securitymiddleware.php @@ -27,7 +27,6 @@ namespace OC\AppFramework\Middleware\Security; -use OC\AppFramework\Http; use OC\Appframework\Middleware\Security\Exceptions\AppNotEnabledException; use OC\Appframework\Middleware\Security\Exceptions\CrossSiteRequestForgeryException; use OC\Appframework\Middleware\Security\Exceptions\NotAdminException; diff --git a/lib/private/appframework/utility/controllermethodreflector.php b/lib/private/appframework/utility/controllermethodreflector.php index 63cf5ac24f0..1118332f930 100644 --- a/lib/private/appframework/utility/controllermethodreflector.php +++ b/lib/private/appframework/utility/controllermethodreflector.php @@ -60,16 +60,18 @@ class ControllerMethodReflector implements IControllerMethodReflector{ // extract type parameter information preg_match_all('/@param\h+(?P<type>\w+)\h+\$(?P<var>\w+)/', $docs, $matches); - // this is just a fix for PHP 5.3 (array_combine raises warning if called with - // two empty arrays - if($matches['var'] === array() && $matches['type'] === array()) { - $this->types = array(); - } else { - $this->types = array_combine($matches['var'], $matches['type']); - } + $this->types = array_combine($matches['var'], $matches['type']); - // get method parameters foreach ($reflection->getParameters() as $param) { + // extract type information from PHP 7 scalar types and prefer them + // over phpdoc annotations + if (method_exists($param, 'getType')) { + $type = $param->getType(); + if ($type !== null) { + $this->types[$param->getName()] = (string) $type; + } + } + if($param->isOptional()) { $default = $param->getDefaultValue(); } else { @@ -82,9 +84,9 @@ class ControllerMethodReflector implements IControllerMethodReflector{ /** * Inspects the PHPDoc parameters for types - * @param string $parameter the parameter whose type comments should be + * @param string $parameter the parameter whose type comments should be * parsed - * @return string|null type in the type parameters (@param int $something) + * @return string|null type in the type parameters (@param int $something) * would return int or null if not existing */ public function getType($parameter) { diff --git a/lib/private/archive/tar.php b/lib/private/archive/tar.php index 4448e56850d..4066e1d86c1 100644 --- a/lib/private/archive/tar.php +++ b/lib/private/archive/tar.php @@ -86,7 +86,7 @@ class OC_Archive_TAR extends OC_Archive { * @return bool */ function addFolder($path) { - $tmpBase = OC_Helper::tmpFolder(); + $tmpBase = \OC::$server->getTempManager()->getTemporaryFolder(); if (substr($path, -1, 1) != '/') { $path .= '/'; } diff --git a/lib/private/avatar.php b/lib/private/avatar.php index 37a813f3ff8..966e4903649 100644 --- a/lib/private/avatar.php +++ b/lib/private/avatar.php @@ -31,6 +31,7 @@ namespace OC; use OCP\Files\Folder; use OCP\Files\File; +use OCP\Files\NotFoundException; use OCP\IL10N; use OC_Image; @@ -62,28 +63,14 @@ class Avatar implements \OCP\IAvatar { * @return boolean|\OCP\IImage containing the avatar or false if there's no image */ public function get ($size = 64) { - if ($this->folder->nodeExists('avatar.jpg')) { - $ext = 'jpg'; - } elseif ($this->folder->nodeExists('avatar.png')) { - $ext = 'png'; - } else { + try { + $file = $this->getFile($size); + } catch (NotFoundException $e) { return false; } $avatar = new OC_Image(); - if ($this->folder->nodeExists('avatar.' . $size . '.' . $ext)) { - /** @var File $node */ - $node = $this->folder->get('avatar.' . $size . '.' . $ext); - $avatar->loadFromData($node->getContent()); - } else { - /** @var File $node */ - $node = $this->folder->get('avatar.' . $ext); - $avatar->loadFromData($node->getContent()); - if ($size > 0) { - $avatar->resize($size); - } - $this->folder->newFile('avatar.' . $size . '.' . $ext)->putContent($avatar->data()); - } + $avatar->loadFromData($file->getContent()); return $avatar; } @@ -137,11 +124,59 @@ class Avatar implements \OCP\IAvatar { * @return void */ public function remove () { + $regex = '/^avatar\.([0-9]+\.)?(jpg|png)$/'; + $avatars = $this->folder->search('avatar'); + + foreach ($avatars as $avatar) { + if (preg_match($regex, $avatar->getName())) { + $avatar->delete(); + } + } + } + + /** + * Get the File of an avatar of size $size. + * + * @param int $size + * @return File + * @throws NotFoundException + */ + public function getFile($size) { + $ext = $this->getExtention(); + + $path = 'avatar.' . $size . '.' . $ext; + try { - $this->folder->get('avatar.jpg')->delete(); - } catch (\OCP\Files\NotFoundException $e) {} - try { - $this->folder->get('avatar.png')->delete(); - } catch (\OCP\Files\NotFoundException $e) {} + $file = $this->folder->get($path); + } catch (NotFoundException $e) { + if ($size <= 0) { + throw new NotFoundException; + } + + $avatar = new OC_Image(); + /** @var File $file */ + $file = $this->folder->get('avatar.' . $ext); + $avatar->loadFromData($file->getContent()); + $avatar->resize($size); + $file = $this->folder->newFile($path); + $file->putContent($avatar->data()); + } + + return $file; + } + + /** + * Get the extention of the avatar. If there is no avatar throw Exception + * + * @return string + * @throws NotFoundException + */ + private function getExtention() { + if ($this->folder->nodeExists('avatar.jpg')) { + return 'jpg'; + } elseif ($this->folder->nodeExists('avatar.png')) { + return 'png'; + } + throw new NotFoundException; } } diff --git a/lib/private/backgroundjob/job.php b/lib/private/backgroundjob/job.php index 88682cd09bb..40a27491fe6 100644 --- a/lib/private/backgroundjob/job.php +++ b/lib/private/backgroundjob/job.php @@ -54,7 +54,6 @@ abstract class Job implements IJob { if ($logger) { $logger->error('Error while running background job: ' . $e->getMessage()); } - $jobList->remove($this, $this->argument); } } diff --git a/lib/private/backgroundjob/joblist.php b/lib/private/backgroundjob/joblist.php index 03c9180ddb0..446de2fa1a4 100644 --- a/lib/private/backgroundjob/joblist.php +++ b/lib/private/backgroundjob/joblist.php @@ -24,134 +24,180 @@ namespace OC\BackgroundJob; +use OCP\AppFramework\QueryException; +use OCP\BackgroundJob\IJob; use OCP\BackgroundJob\IJobList; use OCP\AutoloadNotAllowedException; class JobList implements IJobList { - /** - * @var \OCP\IDBConnection - */ - private $conn; + /** @var \OCP\IDBConnection */ + protected $connection; /** * @var \OCP\IConfig $config */ - private $config; + protected $config; /** - * @param \OCP\IDBConnection $conn + * @param \OCP\IDBConnection $connection * @param \OCP\IConfig $config */ - public function __construct($conn, $config) { - $this->conn = $conn; + public function __construct($connection, $config) { + $this->connection = $connection; $this->config = $config; } /** - * @param Job|string $job + * @param IJob|string $job * @param mixed $argument */ public function add($job, $argument = null) { if (!$this->has($job, $argument)) { - if ($job instanceof Job) { + if ($job instanceof IJob) { $class = get_class($job); } else { $class = $job; } + $argument = json_encode($argument); if (strlen($argument) > 4000) { throw new \InvalidArgumentException('Background job arguments can\'t exceed 4000 characters (json encoded)'); } - $query = $this->conn->prepare('INSERT INTO `*PREFIX*jobs`(`class`, `argument`, `last_run`) VALUES(?, ?, 0)'); - $query->execute(array($class, $argument)); + + $query = $this->connection->getQueryBuilder(); + $query->insert('jobs') + ->values([ + 'class' => $query->createNamedParameter($class), + 'argument' => $query->createNamedParameter($argument), + 'last_run' => $query->createNamedParameter(0, \PDO::PARAM_INT), + ]); + $query->execute(); } } /** - * @param Job|string $job + * @param IJob|string $job * @param mixed $argument */ public function remove($job, $argument = null) { - if ($job instanceof Job) { + if ($job instanceof IJob) { $class = get_class($job); } else { $class = $job; } + + $query = $this->connection->getQueryBuilder(); + $query->delete('jobs') + ->where($query->expr()->eq('class', $query->createNamedParameter($class))); if (!is_null($argument)) { $argument = json_encode($argument); - $query = $this->conn->prepare('DELETE FROM `*PREFIX*jobs` WHERE `class` = ? AND `argument` = ?'); - $query->execute(array($class, $argument)); - } else { - $query = $this->conn->prepare('DELETE FROM `*PREFIX*jobs` WHERE `class` = ?'); - $query->execute(array($class)); + $query->andWhere($query->expr()->eq('argument', $query->createNamedParameter($argument))); } + $query->execute(); } + /** + * @param int $id + */ protected function removeById($id) { - $query = $this->conn->prepare('DELETE FROM `*PREFIX*jobs` WHERE `id` = ?'); - $query->execute([$id]); + $query = $this->connection->getQueryBuilder(); + $query->delete('jobs') + ->where($query->expr()->eq('id', $query->createNamedParameter($id, \PDO::PARAM_INT))); + $query->execute(); } /** * check if a job is in the list * - * @param Job|string $job + * @param IJob|string $job * @param mixed $argument * @return bool */ public function has($job, $argument) { - if ($job instanceof Job) { + if ($job instanceof IJob) { $class = get_class($job); } else { $class = $job; } $argument = json_encode($argument); - $query = $this->conn->prepare('SELECT `id` FROM `*PREFIX*jobs` WHERE `class` = ? AND `argument` = ?'); - $query->execute(array($class, $argument)); - return (bool)$query->fetch(); + + $query = $this->connection->getQueryBuilder(); + $query->select('id') + ->from('jobs') + ->where($query->expr()->eq('class', $query->createNamedParameter($class))) + ->andWhere($query->expr()->eq('argument', $query->createNamedParameter($argument))) + ->setMaxResults(1); + + $result = $query->execute(); + $row = $result->fetch(); + $result->closeCursor(); + + return (bool) $row; } /** * get all jobs in the list * - * @return Job[] + * @return IJob[] */ public function getAll() { - $query = $this->conn->prepare('SELECT `id`, `class`, `last_run`, `argument` FROM `*PREFIX*jobs`'); - $query->execute(); - $jobs = array(); - while ($row = $query->fetch()) { + $query = $this->connection->getQueryBuilder(); + $query->select('*') + ->from('jobs'); + $result = $query->execute(); + + $jobs = []; + while ($row = $result->fetch()) { $job = $this->buildJob($row); if ($job) { $jobs[] = $job; } } + $result->closeCursor(); + return $jobs; } /** * get the next job in the list * - * @return Job + * @return IJob|null */ public function getNext() { $lastId = $this->getLastJob(); - $query = $this->conn->prepare('SELECT `id`, `class`, `last_run`, `argument` FROM `*PREFIX*jobs` WHERE `id` > ? ORDER BY `id` ASC', 1); - $query->execute(array($lastId)); - if ($row = $query->fetch()) { + + $query = $this->connection->getQueryBuilder(); + $query->select('*') + ->from('jobs') + ->where($query->expr()->gt('id', $query->createNamedParameter($lastId, \PDO::PARAM_INT))) + ->orderBy('id', 'ASC') + ->setMaxResults(1); + $result = $query->execute(); + $row = $result->fetch(); + $result->closeCursor(); + + if ($row) { $jobId = $row['id']; $job = $this->buildJob($row); } else { //begin at the start of the queue - $query = $this->conn->prepare('SELECT `id`, `class`, `last_run`, `argument` FROM `*PREFIX*jobs` ORDER BY `id` ASC', 1); - $query->execute(); - if ($row = $query->fetch()) { + $query = $this->connection->getQueryBuilder(); + $query->select('*') + ->from('jobs') + ->orderBy('id', 'ASC') + ->setMaxResults(1); + $result = $query->execute(); + $row = $result->fetch(); + $result->closeCursor(); + + if ($row) { $jobId = $row['id']; $job = $this->buildJob($row); } else { return null; //empty job list } } + if (is_null($job)) { $this->removeById($jobId); return $this->getNext(); @@ -162,12 +208,18 @@ class JobList implements IJobList { /** * @param int $id - * @return Job|null + * @return IJob|null */ public function getById($id) { - $query = $this->conn->prepare('SELECT `id`, `class`, `last_run`, `argument` FROM `*PREFIX*jobs` WHERE `id` = ?'); - $query->execute(array($id)); - if ($row = $query->fetch()) { + $query = $this->connection->getQueryBuilder(); + $query->select('*') + ->from('jobs') + ->where($query->expr()->eq('id', $query->createNamedParameter($id, \PDO::PARAM_INT))); + $result = $query->execute(); + $row = $result->fetch(); + $result->closeCursor(); + + if ($row) { return $this->buildJob($row); } else { return null; @@ -178,33 +230,38 @@ class JobList implements IJobList { * get the job object from a row in the db * * @param array $row - * @return Job + * @return IJob|null */ private function buildJob($row) { - $class = $row['class']; - /** - * @var Job $job - */ try { - if (!class_exists($class)) { - // job from disabled app or old version of an app, no need to do anything - return null; + try { + // Try to load the job as a service + /** @var IJob $job */ + $job = \OC::$server->query($row['class']); + } catch (QueryException $e) { + if (class_exists($row['class'])) { + $class = $row['class']; + $job = new $class(); + } else { + // job from disabled app or old version of an app, no need to do anything + return null; + } } - $job = new $class(); + $job->setId($row['id']); $job->setLastRun($row['last_run']); $job->setArgument(json_decode($row['argument'], true)); return $job; } catch (AutoloadNotAllowedException $e) { // job is from a disabled app, ignore + return null; } - return null; } /** * set the job that was last ran * - * @param Job $job + * @param IJob $job */ public function setLastJob($job) { $this->config->setAppValue('backgroundjob', 'lastjob', $job->getId()); @@ -213,19 +270,22 @@ class JobList implements IJobList { /** * get the id of the last ran job * - * @return string + * @return int */ public function getLastJob() { - return $this->config->getAppValue('backgroundjob', 'lastjob', 0); + return (int) $this->config->getAppValue('backgroundjob', 'lastjob', 0); } /** * set the lastRun of $job to now * - * @param Job $job + * @param IJob $job */ public function setLastRun($job) { - $query = $this->conn->prepare('UPDATE `*PREFIX*jobs` SET `last_run` = ? WHERE `id` = ?'); - $query->execute(array(time(), $job->getId())); + $query = $this->connection->getQueryBuilder(); + $query->update('jobs') + ->set('last_run', $query->createNamedParameter(time(), \PDO::PARAM_INT)) + ->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), \PDO::PARAM_INT))); + $query->execute(); } } diff --git a/lib/private/db.php b/lib/private/db.php index a4a7b7d17d4..d47b7d4f31a 100644 --- a/lib/private/db.php +++ b/lib/private/db.php @@ -36,13 +36,6 @@ class OC_DB { /** - * @return \OCP\IDBConnection - */ - static public function getConnection() { - return \OC::$server->getDatabaseConnection(); - } - - /** * get MDB2 schema manager * * @return \OC\DB\MDB2SchemaManager @@ -158,42 +151,6 @@ class OC_DB { } /** - * gets last value of autoincrement - * @param string $table The optional table name (will replace *PREFIX*) and add sequence suffix - * @return string id - * @throws \OC\DatabaseException - * - * \Doctrine\DBAL\Connection lastInsertId - * - * Call this method right after the insert command or other functions may - * cause trouble! - */ - public static function insertid($table=null) { - return \OC::$server->getDatabaseConnection()->lastInsertId($table); - } - - /** - * Start a transaction - */ - public static function beginTransaction() { - return \OC::$server->getDatabaseConnection()->beginTransaction(); - } - - /** - * Commit the database changes done during a transaction that is in progress - */ - public static function commit() { - return \OC::$server->getDatabaseConnection()->commit(); - } - - /** - * Rollback the database changes done during a transaction that is in progress - */ - public static function rollback() { - return \OC::$server->getDatabaseConnection()->rollback(); - } - - /** * saves database schema to xml file * @param string $file name of file * @param int $mode @@ -254,15 +211,6 @@ class OC_DB { } /** - * drop a table - the database prefix will be prepended - * @param string $tableName the table to drop - */ - public static function dropTable($tableName) { - $connection = \OC::$server->getDatabaseConnection(); - $connection->dropTable($tableName); - } - - /** * remove all tables defined in a database structure xml file * @param string $file the xml file describing the tables */ @@ -272,15 +220,6 @@ class OC_DB { } /** - * check if a result is an error, works with Doctrine - * @param mixed $result - * @return bool - */ - public static function isError($result) { - //Doctrine returns false on error (and throws an exception) - return $result === false; - } - /** * check if a result is an error and throws an exception, works with \Doctrine\DBAL\DBALException * @param mixed $result * @param string $message @@ -288,20 +227,16 @@ class OC_DB { * @throws \OC\DatabaseException */ public static function raiseExceptionOnError($result, $message = null) { - if(self::isError($result)) { + if($result === false) { if ($message === null) { $message = self::getErrorMessage(); } else { $message .= ', Root cause:' . self::getErrorMessage(); } - throw new \OC\DatabaseException($message, self::getErrorCode()); + throw new \OC\DatabaseException($message, \OC::$server->getDatabaseConnection()->errorCode()); } } - public static function getErrorCode() { - $connection = \OC::$server->getDatabaseConnection(); - return $connection->errorCode(); - } /** * returns the error code and message as a string for logging * works with DoctrineException diff --git a/lib/private/defaults.php b/lib/private/defaults.php index 16f45943f54..23f0baad96e 100644 --- a/lib/private/defaults.php +++ b/lib/private/defaults.php @@ -49,7 +49,7 @@ class OC_Defaults { function __construct() { $this->l = \OC::$server->getL10N('lib'); - $version = OC_Util::getVersion(); + $version = \OCP\Util::getVersion(); $this->defaultEntity = 'ownCloud'; /* e.g. company name, used for footers and copyright notices */ $this->defaultName = 'ownCloud'; /* short name, used when referring to the software */ diff --git a/lib/private/eventsource.php b/lib/private/eventsource.php index c076b87ddd9..0e98bdc2628 100644 --- a/lib/private/eventsource.php +++ b/lib/private/eventsource.php @@ -76,7 +76,7 @@ class OC_EventSource implements \OCP\IEventSource { } else { header("Content-Type: text/event-stream"); } - if (!OC_Util::isCallRegistered()) { + if (!(\OC::$server->getRequest()->passesCSRFCheck())) { $this->send('error', 'Possible CSRF attack. Connection will be closed.'); $this->close(); exit(); diff --git a/lib/private/files/cache/scanner.php b/lib/private/files/cache/scanner.php index 983e12d7639..88bb57d2b5c 100644 --- a/lib/private/files/cache/scanner.php +++ b/lib/private/files/cache/scanner.php @@ -336,7 +336,7 @@ class Scanner extends BasicEmitter { $newChildren = $this->getNewChildren($path); if ($this->useTransactions) { - \OC_DB::beginTransaction(); + \OC::$server->getDatabaseConnection()->beginTransaction(); } $exceptionOccurred = false; foreach ($newChildren as $file) { @@ -361,7 +361,7 @@ class Scanner extends BasicEmitter { $exceptionOccurred = true; } catch (\OCP\Lock\LockedException $e) { if ($this->useTransactions) { - \OC_DB::rollback(); + \OC::$server->getDatabaseConnection()->rollback(); } throw $e; } @@ -372,7 +372,7 @@ class Scanner extends BasicEmitter { $this->removeFromCache($child); } if ($this->useTransactions) { - \OC_DB::commit(); + \OC::$server->getDatabaseConnection()->commit(); } if ($exceptionOccurred) { // It might happen that the parallel scan process has already diff --git a/lib/private/files/cache/storage.php b/lib/private/files/cache/storage.php index cee69194095..4998c622e84 100644 --- a/lib/private/files/cache/storage.php +++ b/lib/private/files/cache/storage.php @@ -58,10 +58,10 @@ class Storage { if ($row = self::getStorageById($this->storageId)) { $this->numericId = $row['numeric_id']; } else { - $connection = \OC_DB::getConnection(); + $connection = \OC::$server->getDatabaseConnection(); $available = $isAvailable ? 1 : 0; if ($connection->insertIfNotExist('*PREFIX*storages', ['id' => $this->storageId, 'available' => $available])) { - $this->numericId = \OC_DB::insertid('*PREFIX*storages'); + $this->numericId = $connection->lastInsertId('*PREFIX*storages'); } else { if ($row = self::getStorageById($this->storageId)) { $this->numericId = $row['numeric_id']; diff --git a/lib/private/files/objectstore/objectstorestorage.php b/lib/private/files/objectstore/objectstorestorage.php index 5ec05a3529e..b34a6bdfb84 100644 --- a/lib/private/files/objectstore/objectstorestorage.php +++ b/lib/private/files/objectstore/objectstorestorage.php @@ -274,7 +274,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common { } else { $ext = ''; } - $tmpFile = \OC_Helper::tmpFile($ext); + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($ext); \OC\Files\Stream\Close::registerCallback($tmpFile, array($this, 'writeBack')); if ($this->file_exists($path)) { $source = $this->fopen($path, 'r'); @@ -329,7 +329,7 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common { $stat['mtime'] = $mtime; $this->getCache()->update($stat['fileid'], $stat); } else { - $mimeType = \OC_Helper::getFileNameMimeType($path); + $mimeType = \OC::$server->getMimeTypeDetector()->detectPath($path); // create new file $stat = array( 'etag' => $this->getETag($path), diff --git a/lib/private/files/storage/common.php b/lib/private/files/storage/common.php index b06543d0a6a..1e30d48f613 100644 --- a/lib/private/files/storage/common.php +++ b/lib/private/files/storage/common.php @@ -225,7 +225,7 @@ abstract class Common implements Storage { if ($this->is_dir($path)) { return 'httpd/unix-directory'; } elseif ($this->file_exists($path)) { - return \OC_Helper::getFileNameMimeType($path); + return \OC::$server->getMimeTypeDetector()->detectPath($path); } else { return false; } @@ -248,7 +248,7 @@ abstract class Common implements Storage { } public function getLocalFolder($path) { - $baseDir = \OC_Helper::tmpFolder(); + $baseDir = \OC::$server->getTempManager()->getTemporaryFolder(); $this->addLocalFolder($path, $baseDir); return $baseDir; } diff --git a/lib/private/files/storage/dav.php b/lib/private/files/storage/dav.php index dda163e41a0..9afebab1dd7 100644 --- a/lib/private/files/storage/dav.php +++ b/lib/private/files/storage/dav.php @@ -34,6 +34,7 @@ namespace OC\Files\Storage; use Exception; +use GuzzleHttp\Exception\RequestException; use OC\Files\Filesystem; use OC\Files\Stream\Close; use Icewind\Streams\IteratorDirectory; @@ -339,15 +340,20 @@ class DAV extends Common { switch ($mode) { case 'r': case 'rb': - if (!$this->file_exists($path)) { - return false; + try { + $response = $this->httpClientService + ->newClient() + ->get($this->createBaseUri() . $this->encodePath($path), [ + 'auth' => [$this->user, $this->password], + 'stream' => true + ]); + } catch (RequestException $e) { + if ($e->getResponse()->getStatusCode() === 404) { + return false; + } else { + throw $e; + } } - $response = $this->httpClientService - ->newClient() - ->get($this->createBaseUri() . $this->encodePath($path), [ - 'auth' => [$this->user, $this->password], - 'stream' => true - ]); if ($response->getStatusCode() !== Http::STATUS_OK) { if ($response->getStatusCode() === Http::STATUS_LOCKED) { diff --git a/lib/private/files/storage/localtempfiletrait.php b/lib/private/files/storage/localtempfiletrait.php index 84331f49b19..8875c2c4493 100644 --- a/lib/private/files/storage/localtempfiletrait.php +++ b/lib/private/files/storage/localtempfiletrait.php @@ -70,7 +70,7 @@ trait LocalTempFileTrait { } else { $extension = ''; } - $tmpFile = \OC_Helper::tmpFile($extension); + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension); $target = fopen($tmpFile, 'w'); \OC_Helper::streamCopy($source, $target); fclose($target); diff --git a/lib/private/files/storage/temporary.php b/lib/private/files/storage/temporary.php index c8b99a55637..8abc19929b0 100644 --- a/lib/private/files/storage/temporary.php +++ b/lib/private/files/storage/temporary.php @@ -29,7 +29,7 @@ namespace OC\Files\Storage; */ class Temporary extends Local{ public function __construct($arguments = null) { - parent::__construct(array('datadir' => \OC_Helper::tmpFolder())); + parent::__construct(array('datadir' => \OC::$server->getTempManager()->getTemporaryFolder())); } public function cleanUp() { diff --git a/lib/private/files/type/detection.php b/lib/private/files/type/detection.php index c102e739e04..0e2bab39e5b 100644 --- a/lib/private/files/type/detection.php +++ b/lib/private/files/type/detection.php @@ -238,7 +238,7 @@ class Detection implements IMimeTypeDetector { $finfo = finfo_open(FILEINFO_MIME); return finfo_buffer($finfo, $data); } else { - $tmpFile = \OC_Helper::tmpFile(); + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile(); $fh = fopen($tmpFile, 'wb'); fwrite($fh, $data, 8024); fclose($fh); diff --git a/lib/private/files/view.php b/lib/private/files/view.php index b8b1b8a50d6..357f854e5e2 100644 --- a/lib/private/files/view.php +++ b/lib/private/files/view.php @@ -86,6 +86,8 @@ class View { private $updaterEnabled = true; + private $userManager; + /** * @param string $root * @throws \Exception If $root contains an invalid path @@ -101,6 +103,7 @@ class View { $this->fakeRoot = $root; $this->lockingProvider = \OC::$server->getLockingProvider(); $this->lockingEnabled = !($this->lockingProvider instanceof \OC\Lock\NoopLockingProvider); + $this->userManager = \OC::$server->getUserManager(); } public function getAbsolutePath($path = '/') { @@ -912,7 +915,7 @@ class View { $source = $this->fopen($path, 'r'); if ($source) { $extension = pathinfo($path, PATHINFO_EXTENSION); - $tmpFile = \OC_Helper::tmpFile($extension); + $tmpFile = \OC::$server->getTempManager()->getTemporaryFile($extension); file_put_contents($tmpFile, $source); return $tmpFile; } else { @@ -1196,7 +1199,7 @@ class View { * @return \OC\User\User */ private function getUserObjectForOwner($ownerId) { - $owner = \OC::$server->getUserManager()->get($ownerId); + $owner = $this->userManager->get($ownerId); if ($owner instanceof IUser) { return $owner; } else { @@ -1339,11 +1342,12 @@ class View { $folderId = $data['fileid']; $contents = $cache->getFolderContentsById($folderId); //TODO: mimetype_filter + $sharingDisabled = \OCP\Util::isSharingDisabledForUser(); /** * @var \OC\Files\FileInfo[] $files */ - $files = array_map(function (array $content) use ($path, $storage, $mount) { - if (\OCP\Util::isSharingDisabledForUser()) { + $files = array_map(function (array $content) use ($path, $storage, $mount, $sharingDisabled) { + if ($sharingDisabled) { $content['permissions'] = $content['permissions'] & ~\OCP\Constants::PERMISSION_SHARE; } $owner = $this->getUserObjectForOwner($storage->getOwner($content['path'])); diff --git a/lib/private/helper.php b/lib/private/helper.php index 78a567638ef..64952903712 100644 --- a/lib/private/helper.php +++ b/lib/private/helper.php @@ -54,88 +54,6 @@ class OC_Helper { private static $templateManager; /** - * Creates an url using a defined route - * @param string $route - * @param array $parameters with param=>value, will be appended to the returned url - * @return string the url - * @deprecated Use \OC::$server->getURLGenerator()->linkToRoute($route, $parameters) - * - * Returns a url to the given app and file. - */ - public static function linkToRoute($route, $parameters = array()) { - return OC::$server->getURLGenerator()->linkToRoute($route, $parameters); - } - - /** - * Creates an url - * @param string $app app - * @param string $file file - * @param array $args array with param=>value, will be appended to the returned url - * The value of $args will be urlencoded - * @return string the url - * @deprecated Use \OC::$server->getURLGenerator()->linkTo($app, $file, $args) - * - * Returns a url to the given app and file. - */ - public static function linkTo( $app, $file, $args = array() ) { - return OC::$server->getURLGenerator()->linkTo($app, $file, $args); - } - - /** - * Creates an absolute url - * @param string $app app - * @param string $file file - * @param array $args array with param=>value, will be appended to the returned url - * The value of $args will be urlencoded - * @return string the url - * - * Returns a absolute url to the given app and file. - */ - public static function linkToAbsolute($app, $file, $args = array()) { - return OC::$server->getURLGenerator()->getAbsoluteURL( - self::linkTo($app, $file, $args) - ); - } - - /** - * Makes an $url absolute - * @param string $url the url - * @return string the absolute url - * @deprecated Use \OC::$server->getURLGenerator()->getAbsoluteURL($url) - * - * Returns a absolute url to the given app and file. - */ - public static function makeURLAbsolute($url) { - return OC::$server->getURLGenerator()->getAbsoluteURL($url); - } - - /** - * Creates an url for remote use - * @param string $service id - * @return string the url - * - * Returns a url to the given service. - */ - public static function linkToRemoteBase($service) { - return self::linkTo('', 'remote.php') . '/' . $service; - } - - /** - * Creates an absolute url for remote use - * @param string $service id - * @param bool $add_slash - * @return string the url - * - * Returns a absolute url to the given service. - */ - public static function linkToRemote($service, $add_slash = true) { - return OC::$server->getURLGenerator()->getAbsoluteURL( - self::linkToRemoteBase($service) - . (($add_slash && $service[strlen($service) - 1] != '/') ? '/' : '') - ); - } - - /** * Creates an absolute url for public use * @param string $service id * @param bool $add_slash @@ -147,7 +65,7 @@ class OC_Helper { if ($service === 'files') { $url = OC::$server->getURLGenerator()->getAbsoluteURL('/s'); } else { - $url = OC::$server->getURLGenerator()->getAbsoluteURL(self::linkTo('', 'public.php').'?service='.$service); + $url = OC::$server->getURLGenerator()->getAbsoluteURL(OC::$server->getURLGenerator()->linkTo('', 'public.php').'?service='.$service); } return $url . (($add_slash && $service[strlen($service) - 1] != '/') ? '/' : ''); } @@ -166,18 +84,6 @@ class OC_Helper { } /** - * get path to icon of file type - * @param string $mimetype mimetype - * @return string the url - * - * Returns the path to the image of this file type. - * @deprecated 8.2.0 Use \OC::$server->getMimeTypeDetector()->mimeTypeIcon($mimetype) - */ - public static function mimetypeIcon($mimetype) { - return \OC::$server->getMimeTypeDetector()->mimeTypeIcon($mimetype); - } - - /** * get path to preview of file * @param string $path path * @return string the url @@ -185,21 +91,11 @@ class OC_Helper { * Returns the path to the preview of the file. */ public static function previewIcon($path) { - return self::linkToRoute( 'core_ajax_preview', array('x' => 32, 'y' => 32, 'file' => $path )); + return \OC::$server->getURLGenerator()->linkToRoute('core_ajax_preview', ['x' => 32, 'y' => 32, 'file' => $path]); } public static function publicPreviewIcon( $path, $token ) { - return self::linkToRoute( 'core_ajax_public_preview', array('x' => 32, 'y' => 32, 'file' => $path, 't' => $token)); - } - - /** - * shows whether the user has an avatar - * @param string $user username - * @return bool avatar set or not - * @deprecated 9.0.0 Use \OC::$server->getAvatarManager()->getAvatar($user)->exists(); - **/ - public static function userAvatarSet($user) { - return \OC::$server->getAvatarManager()->getAvatar($user)->exists(); + return \OC::$server->getURLGenerator()->linkToRoute('core_ajax_public_preview', ['x' => 32, 'y' => 32, 'file' => $path, 't' => $token]); } /** @@ -364,14 +260,6 @@ class OC_Helper { } /** - * @return \OC\Files\Type\Detection - * @deprecated 8.2.0 use \OC::$server->getMimeTypeDetector() - */ - static public function getMimetypeDetector() { - return \OC::$server->getMimeTypeDetector(); - } - - /** * @return \OC\Files\Type\TemplateManager */ static public function getFileTemplateManager() { @@ -382,39 +270,6 @@ class OC_Helper { } /** - * Try to guess the mimetype based on filename - * - * @param string $path - * @return string - * @deprecated 8.2.0 Use \OC::$server->getMimeTypeDetector()->detectPath($path) - */ - static public function getFileNameMimeType($path) { - return \OC::$server->getMimeTypeDetector()->detectPath($path); - } - - /** - * Get a secure mimetype that won't expose potential XSS. - * - * @param string $mimeType - * @return string - * @deprecated 8.2.0 Use \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType) - */ - static function getSecureMimeType($mimeType) { - return \OC::$server->getMimeTypeDetector()->getSecureMimeType($mimeType); - } - - /** - * get the mimetype form a data string - * - * @param string $data - * @return string - * @deprecated 8.2.0 Use \OC::$server->getMimeTypeDetector()->detectString($data) - */ - static function getStringMimeType($data) { - return \OC::$server->getMimeTypeDetector()->detectString($data); - } - - /** * detect if a given program is found in the search PATH * * @param string $name @@ -493,31 +348,6 @@ class OC_Helper { } /** - * create a temporary file with an unique filename - * - * @param string $postfix - * @return string - * @deprecated Use the TempManager instead - * - * temporary files are automatically cleaned up after the script is finished - */ - public static function tmpFile($postfix = '') { - return \OC::$server->getTempManager()->getTemporaryFile($postfix); - } - - /** - * create a temporary folder with an unique filename - * - * @return string - * @deprecated Use the TempManager instead - * - * temporary files are automatically cleaned up after the script is finished - */ - public static function tmpFolder() { - return \OC::$server->getTempManager()->getTemporaryFolder(); - } - - /** * Adds a suffix to the name in case the file exists * * @param string $path @@ -782,7 +612,7 @@ class OC_Helper { */ public static function getStorageInfo($path, $rootInfo = null) { // return storage info without adding mount points - $includeExtStorage = \OC_Config::getValue('quota_include_external_storage', false); + $includeExtStorage = \OC::$server->getSystemConfig()->getValue('quota_include_external_storage', false); if (!$rootInfo) { $rootInfo = \OC\Files\Filesystem::getFileInfo($path, false); diff --git a/lib/private/image.php b/lib/private/image.php index 4ca9b811100..a5de7e86b94 100644 --- a/lib/private/image.php +++ b/lib/private/image.php @@ -39,17 +39,19 @@ * Class for basic image manipulation */ class OC_Image implements \OCP\IImage { + /** @var false|resource */ protected $resource = false; // tmp resource. + /** @var int */ protected $imageType = IMAGETYPE_PNG; // Default to png if file type isn't evident. - protected $mimeType = "image/png"; // Default to png + /** @var string */ + protected $mimeType = 'image/png'; // Default to png + /** @var int */ protected $bitDepth = 24; + /** @var null|string */ protected $filePath = null; - + /** @var finfo */ private $fileInfo; - - /** - * @var \OCP\ILogger - */ + /** @var \OCP\ILogger */ private $logger; /** diff --git a/lib/private/installer.php b/lib/private/installer.php index fa9fc6704df..bbd976cda91 100644 --- a/lib/private/installer.php +++ b/lib/private/installer.php @@ -232,8 +232,8 @@ class OC_Installer{ \OC::$server->getConfig(), \OC::$server->getLogger() ); - $appData = $ocsClient->getApplication($ocsId, \OC_Util::getVersion()); - $download = $ocsClient->getApplicationDownload($ocsId, \OC_Util::getVersion()); + $appData = $ocsClient->getApplication($ocsId, \OCP\Util::getVersion()); + $download = $ocsClient->getApplicationDownload($ocsId, \OCP\Util::getVersion()); if (isset($download['downloadlink']) && trim($download['downloadlink']) !== '') { $download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']); @@ -264,7 +264,7 @@ class OC_Installer{ //download the file if necessary if($data['source']=='http') { $pathInfo = pathinfo($data['href']); - $path=OC_Helper::tmpFile('.' . $pathInfo['extension']); + $path = \OC::$server->getTempManager()->getTemporaryFile('.' . $pathInfo['extension']); if(!isset($data['href'])) { throw new \Exception($l->t("No href specified when installing app from http")); } @@ -284,7 +284,7 @@ class OC_Installer{ } //extract the archive in a temporary folder - $extractDir=OC_Helper::tmpFolder(); + $extractDir = \OC::$server->getTempManager()->getTemporaryFolder(); OC_Helper::rmdirr($extractDir); mkdir($extractDir); if($archive=OC_Archive::open($path)) { @@ -342,7 +342,7 @@ class OC_Installer{ } // check if the app is compatible with this version of ownCloud - if(!OC_App::isAppCompatible(OC_Util::getVersion(), $info)) { + if(!OC_App::isAppCompatible(\OCP\Util::getVersion(), $info)) { OC_Helper::rmdirr($extractDir); throw new \Exception($l->t("App can't be installed because it is not compatible with this version of ownCloud")); } @@ -400,7 +400,7 @@ class OC_Installer{ \OC::$server->getConfig(), \OC::$server->getLogger() ); - $ocsdata = $ocsClient->getApplication($ocsid, \OC_Util::getVersion()); + $ocsdata = $ocsClient->getApplication($ocsid, \OCP\Util::getVersion()); $ocsversion= (string) $ocsdata['version']; $currentversion=OC_App::getAppVersion($app); if (version_compare($ocsversion, $currentversion, '>')) { diff --git a/lib/private/json.php b/lib/private/json.php index eba374f4da2..0bf4e8bcd01 100644 --- a/lib/private/json.php +++ b/lib/private/json.php @@ -76,7 +76,7 @@ class OC_JSON{ * @deprecated Use annotation based CSRF checks from the AppFramework instead */ public static function callCheck() { - if( !OC_Util::isCallRegistered()) { + if( !(\OC::$server->getRequest()->passesCSRFCheck())) { $l = \OC::$server->getL10N('lib'); self::error(array( 'data' => array( 'message' => $l->t('Token expired. Please reload page.'), 'error' => 'token_expired' ))); exit(); diff --git a/lib/private/legacy/config.php b/lib/private/legacy/config.php deleted file mode 100644 index 1835d4a4b1c..00000000000 --- a/lib/private/legacy/config.php +++ /dev/null @@ -1,94 +0,0 @@ -<?php -/** - * @author Bart Visscher <bartv@thisnet.nl> - * @author Joas Schilling <nickvergessen@owncloud.com> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Lukas Reschke <lukas@owncloud.com> - * @author Michael Gapczynski <GapczynskiM@gmail.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin McCorkell <rmccorkell@karoshi.org.uk> - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @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/> - * - */ - -/** - * This class is responsible for reading and writing config.php, the very basic - * configuration file of ownCloud. - * - * @deprecated use \OC::$server->getConfig() to get an \OCP\Config instance - */ -class OC_Config { - - /** @var \OC\Config */ - public static $object; - - /** - * Lists all available config keys - * @return array an array of key names - * - * This function returns all keys saved in config.php. Please note that it - * does not return the values. - */ - public static function getKeys() { - return self::$object->getKeys(); - } - - /** - * Gets a value from config.php - * @param string $key key - * @param mixed $default = null default value - * @return mixed the value or $default - * - * This function gets the value from config.php. If it does not exist, - * $default will be returned. - */ - public static function getValue($key, $default = null) { - return self::$object->getValue($key, $default); - } - - /** - * Sets a value - * @param string $key key - * @param mixed $value value - * - * This function sets the value and writes the config.php. - * - */ - public static function setValue($key, $value) { - self::$object->setValue($key, $value); - } - - /** - * Sets and deletes values and writes the config.php - * - * @param array $configs Associative array with `key => value` pairs - * If value is null, the config key will be deleted - */ - public static function setValues(array $configs) { - self::$object->setValues($configs); - } - - /** - * Removes a key from the config - * @param string $key key - * - * This function removes a key from the config.php. - */ - public static function deleteKey($key) { - self::$object->deleteKey($key); - } -} diff --git a/lib/private/log.php b/lib/private/log.php index ee5d61e98df..a722243dc69 100644 --- a/lib/private/log.php +++ b/lib/private/log.php @@ -227,7 +227,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 && StringUtils::equals($request->getParam('log_secret'), $logCondition['shared_secret'])) { + if($request && hash_equals($logCondition['shared_secret'], $request->getParam('log_secret'))) { $this->logConditionSatisfied = true; } } diff --git a/lib/private/log/owncloud.php b/lib/private/log/owncloud.php index f8a5f7534c5..e455824a8dd 100644 --- a/lib/private/log/owncloud.php +++ b/lib/private/log/owncloud.php @@ -73,7 +73,7 @@ class OC_Log_Owncloud { } catch (Exception $e) { $timezone = new DateTimeZone('UTC'); } - $time = DateTime::createFromFormat("U.u", microtime(true), $timezone); + $time = DateTime::createFromFormat("U.u", number_format(microtime(true), 4, ".", ""), $timezone); if ($time === false) { $time = new DateTime(null, $timezone); } @@ -101,6 +101,9 @@ class OC_Log_Owncloud { // Fall back to error_log error_log($entry); } + if (php_sapi_name() === 'cli-server') { + error_log($message, 4); + } } /** diff --git a/lib/private/memcache/apcu.php b/lib/private/memcache/apcu.php index 84147233ef0..778e27d4567 100644 --- a/lib/private/memcache/apcu.php +++ b/lib/private/memcache/apcu.php @@ -24,7 +24,101 @@ namespace OC\Memcache; -class APCu extends APC { +use OCP\IMemcache; + +class APCu extends Cache implements IMemcache { + use CASTrait { + cas as casEmulated; + } + + use CADTrait; + + public function get($key) { + $result = apcu_fetch($this->getPrefix() . $key, $success); + if (!$success) { + return null; + } + return $result; + } + + public function set($key, $value, $ttl = 0) { + return apcu_store($this->getPrefix() . $key, $value, $ttl); + } + + public function hasKey($key) { + return apcu_exists($this->getPrefix() . $key); + } + + public function remove($key) { + return apcu_delete($this->getPrefix() . $key); + } + + public function clear($prefix = '') { + $ns = $this->getPrefix() . $prefix; + $ns = preg_quote($ns, '/'); + if(class_exists('\APCIterator')) { + $iter = new \APCIterator('user', '/^' . $ns . '/', APC_ITER_KEY); + } else { + $iter = new \APCUIterator('user', '/^' . $ns . '/', APC_ITER_KEY); + } + return apcu_delete($iter); + } + + /** + * Set a value in the cache if it's not already stored + * + * @param string $key + * @param mixed $value + * @param int $ttl Time To Live in seconds. Defaults to 60*60*24 + * @return bool + */ + public function add($key, $value, $ttl = 0) { + return apcu_add($this->getPrefix() . $key, $value, $ttl); + } + + /** + * Increase a stored number + * + * @param string $key + * @param int $step + * @return int | bool + */ + public function inc($key, $step = 1) { + $this->add($key, 0); + return apcu_inc($this->getPrefix() . $key, $step); + } + + /** + * Decrease a stored number + * + * @param string $key + * @param int $step + * @return int | bool + */ + public function dec($key, $step = 1) { + return apcu_dec($this->getPrefix() . $key, $step); + } + + /** + * Compare and set + * + * @param string $key + * @param mixed $old + * @param mixed $new + * @return bool + */ + public function cas($key, $old, $new) { + // apc only does cas for ints + if (is_int($old) and is_int($new)) { + return apcu_cas($this->getPrefix() . $key, $old, $new); + } else { + return $this->casEmulated($key, $old, $new); + } + } + + /** + * @return bool + */ static public function isAvailable() { if (!extension_loaded('apcu')) { return false; @@ -32,7 +126,10 @@ class APCu extends APC { return false; } elseif (!\OC::$server->getIniWrapper()->getBool('apc.enable_cli') && \OC::$CLI) { return false; - } elseif (version_compare(phpversion('apc'), '4.0.6') === -1) { + } elseif ( + version_compare(phpversion('apc'), '4.0.6') === -1 && + version_compare(phpversion('apcu'), '5.1.0') === -1 + ) { return false; } else { return true; diff --git a/lib/private/ocs/cloud.php b/lib/private/ocs/cloud.php index 2cf40c449ff..1b04f43d477 100644 --- a/lib/private/ocs/cloud.php +++ b/lib/private/ocs/cloud.php @@ -26,7 +26,7 @@ class OC_OCS_Cloud { public static function getCapabilities() { $result = array(); - list($major, $minor, $micro) = OC_Util::getVersion(); + list($major, $minor, $micro) = \OCP\Util::getVersion(); $result['version'] = array( 'major' => $major, 'minor' => $minor, diff --git a/lib/private/ocsclient.php b/lib/private/ocsclient.php index e2973f82605..81c9abee058 100644 --- a/lib/private/ocsclient.php +++ b/lib/private/ocsclient.php @@ -97,6 +97,7 @@ class OCSClient { libxml_disable_entity_loader($loadEntities); if($data === false) { + libxml_clear_errors(); $this->logger->error( sprintf('Could not get %s, content was no valid XML', $action), [ diff --git a/lib/private/preview.php b/lib/private/preview.php index 38c043030fc..44d38b354a9 100644 --- a/lib/private/preview.php +++ b/lib/private/preview.php @@ -1168,7 +1168,7 @@ class Preview { */ private function getMimeIcon() { $image = new \OC_Image(); - $mimeIconWebPath = \OC_Helper::mimetypeIcon($this->mimeType); + $mimeIconWebPath = \OC::$server->getMimeTypeDetector()->mimeTypeIcon($this->mimeType); if (empty(\OC::$WEBROOT)) { $mimeIconServerPath = \OC::$SERVERROOT . $mimeIconWebPath; } else { diff --git a/lib/private/preview/movie.php b/lib/private/preview/movie.php index f71eaaf3eb2..2c2e6d09399 100644 --- a/lib/private/preview/movie.php +++ b/lib/private/preview/movie.php @@ -46,7 +46,7 @@ class Movie extends Provider { if ($useFileDirectly) { $absPath = $fileview->getLocalFile($path); } else { - $absPath = \OC_Helper::tmpFile(); + $absPath = \OC::$server->getTempManager()->getTemporaryFile(); $handle = $fileview->fopen($path, 'rb'); @@ -79,7 +79,7 @@ class Movie extends Provider { * @return bool|\OCP\IImage */ private function generateThumbNail($maxX, $maxY, $absPath, $second) { - $tmpPath = \OC_Helper::tmpFile(); + $tmpPath = \OC::$server->getTempManager()->getTemporaryFile(); if (self::$avconvBinary) { $cmd = self::$avconvBinary . ' -an -y -ss ' . escapeshellarg($second) . diff --git a/lib/private/repair.php b/lib/private/repair.php index d870b472c4f..269fe4c5f09 100644 --- a/lib/private/repair.php +++ b/lib/private/repair.php @@ -136,10 +136,11 @@ class Repair extends BasicEmitter { * @return array of RepairStep instances */ public static function getBeforeUpgradeRepairSteps() { + $connection = \OC::$server->getDatabaseConnection(); $steps = [ new InnoDB(), - new Collation(\OC::$server->getConfig(), \OC_DB::getConnection()), - new SqliteAutoincrement(\OC_DB::getConnection()), + new Collation(\OC::$server->getConfig(), $connection), + new SqliteAutoincrement($connection), new SearchLuceneTables(), ]; diff --git a/lib/private/repair/assetcache.php b/lib/private/repair/assetcache.php new file mode 100644 index 00000000000..c46aa63a3e4 --- /dev/null +++ b/lib/private/repair/assetcache.php @@ -0,0 +1,45 @@ +<?php +/** + * @author Adam Williamson <awilliam@redhat.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Repair; + +use Doctrine\DBAL\Platforms\MySqlPlatform; +use OC\Hooks\BasicEmitter; + +class AssetCache extends BasicEmitter implements \OC\RepairStep { + + public function getName() { + return 'Clear asset cache after upgrade'; + } + + public function run() { + if (!\OC_Template::isAssetPipelineEnabled()) { + $this->emit('\OC\Repair', 'info', array('Asset pipeline disabled -> nothing to do')); + return; + } + $assetDir = \OC::$server->getConfig()->getSystemValue('assetdirectory', \OC::$SERVERROOT) . '/assets'; + \OC_Helper::rmdirr($assetDir, false); + $this->emit('\OC\Repair', 'info', array('Asset cache cleared.')); + } +} + diff --git a/lib/private/repair/cleantags.php b/lib/private/repair/cleantags.php new file mode 100644 index 00000000000..d16a49fbca7 --- /dev/null +++ b/lib/private/repair/cleantags.php @@ -0,0 +1,146 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Repair; + +use OC\Hooks\BasicEmitter; +use OC\RepairStep; +use OCP\IDBConnection; + +/** + * Class RepairConfig + * + * @package OC\Repair + */ +class CleanTags extends BasicEmitter implements RepairStep { + + /** @var IDBConnection */ + protected $connection; + + /** + * @param IDBConnection $connection + */ + public function __construct(IDBConnection $connection) { + $this->connection = $connection; + } + + /** + * @return string + */ + public function getName() { + return 'Clean tags and favorites'; + } + + /** + * Updates the configuration after running an update + */ + public function run() { + $this->deleteOrphanFileEntries(); + $this->deleteOrphanTagEntries(); + $this->deleteOrphanCategoryEntries(); + } + + /** + * Delete tag entries for deleted files + */ + protected function deleteOrphanFileEntries() { + $this->deleteOrphanEntries( + '%d tags for delete files have been removed.', + 'vcategory_to_object', 'objid', + 'filecache', 'fileid', 'path_hash' + ); + } + + /** + * Delete tag entries for deleted tags + */ + protected function deleteOrphanTagEntries() { + $this->deleteOrphanEntries( + '%d tag entries for deleted tags have been removed.', + 'vcategory_to_object', 'categoryid', + 'vcategory', 'id', 'uid' + ); + } + + /** + * Delete tags that have no entries + */ + protected function deleteOrphanCategoryEntries() { + $this->deleteOrphanEntries( + '%d tags with no entries have been removed.', + 'vcategory', 'id', + 'vcategory_to_object', 'categoryid', 'type' + ); + } + + /** + * Deletes all entries from $deleteTable that do not have a matching entry in $sourceTable + * + * A query joins $deleteTable.$deleteId = $sourceTable.$sourceId and checks + * whether $sourceNullColumn is null. If it is null, the entry in $deleteTable + * is being deleted. + * + * @param string $repairInfo + * @param string $deleteTable + * @param string $deleteId + * @param string $sourceTable + * @param string $sourceId + * @param string $sourceNullColumn If this column is null in the source table, + * the entry is deleted in the $deleteTable + */ + protected function deleteOrphanEntries($repairInfo, $deleteTable, $deleteId, $sourceTable, $sourceId, $sourceNullColumn) { + $qb = $this->connection->getQueryBuilder(); + + $qb->select('d.' . $deleteId) + ->from($deleteTable, 'd') + ->leftJoin('d', $sourceTable, 's', $qb->expr()->eq('d.' . $deleteId, ' s.' . $sourceId)) + ->where( + $qb->expr()->eq('d.type', $qb->expr()->literal('files')) + ) + ->andWhere( + $qb->expr()->isNull('s.' . $sourceNullColumn) + ); + $result = $qb->execute(); + + $orphanItems = array(); + while ($row = $result->fetch()) { + $orphanItems[] = (int) $row[$deleteId]; + } + + if (!empty($orphanItems)) { + $orphanItemsBatch = array_chunk($orphanItems, 200); + foreach ($orphanItemsBatch as $items) { + $qb->delete($deleteTable) + ->where( + $qb->expr()->eq('type', $qb->expr()->literal('files')) + ) + ->andWhere($qb->expr()->in($deleteId, $qb->createParameter('ids'))); + $qb->setParameter('ids', $items, \Doctrine\DBAL\Connection::PARAM_INT_ARRAY); + $qb->execute(); + } + } + + if ($repairInfo) { + $this->emit('\OC\Repair', 'info', array(sprintf($repairInfo, sizeof($orphanItems)))); + } + } +} diff --git a/lib/private/repair/collation.php b/lib/private/repair/collation.php new file mode 100644 index 00000000000..7eb14f0ded2 --- /dev/null +++ b/lib/private/repair/collation.php @@ -0,0 +1,90 @@ +<?php +/** + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Repair; + +use Doctrine\DBAL\Platforms\MySqlPlatform; +use OC\Hooks\BasicEmitter; + +class Collation extends BasicEmitter implements \OC\RepairStep { + /** + * @var \OCP\IConfig + */ + protected $config; + + /** + * @var \OC\DB\Connection + */ + protected $connection; + + /** + * @param \OCP\IConfig $config + * @param \OC\DB\Connection $connection + */ + public function __construct($config, $connection) { + $this->connection = $connection; + $this->config = $config; + } + + public function getName() { + return 'Repair MySQL collation'; + } + + /** + * Fix mime types + */ + public function run() { + if (!$this->connection->getDatabasePlatform() instanceof MySqlPlatform) { + $this->emit('\OC\Repair', 'info', array('Not a mysql database -> nothing to no')); + return; + } + + $tables = $this->getAllNonUTF8BinTables($this->connection); + foreach ($tables as $table) { + $this->emit('\OC\Repair', 'info', array("Change collation for $table ...")); + $query = $this->connection->prepare('ALTER TABLE `' . $table . '` CONVERT TO CHARACTER SET utf8 COLLATE utf8_bin;'); + $query->execute(); + } + } + + /** + * @param \Doctrine\DBAL\Connection $connection + * @return string[] + */ + protected function getAllNonUTF8BinTables($connection) { + $dbName = $this->config->getSystemValue("dbname"); + $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 TABLE_NAME LIKE \"*PREFIX*%\"", + array($dbName) + ); + $result = array(); + foreach ($rows as $row) { + $result[] = $row['table']; + } + return $result; + } +} + diff --git a/lib/private/repair/dropoldjobs.php b/lib/private/repair/dropoldjobs.php new file mode 100644 index 00000000000..89d7f96a144 --- /dev/null +++ b/lib/private/repair/dropoldjobs.php @@ -0,0 +1,78 @@ +<?php +/** + * @author Arthur Schiwon <blizzz@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Repair; + +use OC\Hooks\BasicEmitter; +use OC\RepairStep; +use OCP\BackgroundJob\IJobList; + +class DropOldJobs extends BasicEmitter implements RepairStep { + + /** @var IJobList */ + protected $jobList; + + /** + * @param IJobList $jobList + */ + public function __construct(IJobList $jobList) { + $this->jobList = $jobList; + } + + /** + * Returns the step's name + * + * @return string + */ + public function getName() { + return 'Drop old background jobs'; + } + + /** + * Run repair step. + * Must throw exception on error. + * + * @throws \Exception in case of failure + */ + public function run() { + $oldJobs = $this->oldJobs(); + foreach($oldJobs as $job) { + if($this->jobList->has($job['class'], $job['arguments'])) { + $this->jobList->remove($job['class'], $job['arguments']); + } + } + } + + /** + * returns a list of old jobs as an associative array with keys 'class' and + * 'arguments'. + * + * @return array + */ + public function oldJobs() { + return [ + ['class' => 'OC_Cache_FileGlobalGC', 'arguments' => null], + ['class' => 'OC\Cache\FileGlobalGC', 'arguments' => null], + ]; + } + + +} diff --git a/lib/private/repair/dropoldtables.php b/lib/private/repair/dropoldtables.php new file mode 100644 index 00000000000..2d7fc8376b3 --- /dev/null +++ b/lib/private/repair/dropoldtables.php @@ -0,0 +1,96 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Repair; + + +use OC\Hooks\BasicEmitter; +use OC\RepairStep; +use OCP\IDBConnection; + +class DropOldTables extends BasicEmitter implements RepairStep { + + /** @var IDBConnection */ + protected $connection; + + /** + * @param IDBConnection $connection + */ + public function __construct(IDBConnection $connection) { + $this->connection = $connection; + } + + /** + * Returns the step's name + * + * @return string + */ + public function getName() { + return 'Drop old database tables'; + } + + /** + * Run repair step. + * Must throw exception on error. + * + * @throws \Exception in case of failure + */ + public function run() { + foreach ($this->oldDatabaseTables() as $tableName) { + if ($this->connection->tableExists($tableName)){ + $this->emit('\OC\Repair', 'info', [ + sprintf('Table %s has been deleted', $tableName) + ]); + $this->connection->dropTable($tableName); + } + } + } + + /** + * Returns a list of outdated tables which are not used anymore + * @return array + */ + protected function oldDatabaseTables() { + return [ + 'calendar_calendars', + 'calendar_objects', + 'calendar_share_calendar', + 'calendar_share_event', + 'file_map', + 'foldersize', + 'fscache', + 'gallery_sharing', + 'locks', + 'log', + 'media_albums', + 'media_artists', + 'media_sessions', + 'media_songs', + 'media_users', + 'permissions', + 'pictures_images_cache', + 'principalgroups', + 'principals', + 'queuedtasks', + 'sharing', + ]; + } +} diff --git a/lib/private/repair/filletags.php b/lib/private/repair/filletags.php new file mode 100644 index 00000000000..8cfc4a7c258 --- /dev/null +++ b/lib/private/repair/filletags.php @@ -0,0 +1,55 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Repair; + +use OC\Hooks\BasicEmitter; + +class FillETags extends BasicEmitter implements \OC\RepairStep { + + /** @var \OCP\IDBConnection */ + protected $connection; + + /** + * @param \OCP\IDBConnection $connection + */ + public function __construct($connection) { + $this->connection = $connection; + } + + public function getName() { + return 'Generate ETags for file where no ETag is present.'; + } + + public function run() { + $qb = $this->connection->getQueryBuilder(); + $qb->update('filecache') + ->set('etag', $qb->expr()->literal('xxx')) + ->where($qb->expr()->eq('etag', $qb->expr()->literal(''))) + ->orWhere($qb->expr()->isNull('etag')); + + $result = $qb->execute(); + $this->emit('\OC\Repair', 'info', array("ETags have been fixed for $result files/folders.")); + } +} + diff --git a/lib/private/repair/innodb.php b/lib/private/repair/innodb.php new file mode 100644 index 00000000000..4bbfdcea20a --- /dev/null +++ b/lib/private/repair/innodb.php @@ -0,0 +1,69 @@ +<?php +/** + * @author Jörn Friedrich Dreyer <jfd@butonic.de> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Robin Appelman <icewind@owncloud.com> + * @author Thomas Müller <thomas.mueller@tmit.eu> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Repair; + +use Doctrine\DBAL\Platforms\MySqlPlatform; +use OC\Hooks\BasicEmitter; + +class InnoDB extends BasicEmitter implements \OC\RepairStep { + + public function getName() { + return 'Repair MySQL database engine'; + } + + /** + * Fix mime types + */ + public function run() { + $connection = \OC::$server->getDatabaseConnection(); + if (!$connection->getDatabasePlatform() instanceof MySqlPlatform) { + $this->emit('\OC\Repair', 'info', array('Not a mysql database -> nothing to do')); + return; + } + + $tables = $this->getAllMyIsamTables($connection); + if (is_array($tables)) { + foreach ($tables as $table) { + $connection->exec("ALTER TABLE $table ENGINE=InnoDB;"); + $this->emit('\OC\Repair', 'info', array("Fixed $table")); + } + } + } + + /** + * @param \Doctrine\DBAL\Connection $connection + * @return string[] + */ + private function getAllMyIsamTables($connection) { + $dbName = \OC::$server->getConfig()->getSystemValue("dbname"); + $result = $connection->fetchArray( + "SELECT table_name FROM information_schema.tables WHERE table_schema = ? AND engine = 'MyISAM' AND TABLE_NAME LIKE \"*PREFIX*%\"", + array($dbName) + ); + + return $result; + } +} + diff --git a/lib/private/repair/oldgroupmembershipshares.php b/lib/private/repair/oldgroupmembershipshares.php new file mode 100644 index 00000000000..2d701ac9fb7 --- /dev/null +++ b/lib/private/repair/oldgroupmembershipshares.php @@ -0,0 +1,117 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Repair; + + +use OC\Hooks\BasicEmitter; +use OC\RepairStep; +use OCP\IDBConnection; +use OCP\IGroupManager; +use OCP\Share; + +class OldGroupMembershipShares extends BasicEmitter implements RepairStep { + + /** @var \OCP\IDBConnection */ + protected $connection; + + /** @var \OCP\IGroupManager */ + protected $groupManager; + + /** + * @var array [gid => [uid => (bool)]] + */ + protected $memberships; + + /** + * @param IDBConnection $connection + * @param IGroupManager $groupManager + */ + public function __construct(IDBConnection $connection, IGroupManager $groupManager) { + $this->connection = $connection; + $this->groupManager = $groupManager; + } + + /** + * Returns the step's name + * + * @return string + */ + public function getName() { + return 'Remove shares of old group memberships'; + } + + /** + * Run repair step. + * Must throw exception on error. + * + * @throws \Exception in case of failure + */ + public function run() { + $deletedEntries = 0; + + $query = $this->connection->getQueryBuilder(); + $query->select(['s1.id', $query->createFunction('s1.`share_with` AS `user`'), $query->createFunction('s2.`share_with` AS `group`')]) + ->from('share', 's1') + ->where($query->expr()->isNotNull('s1.parent')) + // \OC\Share\Constant::$shareTypeGroupUserUnique === 2 + ->andWhere($query->expr()->eq('s1.share_type', $query->expr()->literal(2))) + ->andWhere($query->expr()->isNotNull('s2.id')) + ->andWhere($query->expr()->eq('s2.share_type', $query->expr()->literal(Share::SHARE_TYPE_GROUP))) + ->leftJoin('s1', 'share', 's2', $query->expr()->eq('s1.parent', 's2.id')); + + $deleteQuery = $this->connection->getQueryBuilder(); + $deleteQuery->delete('share') + ->where($query->expr()->eq('id', $deleteQuery->createParameter('share'))); + + $result = $query->execute(); + while ($row = $result->fetch()) { + if (!$this->isMember($row['group'], $row['user'])) { + $deletedEntries += $deleteQuery->setParameter('share', (int) $row['id']) + ->execute(); + } + } + $result->closeCursor(); + + if ($deletedEntries) { + $this->emit('\OC\Repair', 'info', array('Removed ' . $deletedEntries . ' shares where user is not a member of the group anymore')); + } + } + + /** + * @param string $gid + * @param string $uid + * @return bool + */ + protected function isMember($gid, $uid) { + if (isset($this->memberships[$gid][$uid])) { + return $this->memberships[$gid][$uid]; + } + + $isMember = $this->groupManager->isInGroup($uid, $gid); + if (!isset($this->memberships[$gid])) { + $this->memberships[$gid] = []; + } + $this->memberships[$gid][$uid] = $isMember; + + return $isMember; + } +} diff --git a/lib/private/repair/preview.php b/lib/private/repair/preview.php new file mode 100644 index 00000000000..2284da93734 --- /dev/null +++ b/lib/private/repair/preview.php @@ -0,0 +1,45 @@ +<?php +/** + * @author Georg Ehrke <georg@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Repair; + +use OC\Files\View; +use OC\Hooks\BasicEmitter; + +class Preview extends BasicEmitter implements \OC\RepairStep { + + public function getName() { + return 'Cleaning-up broken previews'; + } + + public function run() { + $view = new View('/'); + $children = $view->getDirectoryContent('/'); + + foreach ($children as $child) { + if ($view->is_dir($child->getPath())) { + $thumbnailsFolder = $child->getPath() . '/thumbnails'; + if ($view->is_dir($thumbnailsFolder)) { + $view->rmdir($thumbnailsFolder); + } + } + } + } +}
\ No newline at end of file diff --git a/lib/private/repair/removegetetagentries.php b/lib/private/repair/removegetetagentries.php new file mode 100644 index 00000000000..40040763654 --- /dev/null +++ b/lib/private/repair/removegetetagentries.php @@ -0,0 +1,59 @@ +<?php +/** + * @author Morris Jobke <hey@morrisjobke.de> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Repair; + +use OC\Hooks\BasicEmitter; +use OCP\IDBConnection; + +class RemoveGetETagEntries extends BasicEmitter { + + /** + * @var IDBConnection + */ + protected $connection; + + /** + * @param IDBConnection $connection + */ + public function __construct(IDBConnection $connection) { + $this->connection = $connection; + } + + public function getName() { + return 'Remove getetag entries in properties table'; + } + + /** + * Removes all entries with the key "{DAV:}getetag" from the table properties + */ + public function run() { + $sql = 'DELETE FROM `*PREFIX*properties`' + . ' WHERE `propertyname` = ?'; + $deletedRows = $this->connection->executeUpdate($sql, ['{DAV:}getetag']); + + $this->emit( + '\OC\Repair', + 'info', + ['Removed ' . $deletedRows . ' unneeded "{DAV:}getetag" entries from properties table.'] + ); + } +} diff --git a/lib/private/repair/repairinvalidshares.php b/lib/private/repair/repairinvalidshares.php new file mode 100644 index 00000000000..5a4cb445ce9 --- /dev/null +++ b/lib/private/repair/repairinvalidshares.php @@ -0,0 +1,112 @@ +<?php +/** + * @author Morris Jobke <hey@morrisjobke.de> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Repair; + +use OC\Hooks\BasicEmitter; + +/** + * Repairs shares with invalid data + */ +class RepairInvalidShares extends BasicEmitter implements \OC\RepairStep { + + /** + * @var \OCP\IConfig + */ + protected $config; + + /** + * @var \OCP\IDBConnection + */ + protected $connection; + + /** + * @param \OCP\IConfig $config + * @param \OCP\IDBConnection $connection + */ + public function __construct($config, $connection) { + $this->connection = $connection; + $this->config = $config; + } + + public function getName() { + return 'Repair invalid shares'; + } + + /** + * Past bugs would make it possible to set an expiration date on user shares even + * though it is not supported. This functions removes the expiration date from such entries. + */ + private function removeExpirationDateFromNonLinkShares() { + $builder = $this->connection->getQueryBuilder(); + $builder + ->update('share') + ->set('expiration', 'null') + ->where($builder->expr()->isNotNull('expiration')) + ->andWhere($builder->expr()->neq('share_type', $builder->expr()->literal(\OC\Share\Constants::SHARE_TYPE_LINK))); + + $updatedEntries = $builder->execute(); + if ($updatedEntries > 0) { + $this->emit('\OC\Repair', 'info', array('Removed invalid expiration date from ' . $updatedEntries . ' shares')); + } + } + + /** + * Remove shares where the parent share does not exist anymore + */ + private function removeSharesNonExistingParent() { + $deletedEntries = 0; + + $query = $this->connection->getQueryBuilder(); + $query->select('s1.parent') + ->from('share', 's1') + ->where($query->expr()->isNotNull('s1.parent')) + ->andWhere($query->expr()->isNull('s2.id')) + ->leftJoin('s1', 'share', 's2', $query->expr()->eq('s1.parent', 's2.id')) + ->groupBy('s1.parent'); + + $deleteQuery = $this->connection->getQueryBuilder(); + $deleteQuery->delete('share') + ->where($query->expr()->eq('parent', $deleteQuery->createParameter('parent'))); + + $result = $query->execute(); + while ($row = $result->fetch()) { + $deletedEntries += $deleteQuery->setParameter('parent', (int) $row['parent']) + ->execute(); + } + $result->closeCursor(); + + if ($deletedEntries) { + $this->emit('\OC\Repair', 'info', array('Removed ' . $deletedEntries . ' shares where the parent did not exist')); + } + } + + public function run() { + $ocVersionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0'); + if (version_compare($ocVersionFromBeforeUpdate, '8.2.0.7', '<')) { + // this situation was only possible before 8.2 + $this->removeExpirationDateFromNonLinkShares(); + } + + $this->removeSharesNonExistingParent(); + } +} diff --git a/lib/private/repair/repairlegacystorages.php b/lib/private/repair/repairlegacystorages.php new file mode 100644 index 00000000000..5ba452cbbc6 --- /dev/null +++ b/lib/private/repair/repairlegacystorages.php @@ -0,0 +1,263 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Repair; + +use OC\Files\Cache\Storage; +use OC\Hooks\BasicEmitter; +use OC\RepairException; + +class RepairLegacyStorages extends BasicEmitter { + /** + * @var \OCP\IConfig + */ + protected $config; + + /** + * @var \OCP\IDBConnection + */ + protected $connection; + + protected $findStorageInCacheStatement; + protected $renameStorageStatement; + + /** + * @param \OCP\IConfig $config + * @param \OCP\IDBConnection $connection + */ + public function __construct($config, $connection) { + $this->connection = $connection; + $this->config = $config; + + $this->findStorageInCacheStatement = $this->connection->prepare( + 'SELECT DISTINCT `storage` FROM `*PREFIX*filecache`' + . ' WHERE `storage` in (?, ?)' + ); + $this->renameStorageStatement = $this->connection->prepare( + 'UPDATE `*PREFIX*storages`' + . ' SET `id` = ?' + . ' WHERE `id` = ?' + ); + } + + public function getName() { + return 'Repair legacy storages'; + } + + /** + * Extracts the user id from a legacy storage id + * + * @param string $storageId legacy storage id in the + * format "local::/path/to/datadir/userid" + * @return string user id extracted from the storage id + */ + private function extractUserId($storageId) { + $storageId = rtrim($storageId, '/'); + $pos = strrpos($storageId, '/'); + return substr($storageId, $pos + 1); + } + + /** + * Fix the given legacy storage by renaming the old id + * to the new id. If the new id already exists, whichever + * storage that has data in the file cache will be used. + * If both have data, nothing will be done and false is + * returned. + * + * @param string $oldId old storage id + * @param int $oldNumericId old storage numeric id + * @param string $userId + * @return bool true if fixed, false otherwise + * @throws RepairException + */ + private function fixLegacyStorage($oldId, $oldNumericId, $userId = null) { + // check whether the new storage already exists + if (is_null($userId)) { + $userId = $this->extractUserId($oldId); + } + $newId = 'home::' . $userId; + + // check if target id already exists + $newNumericId = Storage::getNumericStorageId($newId); + if (!is_null($newNumericId)) { + $newNumericId = (int)$newNumericId; + // try and resolve the conflict + // check which one of "local::" or "home::" needs to be kept + $this->findStorageInCacheStatement->execute(array($oldNumericId, $newNumericId)); + $row1 = $this->findStorageInCacheStatement->fetch(); + $row2 = $this->findStorageInCacheStatement->fetch(); + $this->findStorageInCacheStatement->closeCursor(); + if ($row2 !== false) { + // two results means both storages have data, not auto-fixable + throw new RepairException( + 'Could not automatically fix legacy storage ' + . '"' . $oldId . '" => "' . $newId . '"' + . ' because they both have data.' + ); + } + if ($row1 === false || (int)$row1['storage'] === $oldNumericId) { + // old storage has data, then delete the empty new id + $toDelete = $newId; + } else if ((int)$row1['storage'] === $newNumericId) { + // new storage has data, then delete the empty old id + $toDelete = $oldId; + } else { + // unknown case, do not continue + return false; + } + + // delete storage including file cache + Storage::remove($toDelete); + + // if we deleted the old id, the new id will be used + // automatically + if ($toDelete === $oldId) { + // nothing more to do + return true; + } + } + + // rename old id to new id + $newId = Storage::adjustStorageId($newId); + $oldId = Storage::adjustStorageId($oldId); + $rowCount = $this->renameStorageStatement->execute(array($newId, $oldId)); + $this->renameStorageStatement->closeCursor(); + return ($rowCount === 1); + } + + /** + * Converts legacy home storage ids in the format + * "local::/data/dir/path/userid/" to the new format "home::userid" + */ + public function run() { + // only run once + if ($this->config->getAppValue('core', 'repairlegacystoragesdone') === 'yes') { + return; + } + + $dataDir = $this->config->getSystemValue('datadirectory', \OC::$SERVERROOT . '/data/'); + $dataDir = rtrim($dataDir, '/') . '/'; + $dataDirId = 'local::' . $dataDir; + + $count = 0; + $hasWarnings = false; + + $this->connection->beginTransaction(); + + // note: not doing a direct UPDATE with the REPLACE function + // because regexp search/extract is needed and it is not guaranteed + // to work on all database types + $sql = 'SELECT `id`, `numeric_id` FROM `*PREFIX*storages`' + . ' WHERE `id` LIKE ?' + . ' ORDER BY `id`'; + $result = $this->connection->executeQuery($sql, array($dataDirId . '%')); + + while ($row = $result->fetch()) { + $currentId = $row['id']; + // one entry is the datadir itself + if ($currentId === $dataDirId) { + continue; + } + + try { + if ($this->fixLegacyStorage($currentId, (int)$row['numeric_id'])) { + $count++; + } + } + catch (RepairException $e) { + $hasWarnings = true; + $this->emit( + '\OC\Repair', + 'warning', + array('Could not repair legacy storage ' . $currentId . ' automatically.') + ); + } + } + + // check for md5 ids, not in the format "prefix::" + $sql = 'SELECT COUNT(*) AS "c" FROM `*PREFIX*storages`' + . ' WHERE `id` NOT LIKE \'%::%\''; + $result = $this->connection->executeQuery($sql); + $row = $result->fetch(); + + // find at least one to make sure it's worth + // querying the user list + if ((int)$row['c'] > 0) { + $userManager = \OC::$server->getUserManager(); + + // use chunks to avoid caching too many users in memory + $limit = 30; + $offset = 0; + + do { + // query the next page of users + $results = $userManager->search('', $limit, $offset); + $storageIds = array(); + foreach ($results as $uid => $userObject) { + $storageId = $dataDirId . $uid . '/'; + if (strlen($storageId) <= 64) { + // skip short storage ids as they were handled in the previous section + continue; + } + $storageIds[$uid] = $storageId; + } + + if (count($storageIds) > 0) { + // update the storages of these users + foreach ($storageIds as $uid => $storageId) { + $numericId = Storage::getNumericStorageId($storageId); + try { + if (!is_null($numericId) && $this->fixLegacyStorage($storageId, (int)$numericId)) { + $count++; + } + } + catch (RepairException $e) { + $hasWarnings = true; + $this->emit( + '\OC\Repair', + 'warning', + array('Could not repair legacy storage ' . $storageId . ' automatically.') + ); + } + } + } + $offset += $limit; + } while (count($results) >= $limit); + } + + $this->emit('\OC\Repair', 'info', array('Updated ' . $count . ' legacy home storage ids')); + + $this->connection->commit(); + + if ($hasWarnings) { + $this->emit( + '\OC\Repair', + 'warning', + array('Some legacy storages could not be repaired. Please manually fix them then re-run ./occ maintenance:repair') + ); + } else { + // if all were done, no need to redo the repair during next upgrade + $this->config->setAppValue('core', 'repairlegacystoragesdone', 'yes'); + } + } +} diff --git a/lib/private/repair/repairmimetypes.php b/lib/private/repair/repairmimetypes.php new file mode 100644 index 00000000000..e687dbde688 --- /dev/null +++ b/lib/private/repair/repairmimetypes.php @@ -0,0 +1,360 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * @author Morris Jobke <hey@morrisjobke.de> + * @author Normal Ra <normalraw@gmail.com> + * @author Olivier Paroz <github@oparoz.com> + * @author Roeland Jago Douma <rullzer@owncloud.com> + * @author Victor Dubiniuk <dubiniuk@owncloud.com> + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Repair; + +use OC\Hooks\BasicEmitter; + +class RepairMimeTypes extends BasicEmitter implements \OC\RepairStep { + /** + * @var \OCP\IConfig + */ + protected $config; + + /** + * @var int + */ + protected $folderMimeTypeId; + + /** + * @param \OCP\IConfig $config + */ + public function __construct($config) { + $this->config = $config; + } + + public function getName() { + return 'Repair mime types'; + } + + private static function existsStmt() { + return \OC_DB::prepare(' + SELECT count(`mimetype`) + FROM `*PREFIX*mimetypes` + WHERE `mimetype` = ? + '); + } + + private static function getIdStmt() { + return \OC_DB::prepare(' + SELECT `id` + FROM `*PREFIX*mimetypes` + WHERE `mimetype` = ? + '); + } + + private static function insertStmt() { + return \OC_DB::prepare(' + INSERT INTO `*PREFIX*mimetypes` ( `mimetype` ) + VALUES ( ? ) + '); + } + + private static function updateWrongStmt() { + return \OC_DB::prepare(' + UPDATE `*PREFIX*filecache` + SET `mimetype` = ( + SELECT `id` + FROM `*PREFIX*mimetypes` + WHERE `mimetype` = ? + ) WHERE `mimetype` = ? + '); + } + + private static function deleteStmt() { + return \OC_DB::prepare(' + DELETE FROM `*PREFIX*mimetypes` + WHERE `id` = ? + '); + } + + private static function updateByNameStmt() { + return \OC_DB::prepare(' + UPDATE `*PREFIX*filecache` + SET `mimetype` = ? + WHERE `mimetype` <> ? AND `mimetype` <> ? AND `name` ILIKE ? + '); + } + + private function repairMimetypes($wrongMimetypes) { + foreach ($wrongMimetypes as $wrong => $correct) { + // do we need to remove a wrong mimetype? + $result = \OC_DB::executeAudited(self::getIdStmt(), array($wrong)); + $wrongId = $result->fetchOne(); + + if ($wrongId !== false) { + // do we need to insert the correct mimetype? + $result = \OC_DB::executeAudited(self::existsStmt(), array($correct)); + $exists = $result->fetchOne(); + + if (!is_null($correct)) { + if (!$exists) { + // insert mimetype + \OC_DB::executeAudited(self::insertStmt(), array($correct)); + } + + // change wrong mimetype to correct mimetype in filecache + \OC_DB::executeAudited(self::updateWrongStmt(), array($correct, $wrongId)); + } + + // delete wrong mimetype + \OC_DB::executeAudited(self::deleteStmt(), array($wrongId)); + + } + } + } + + private function updateMimetypes($updatedMimetypes) { + if (empty($this->folderMimeTypeId)) { + $result = \OC_DB::executeAudited(self::getIdStmt(), array('httpd/unix-directory')); + $this->folderMimeTypeId = (int)$result->fetchOne(); + } + + foreach ($updatedMimetypes as $extension => $mimetype) { + $result = \OC_DB::executeAudited(self::existsStmt(), array($mimetype)); + $exists = $result->fetchOne(); + + if (!$exists) { + // insert mimetype + \OC_DB::executeAudited(self::insertStmt(), array($mimetype)); + } + + // get target mimetype id + $result = \OC_DB::executeAudited(self::getIdStmt(), array($mimetype)); + $mimetypeId = $result->fetchOne(); + + // change mimetype for files with x extension + \OC_DB::executeAudited(self::updateByNameStmt(), array($mimetypeId, $this->folderMimeTypeId, $mimetypeId, '%.' . $extension)); + } + } + + private function fixOfficeMimeTypes() { + // update wrong mimetypes + $wrongMimetypes = array( + 'application/mspowerpoint' => 'application/vnd.ms-powerpoint', + 'application/msexcel' => 'application/vnd.ms-excel', + ); + + self::repairMimetypes($wrongMimetypes); + + $updatedMimetypes = array( + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + ); + + + // separate doc from docx etc + self::updateMimetypes($updatedMimetypes); + + } + + private function fixApkMimeType() { + $updatedMimetypes = array( + 'apk' => 'application/vnd.android.package-archive', + ); + + self::updateMimetypes($updatedMimetypes); + } + + private function fixFontsMimeTypes() { + // update wrong mimetypes + $wrongMimetypes = array( + 'font' => null, + 'font/opentype' => 'application/font-sfnt', + 'application/x-font-ttf' => 'application/font-sfnt', + ); + + self::repairMimetypes($wrongMimetypes); + + $updatedMimetypes = array( + 'ttf' => 'application/font-sfnt', + 'otf' => 'application/font-sfnt', + 'pfb' => 'application/x-font', + ); + + self::updateMimetypes($updatedMimetypes); + } + + private function fixPostscriptMimeType() { + $updatedMimetypes = array( + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + ); + + self::updateMimetypes($updatedMimetypes); + } + + private function introduceRawMimeType() { + $updatedMimetypes = array( + 'arw' => 'image/x-dcraw', + 'cr2' => 'image/x-dcraw', + 'dcr' => 'image/x-dcraw', + 'dng' => 'image/x-dcraw', + 'erf' => 'image/x-dcraw', + 'iiq' => 'image/x-dcraw', + 'k25' => 'image/x-dcraw', + 'kdc' => 'image/x-dcraw', + 'mef' => 'image/x-dcraw', + 'nef' => 'image/x-dcraw', + 'orf' => 'image/x-dcraw', + 'pef' => 'image/x-dcraw', + 'raf' => 'image/x-dcraw', + 'rw2' => 'image/x-dcraw', + 'srf' => 'image/x-dcraw', + 'sr2' => 'image/x-dcraw', + 'xrf' => 'image/x-dcraw', + ); + + self::updateMimetypes($updatedMimetypes); + } + + private function introduce3dImagesMimeType() { + $updatedMimetypes = array( + 'jps' => 'image/jpeg', + 'mpo' => 'image/jpeg', + ); + + self::updateMimetypes($updatedMimetypes); + } + + private function introduceConfMimeType() { + $updatedMimetypes = array( + 'conf' => 'text/plain', + 'cnf' => 'text/plain', + ); + + self::updateMimetypes($updatedMimetypes); + } + + private function introduceYamlMimeType() { + $updatedMimetypes = array( + 'yaml' => 'application/yaml', + 'yml' => 'application/yaml', + ); + + self::updateMimetypes($updatedMimetypes); + } + + private function introduceJavaMimeType() { + $updatedMimetypes = array( + 'class' => 'application/java', + 'java' => 'text/x-java-source', + ); + + self::updateMimetypes($updatedMimetypes); + } + + private function introduceHppMimeType() { + $updatedMimetypes = array( + 'hpp' => 'text/x-h', + ); + + self::updateMimetypes($updatedMimetypes); + } + + private function introduceRssMimeType() { + $updatedMimetypes = array( + 'rss' => 'application/rss+xml', + ); + + self::updateMimetypes($updatedMimetypes); + } + + private function introduceRtfMimeType() { + $updatedMimetypes = array( + 'rtf' => 'text/rtf', + ); + + self::updateMimetypes($updatedMimetypes); + } + + /** + * Fix mime types + */ + public function run() { + + $ocVersionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0'); + + // NOTE TO DEVELOPERS: when adding new mime types, please make sure to + // add a version comparison to avoid doing it every time + + // only update mime types if necessary as it can be expensive + if (version_compare($ocVersionFromBeforeUpdate, '8.2.0', '<')) { + if ($this->fixOfficeMimeTypes()) { + $this->emit('\OC\Repair', 'info', array('Fixed office mime types')); + } + + if ($this->fixApkMimeType()) { + $this->emit('\OC\Repair', 'info', array('Fixed APK mime type')); + } + + if ($this->fixFontsMimeTypes()) { + $this->emit('\OC\Repair', 'info', array('Fixed fonts mime types')); + } + + if ($this->fixPostscriptMimeType()) { + $this->emit('\OC\Repair', 'info', array('Fixed Postscript mime types')); + } + + if ($this->introduceRawMimeType()) { + $this->emit('\OC\Repair', 'info', array('Fixed Raw mime types')); + } + + if ($this->introduce3dImagesMimeType()) { + $this->emit('\OC\Repair', 'info', array('Fixed 3D images mime types')); + } + + if ($this->introduceConfMimeType()) { + $this->emit('\OC\Repair', 'info', array('Fixed Conf/cnf mime types')); + } + + if ($this->introduceYamlMimeType()) { + $this->emit('\OC\Repair', 'info', array('Fixed Yaml/Yml mime types')); + } + } + + // Mimetype updates from #19272 + if (version_compare($ocVersionFromBeforeUpdate, '8.2.0.8', '<')) { + if ($this->introduceJavaMimeType()) { + $this->emit('\OC\Repair', 'info', array('Fixed java/class mime types')); + } + + if ($this->introduceHppMimeType()) { + $this->emit('\OC\Repair', 'info', array('Fixed hpp mime type')); + } + + if ($this->introduceRssMimeType()) { + $this->emit('\OC\Repair', 'info', array('Fixed rss mime type')); + } + + if ($this->introduceRtfMimeType()) { + $this->emit('\OC\Repair', 'info', array('Fixed rtf mime type')); + } + } + } +} diff --git a/lib/private/repair/searchlucenetables.php b/lib/private/repair/searchlucenetables.php new file mode 100644 index 00000000000..52d41083c45 --- /dev/null +++ b/lib/private/repair/searchlucenetables.php @@ -0,0 +1,77 @@ +<?php +/** + * @author Jörn Friedrich Dreyer <jfd@butonic.de> + * @author Morris Jobke <hey@morrisjobke.de> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Repair; + +use OC\Hooks\BasicEmitter; + +class SearchLuceneTables extends BasicEmitter implements \OC\RepairStep { + + public function getName() { + return 'Repair duplicate entries in oc_lucene_status'; + } + + /** + * Fix duplicate entries in oc_lucene_status + * + * search_lucene prior to v0.5.0 did not have a primary key on the lucene_status table. Newer versions do, which + * causes the migration check to fail because it tries to insert duplicate rows into the new schema. + * + * FIXME Currently, apps don't have a way of repairing anything before the migration check: + * @link https://github.com/owncloud/core/issues/10980 + * + * As a result this repair step needs to live in the core repo, although it belongs into search_lucene: + * @link https://github.com/owncloud/core/issues/10205#issuecomment-54957557 + * + * It will completely remove any rows that make a file id have more than one status: + * fileid | status fileid | status + * --------+-------- will become --------+-------- + * 2 | E 3 | E + * 2 | I + * 3 | E + * + * search_lucene will then reindex the fileids without a status when the next indexing job is executed + */ + public function run() { + $connection = \OC::$server->getDatabaseConnection(); + if ($connection->tableExists('lucene_status')) { + $this->emit('\OC\Repair', 'info', array('removing duplicate entries from lucene_status')); + + $query = $connection->prepare(' + DELETE FROM `*PREFIX*lucene_status` + WHERE `fileid` IN ( + SELECT `fileid` + FROM ( + SELECT `fileid` + FROM `*PREFIX*lucene_status` + GROUP BY `fileid` + HAVING count(`fileid`) > 1 + ) AS `mysqlerr1093hack` + )'); + $query->execute(); + } else { + $this->emit('\OC\Repair', 'info', array('lucene_status table does not exist -> nothing to do')); + } + } + +} + diff --git a/lib/private/repair/sqliteautoincrement.php b/lib/private/repair/sqliteautoincrement.php new file mode 100644 index 00000000000..70d0adae5d7 --- /dev/null +++ b/lib/private/repair/sqliteautoincrement.php @@ -0,0 +1,98 @@ +<?php +/** + * @author Vincent Petry <pvince81@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Repair; + +use Doctrine\DBAL\Platforms\SqlitePlatform; +use Doctrine\DBAL\Schema\SchemaException; +use Doctrine\DBAL\Schema\SchemaDiff; +use Doctrine\DBAL\Schema\TableDiff; +use Doctrine\DBAL\Schema\ColumnDiff; +use OC\Hooks\BasicEmitter; + +/** + * Fixes Sqlite autoincrement by forcing the SQLite table schemas to be + * altered in order to retrigger SQL schema generation through OCSqlitePlatform. + */ +class SqliteAutoincrement extends BasicEmitter implements \OC\RepairStep { + /** + * @var \OC\DB\Connection + */ + protected $connection; + + /** + * @param \OC\DB\Connection $connection + */ + public function __construct($connection) { + $this->connection = $connection; + } + + public function getName() { + return 'Repair SQLite autoincrement'; + } + + /** + * Fix mime types + */ + public function run() { + if (!$this->connection->getDatabasePlatform() instanceof SqlitePlatform) { + return; + } + + $sourceSchema = $this->connection->getSchemaManager()->createSchema(); + + $schemaDiff = new SchemaDiff(); + + foreach ($sourceSchema->getTables() as $tableSchema) { + $primaryKey = $tableSchema->getPrimaryKey(); + if (!$primaryKey) { + continue; + } + + $columnNames = $primaryKey->getColumns(); + + // add a column diff for every primary key column, + // but do not actually change anything, this will + // force the generation of SQL statements to alter + // those tables, which will then trigger the + // specific SQL code from OCSqlitePlatform + try { + $tableDiff = new TableDiff($tableSchema->getName()); + $tableDiff->fromTable = $tableSchema; + foreach ($columnNames as $columnName) { + $columnSchema = $tableSchema->getColumn($columnName); + $columnDiff = new ColumnDiff($columnSchema->getName(), $columnSchema); + $tableDiff->changedColumns[] = $columnDiff; + $schemaDiff->changedTables[] = $tableDiff; + } + } catch (SchemaException $e) { + // ignore + } + } + + $this->connection->beginTransaction(); + foreach ($schemaDiff->toSql($this->connection->getDatabasePlatform()) as $sql) { + $this->connection->query($sql); + } + $this->connection->commit(); + } +} + diff --git a/lib/private/repair/updatecertificatestore.php b/lib/private/repair/updatecertificatestore.php new file mode 100644 index 00000000000..5fad309a959 --- /dev/null +++ b/lib/private/repair/updatecertificatestore.php @@ -0,0 +1,88 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Repair; + +use OC\Files\View; +use OC\Hooks\BasicEmitter; +use OC\RepairStep; +use OC\Server; +use OCP\IConfig; + +/** + * Class UpdateCertificateStore rewrites the user specific certificate store after + * an update has been performed. This is done because a new root certificate file + * might have been added. + * + * @package OC\Repair + */ +class UpdateCertificateStore extends BasicEmitter implements RepairStep { + /** + * FIXME: The certificate manager does only allow specifying the user + * within the constructor. This makes DI impossible. + * @var Server + */ + protected $server; + /** @var IConfig */ + protected $config; + + /** + * @param Server $server + * @param IConfig $config + */ + public function __construct(Server $server, + IConfig $config) { + $this->server = $server; + $this->config = $config; + } + + /** {@inheritDoc} */ + public function getName() { + return 'Update user certificate stores with new root certificates'; + } + + /** {@inheritDoc} */ + public function run() { + $rootView = new View(); + $dataDirectory = $this->config->getSystemValue('datadirectory', null); + if(is_null($dataDirectory)) { + throw new \Exception('No data directory specified'); + } + + $pathToRootCerts = '/files_external/rootcerts.crt'; + + foreach($rootView->getDirectoryContent('', 'httpd/unix-directory') as $fileInfo) { + $uid = trim($fileInfo->getPath(), '/'); + if($rootView->file_exists($uid . $pathToRootCerts)) { + // Delete the existing root certificate + $rootView->unlink($uid . $pathToRootCerts); + + /** + * FIXME: The certificate manager does only allow specifying the user + * within the constructor. This makes DI impossible. + */ + // Regenerate the certificates + $certificateManager = $this->server->getCertificateManager($uid); + $certificateManager->createCertificateBundle(); + } + } + } +} diff --git a/lib/private/repair/updateoutdatedocsids.php b/lib/private/repair/updateoutdatedocsids.php new file mode 100644 index 00000000000..5f6ee029536 --- /dev/null +++ b/lib/private/repair/updateoutdatedocsids.php @@ -0,0 +1,108 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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\Repair; + +use OC\Hooks\BasicEmitter; +use OC\RepairStep; +use OCP\IConfig; + +/** + * Class UpdateOutdatedOcsIds is used to update invalid outdated OCS IDs, this is + * for example the case when an application has had another OCS ID in the past such + * as for contacts and calendar when apps.owncloud.com migrated to a unified identifier + * for multiple versions. + * + * @package OC\Repair + */ +class UpdateOutdatedOcsIds extends BasicEmitter implements RepairStep { + /** @var IConfig */ + private $config; + + /** + * @param IConfig $config + */ + public function __construct(IConfig $config) { + $this->config = $config; + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'Repair outdated OCS IDs'; + } + + /** + * @param string $appName + * @param string $oldId + * @param string $newId + * @return bool True if updated, false otherwise + */ + public function fixOcsId($appName, $oldId, $newId) { + $existingId = $this->config->getAppValue($appName, 'ocsid'); + + if($existingId === $oldId) { + $this->config->setAppValue($appName, 'ocsid', $newId); + return true; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function run() { + $appsToUpdate = [ + 'contacts' => [ + 'old' => '166044', + 'new' => '168708', + ], + 'calendar' => [ + 'old' => '166043', + 'new' => '168707', + ], + 'bookmarks' => [ + 'old' => '166042', + 'new' => '168710', + ], + 'search_lucene' => [ + 'old' => '166057', + 'new' => '168709', + ], + 'documents' => [ + 'old' => '166045', + 'new' => '168711', + ] + ]; + + foreach($appsToUpdate as $appName => $ids) { + if ($this->fixOcsId($appName, $ids['old'], $ids['new'])) { + $this->emit( + '\OC\Repair', + 'info', + [sprintf('Fixed invalid %s OCS id', $appName)] + ); + } + } + } +} diff --git a/lib/private/route/router.php b/lib/private/route/router.php index 4ca5e16ddf2..5cfddca966a 100644 --- a/lib/private/route/router.php +++ b/lib/private/route/router.php @@ -33,6 +33,7 @@ namespace OC\Route; use OCP\ILogger; use OCP\Route\IRouter; use OCP\AppFramework\App; +use OCP\Util; use Symfony\Component\Routing\Exception\RouteNotFoundException; use Symfony\Component\Routing\Matcher\UrlMatcher; use Symfony\Component\Routing\Generator\UrlGenerator; @@ -41,49 +42,26 @@ use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\Exception\ResourceNotFoundException; class Router implements IRouter { - /** - * @var \Symfony\Component\Routing\RouteCollection[] - */ - protected $collections = array(); - - /** - * @var \Symfony\Component\Routing\RouteCollection - */ + /** @var RouteCollection[] */ + protected $collections = []; + /** @var null|RouteCollection */ protected $collection = null; - - /** - * @var string - */ + /** @var null|string */ protected $collectionName = null; - - /** - * @var \Symfony\Component\Routing\RouteCollection - */ + /** @var null|RouteCollection */ protected $root = null; - - /** - * @var \Symfony\Component\Routing\Generator\UrlGenerator - */ + /** @var null|UrlGenerator */ protected $generator = null; - - /** - * @var string[] - */ + /** @var string[] */ protected $routingFiles; - - /** - * @var string - */ - protected $cacheKey; - + /** @var bool */ protected $loaded = false; - - protected $loadedApps = array(); - - /** - * @var ILogger - */ + /** @var array */ + protected $loadedApps = []; + /** @var ILogger */ protected $logger; + /** @var RequestContext */ + protected $context; /** * @param ILogger $logger @@ -92,7 +70,7 @@ class Router implements IRouter { $this->logger = $logger; $baseUrl = \OC::$WEBROOT; if(!(getenv('front_controller_active') === 'true')) { - $baseUrl = \OC_Helper::linkTo('', 'index.php'); + $baseUrl = \OC::$server->getURLGenerator()->linkTo('', 'index.php'); } if (!\OC::$CLI) { $method = $_SERVER['REQUEST_METHOD']; @@ -114,7 +92,7 @@ class Router implements IRouter { */ public function getRoutingFiles() { if (!isset($this->routingFiles)) { - $this->routingFiles = array(); + $this->routingFiles = []; foreach (\OC_APP::getEnabledApps() as $app) { $file = \OC_App::getAppPath($app) . '/appinfo/routes.php'; if (file_exists($file)) { @@ -126,23 +104,9 @@ class Router implements IRouter { } /** - * @return string - */ - public function getCacheKey() { - if (!isset($this->cacheKey)) { - $files = $this->getRoutingFiles(); - $files[] = 'settings/routes.php'; - $files[] = 'core/routes.php'; - $files[] = 'ocs/routes.php'; - $this->cacheKey = \OC\Cache::generateCacheKeyFromFiles($files); - } - return $this->cacheKey; - } - - /** - * loads the api routes + * Loads the routes * - * @return void + * @param null|string $app */ public function loadRoutes($app = null) { $requestedApp = $app; @@ -157,10 +121,10 @@ class Router implements IRouter { return; } $file = \OC_App::getAppPath($app) . '/appinfo/routes.php'; - if (file_exists($file)) { - $routingFiles = array($app => $file); + if ($file !== false && file_exists($file)) { + $routingFiles = [$app => $file]; } else { - $routingFiles = array(); + $routingFiles = []; } } \OC::$server->getEventLogger()->start('loadroutes' . $requestedApp, 'Loading Routes'); @@ -183,12 +147,12 @@ class Router implements IRouter { if (!isset($this->loadedApps['core'])) { $this->loadedApps['core'] = true; $this->useCollection('root'); - require_once 'settings/routes.php'; - require_once 'core/routes.php'; + require_once __DIR__ . '/../../../settings/routes.php'; + require_once __DIR__ . '/../../../core/routes.php'; } if ($this->loaded) { // include ocs routes, must be loaded last for /ocs prefix - require_once 'ocs/routes.php'; + require_once __DIR__ . '/../../../ocs/routes.php'; $collection = $this->getCollection('ocs'); $collection->addPrefix('/ocs'); $this->root->addCollection($collection); @@ -197,6 +161,14 @@ class Router implements IRouter { } /** + * @return string + * @deprecated + */ + public function getCacheKey() { + return ''; + } + + /** * @param string $name * @return \Symfony\Component\Routing\RouteCollection */ @@ -237,7 +209,10 @@ class Router implements IRouter { * @param array $requirements An array of requirements for parameters (regexes) * @return \OC\Route\Route */ - public function create($name, $pattern, array $defaults = array(), array $requirements = array()) { + public function create($name, + $pattern, + array $defaults = [], + array $requirements = []) { $route = new Route($pattern, $defaults, $requirements); $this->collection->add($name, $route); return $route; @@ -260,7 +235,7 @@ class Router implements IRouter { $this->loadRoutes($app); } else if (substr($url, 0, 6) === '/core/' or substr($url, 0, 10) === '/settings/') { \OC::$REQUESTEDAPP = $url; - if (!\OC::$server->getConfig()->getSystemValue('maintenance', false) && !\OCP\Util::needUpgrade()) { + if (!\OC::$server->getConfig()->getSystemValue('maintenance', false) && !Util::needUpgrade()) { \OC_App::loadApps(); } $this->loadRoutes('core'); @@ -325,10 +300,16 @@ class Router implements IRouter { * @param bool $absolute * @return string */ - public function generate($name, $parameters = array(), $absolute = false) { + public function generate($name, + $parameters = [], + $absolute = false) { $this->loadRoutes(); try { - return $this->getGenerator()->generate($name, $parameters, $absolute); + $referenceType = UrlGenerator::ABSOLUTE_URL; + if ($absolute === false) { + $referenceType = UrlGenerator::ABSOLUTE_PATH; + } + return $this->getGenerator()->generate($name, $parameters, $referenceType); } catch (RouteNotFoundException $e) { $this->logger->logException($e); return ''; @@ -372,6 +353,4 @@ class Router implements IRouter { $application->registerRoutes($this, $routes); } } - - } diff --git a/lib/private/security/crypto.php b/lib/private/security/crypto.php index 0bd34df3f36..46d0c750b2f 100644 --- a/lib/private/security/crypto.php +++ b/lib/private/security/crypto.php @@ -123,7 +123,7 @@ class Crypto implements ICrypto { $this->cipher->setIV($iv); - if(!\OCP\Security\StringUtils::equals($this->calculateHMAC($parts[0].$parts[1], $password), $hmac)) { + if(!hash_equals($this->calculateHMAC($parts[0].$parts[1], $password), $hmac)) { throw new \Exception('HMAC does not match.'); } diff --git a/lib/private/security/hasher.php b/lib/private/security/hasher.php index a5dd22e5dc8..318141b6852 100644 --- a/lib/private/security/hasher.php +++ b/lib/private/security/hasher.php @@ -109,7 +109,7 @@ class Hasher implements IHasher { // Verify whether it matches a legacy PHPass or SHA1 string $hashLength = strlen($hash); if($hashLength === 60 && password_verify($message.$this->legacySalt, $hash) || - $hashLength === 40 && StringUtils::equals($hash, sha1($message))) { + $hashLength === 40 && hash_equals($hash, sha1($message))) { $newHash = $this->hash($message); return true; } diff --git a/lib/private/security/securerandom.php b/lib/private/security/securerandom.php index 87dca68985e..24affbe8988 100644 --- a/lib/private/security/securerandom.php +++ b/lib/private/security/securerandom.php @@ -27,25 +27,15 @@ use Sabre\DAV\Exception; use OCP\Security\ISecureRandom; /** - * Class SecureRandom provides a layer around RandomLib to generate - * secure random strings. For PHP 7 the native CSPRNG is used. + * Class SecureRandom provides a wrapper around the random_int function to generate + * secure random strings. For PHP 7 the native CSPRNG is used, older versions do + * use a fallback. * * Usage: - * \OC::$server->getSecureRandom()->getMediumStrengthGenerator()->generate(10); - * + * \OC::$server->getSecureRandom()->generate(10); * @package OC\Security */ class SecureRandom implements ISecureRandom { - - /** @var \RandomLib\Factory */ - var $factory; - /** @var \RandomLib\Generator */ - var $generator; - - function __construct() { - $this->factory = new RandomLib\Factory; - } - /** * Convenience method to get a low strength random number generator. * @@ -53,10 +43,10 @@ class SecureRandom implements ISecureRandom { * in a non-cryptographical setting. They are not strong enough to be * used as keys or salts. They are however useful for one-time use tokens. * + * @deprecated 9.0.0 Use \OC\Security\SecureRandom::generate directly or random_bytes() / random_int() * @return $this */ public function getLowStrengthGenerator() { - $this->generator = $this->factory->getLowStrengthGenerator(); return $this; } @@ -67,10 +57,10 @@ class SecureRandom implements ISecureRandom { * They are strong enough to be used as keys and salts. However, they do * take some time and resources to generate, so they should not be over-used * + * @deprecated 9.0.0 Use \OC\Security\SecureRandom::generate directly or random_bytes() / random_int() * @return $this */ public function getMediumStrengthGenerator() { - $this->generator = $this->factory->getMediumStrengthGenerator(); return $this; } @@ -80,26 +70,17 @@ class SecureRandom implements ISecureRandom { * @param string $characters An optional list of characters to use if no character list is * specified all valid base64 characters are used. * @return string - * @throws \Exception If the generator is not initialized. */ public function generate($length, $characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/') { - if(is_null($this->generator)) { - throw new \Exception('Generator is not initialized.'); - } + $maxCharIndex = strlen($characters) - 1; + $randomString = ''; - if(function_exists('random_int')) { - $maxCharIndex = strlen($characters) - 1; - $randomString = ''; - - while($length > 0) { - $randomNumber = random_int(0, $maxCharIndex); - $randomString .= $characters[$randomNumber]; - $length--; - } - return $randomString; + while($length > 0) { + $randomNumber = random_int(0, $maxCharIndex); + $randomString .= $characters[$randomNumber]; + $length--; } - - return $this->generator->generateString($length, $characters); + return $randomString; } } diff --git a/lib/private/security/stringutils.php b/lib/private/security/stringutils.php deleted file mode 100644 index fa4342a2b45..00000000000 --- a/lib/private/security/stringutils.php +++ /dev/null @@ -1,60 +0,0 @@ -<?php -/** - * @author Lukas Reschke <lukas@owncloud.com> - * @author Morris Jobke <hey@morrisjobke.de> - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @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\Security; - -class StringUtils { - - /** - * Compares whether two strings are equal. To prevent guessing of the string - * length this is done by comparing two hashes against each other and afterwards - * a comparison of the real string to prevent against the unlikely chance of - * collisions. - * - * Be aware that this function may leak whether the string to compare have a different - * length. - * - * @param string $expected The expected value - * @param string $input The input to compare against - * @return bool True if the two strings are equal, otherwise false. - */ - public static function equals($expected, $input) { - - if(!is_string($expected) || !is_string($input)) { - return false; - } - - if(function_exists('hash_equals')) { - return hash_equals($expected, $input); - } - - $randomString = \OC::$server->getSecureRandom()->getLowStrengthGenerator()->generate(10); - - if(hash('sha512', $expected.$randomString) === hash('sha512', $input.$randomString)) { - if($expected === $input) { - return true; - } - } - - return false; - } -}
\ No newline at end of file diff --git a/lib/private/server.php b/lib/private/server.php index 8439500706d..a21ff58f355 100644 --- a/lib/private/server.php +++ b/lib/private/server.php @@ -78,14 +78,15 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; * * TODO: hookup all manager classes */ -class Server extends SimpleContainer implements IServerContainer { +class Server extends ServerContainer implements IServerContainer { /** @var string */ private $webRoot; /** * @param string $webRoot + * @param \OC\Config $config */ - public function __construct($webRoot) { + public function __construct($webRoot, \OC\Config $config) { parent::__construct(); $this->webRoot = $webRoot; @@ -238,11 +239,11 @@ class Server extends SimpleContainer implements IServerContainer { $c->getSystemConfig() ); }); - $this->registerService('SystemConfig', function ($c) { - return new \OC\SystemConfig(); + $this->registerService('SystemConfig', function ($c) use ($config) { + return new \OC\SystemConfig($config); }); - $this->registerService('AppConfig', function ($c) { - return new \OC\AppConfig(\OC_DB::getConnection()); + $this->registerService('AppConfig', function (Server $c) { + return new \OC\AppConfig($c->getDatabaseConnection()); }); $this->registerService('L10NFactory', function ($c) { return new \OC\L10N\Factory(); diff --git a/lib/private/servercontainer.php b/lib/private/servercontainer.php new file mode 100644 index 00000000000..385700957a1 --- /dev/null +++ b/lib/private/servercontainer.php @@ -0,0 +1,89 @@ +<?php +/** + * @author Joas Schilling <nickvergessen@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @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; + + +use OC\AppFramework\DependencyInjection\DIContainer; +use OC\AppFramework\Utility\SimpleContainer; +use OCP\AppFramework\QueryException; + +/** + * Class ServerContainer + * + * @package OC + */ +class ServerContainer extends SimpleContainer { + /** @var DIContainer[] */ + protected $appContainers; + + /** + * ServerContainer constructor. + */ + public function __construct() { + parent::__construct(); + $this->appContainers = []; + } + + /** + * @param string $appName + * @param DIContainer $container + */ + public function registerAppContainer($appName, DIContainer $container) { + $this->appContainers[$appName] = $container; + } + + /** + * @param string $appName + * @return DIContainer + */ + public function getAppContainer($appName) { + if (isset($this->appContainers[$appName])) { + return $this->appContainers[$appName]; + } + + return new DIContainer($appName); + } + + /** + * @param string $name name of the service to query for + * @return mixed registered service for the given $name + * @throws QueryException if the query could not be resolved + */ + public function query($name) { + $name = $this->sanitizeName($name); + + // In case the service starts with OCA\ we try to find the service in + // the apps container first. + if (strpos($name, 'OCA\\') === 0 && substr_count($name, '\\') >= 2) { + $segments = explode('\\', $name); + $appContainer = $this->getAppContainer(strtolower($segments[1])); + try { + return $appContainer->query($name); + } catch (QueryException $e) { + // Didn't find the service in the respective app container, + // ignore it and fall back to the core container. + } + } + + return parent::query($name); + } +} diff --git a/lib/private/session/cryptosessiondata.php b/lib/private/session/cryptosessiondata.php index dcae1648fe1..b600874412b 100644 --- a/lib/private/session/cryptosessiondata.php +++ b/lib/private/session/cryptosessiondata.php @@ -132,6 +132,16 @@ class CryptoSessionData implements \ArrayAccess, ISession { } /** + * Wrapper around session_regenerate_id + * + * @param bool $deleteOldSession Whether to delete the old associated session file or not. + * @return void + */ + public function regenerateId($deleteOldSession = true) { + $this->session->regenerateId($deleteOldSession); + } + + /** * Close the session and release the lock, also writes all changed data in batch */ public function close() { diff --git a/lib/private/session/internal.php b/lib/private/session/internal.php index 0b6152acf12..8be3356c6db 100644 --- a/lib/private/session/internal.php +++ b/lib/private/session/internal.php @@ -89,10 +89,9 @@ class Internal extends Session { } } - public function clear() { session_unset(); - @session_regenerate_id(true); + $this->regenerateId(); @session_start(); $_SESSION = array(); } @@ -102,14 +101,35 @@ class Internal extends Session { parent::close(); } - public function reopen() { - throw new \Exception('The session cannot be reopened - reopen() is ony to be used in unit testing.'); - } + /** + * Wrapper around session_regenerate_id + * + * @param bool $deleteOldSession Whether to delete the old associated session file or not. + * @return void + */ + public function regenerateId($deleteOldSession = true) { + @session_regenerate_id($deleteOldSession); + } + + /** + * @throws \Exception + */ + public function reopen() { + throw new \Exception('The session cannot be reopened - reopen() is ony to be used in unit testing.'); + } + /** + * @param int $errorNumber + * @param string $errorString + * @throws \ErrorException + */ public function trapError($errorNumber, $errorString) { throw new \ErrorException($errorString); } + /** + * @throws \Exception + */ private function validateSession() { if ($this->sessionClosed) { throw new \Exception('Session has been closed - no further changes to the session are allowed'); diff --git a/lib/private/session/memory.php b/lib/private/session/memory.php index ff95efc5345..c6090087457 100644 --- a/lib/private/session/memory.php +++ b/lib/private/session/memory.php @@ -81,6 +81,13 @@ class Memory extends Session { } /** + * Stub since the session ID does not need to get regenerated for the cache + * + * @param bool $deleteOldSession + */ + public function regenerateId($deleteOldSession = true) {} + + /** * Helper function for PHPUnit execution - don't use in non-test code */ public function reopen() { diff --git a/lib/private/setup.php b/lib/private/setup.php index 814d78679e2..770f5cdab52 100644 --- a/lib/private/setup.php +++ b/lib/private/setup.php @@ -322,7 +322,7 @@ class Setup { 'datadirectory' => $dataDir, 'overwrite.cli.url' => $request->getServerProtocol() . '://' . $request->getInsecureServerHost() . \OC::$WEBROOT, 'dbtype' => $dbType, - 'version' => implode('.', \OC_Util::getVersion()), + 'version' => implode('.', \OCP\Util::getVersion()), ]); try { @@ -369,11 +369,9 @@ class Setup { // out that this is indeed an ownCloud data directory file_put_contents($config->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data').'/.ocdata', ''); - // Update htaccess files for apache hosts - if (isset($_SERVER['SERVER_SOFTWARE']) && strstr($_SERVER['SERVER_SOFTWARE'], 'Apache')) { - self::updateHtaccess(); - self::protectDataDirectory(); - } + // Update .htaccess files + Setup::updateHtaccess(); + Setup::protectDataDirectory(); //try to write logtimezone if (date_default_timezone_get()) { @@ -395,32 +393,17 @@ class Setup { } /** - * Checks if the .htaccess contains the current version parameter - * - * @return bool - */ - private function isCurrentHtaccess() { - $version = \OC_Util::getVersion(); - unset($version[3]); - - return !strpos( - file_get_contents($this->pathToHtaccess()), - 'Version: '.implode('.', $version) - ) === false; - } - - /** * Append the correct ErrorDocument path for Apache hosts - * - * @throws \OC\HintException If .htaccess does not include the current version */ public static function updateHtaccess() { + // From CLI we don't know the defined web root. Thus we can't write any + // directives into the .htaccess file. + if(\OC::$CLI) { + return; + } $setupHelper = new \OC\Setup(\OC::$server->getConfig(), \OC::$server->getIniWrapper(), \OC::$server->getL10N('lib'), new \OC_Defaults(), \OC::$server->getLogger(), \OC::$server->getSecureRandom()); - if(!$setupHelper->isCurrentHtaccess()) { - throw new \OC\HintException('.htaccess file has the wrong version. Please upload the correct version. Maybe you forgot to replace it after updating?'); - } $htaccessContent = file_get_contents($setupHelper->pathToHtaccess()); $content = ''; @@ -439,6 +422,9 @@ class Setup { $content.="\n RewriteBase ".$webRoot; $content .= "\n <IfModule mod_env.c>"; $content .= "\n SetEnv front_controller_active true"; + $content .= "\n <IfModule mod_dir.c>"; + $content .= "\n DirectorySlash off"; + $content .= "\n </IfModule>"; $content.="\n </IfModule>"; $content.="\n</IfModule>"; diff --git a/lib/private/share/helper.php b/lib/private/share/helper.php index 26bbca81317..0441647df83 100644 --- a/lib/private/share/helper.php +++ b/lib/private/share/helper.php @@ -289,4 +289,38 @@ class Helper extends \OC\Share\Constants { $hint = $l->t('Invalid Federated Cloud ID'); throw new HintException('Invalid Fededrated Cloud ID', $hint); } + + /** + * check if two federated cloud IDs refer to the same user + * + * @param string $user1 + * @param string $server1 + * @param string $user2 + * @param string $server2 + * @return bool true if both users and servers are the same + */ + public static function isSameUserOnSameServer($user1, $server1, $user2, $server2) { + $normalizedServer1 = strtolower(\OC\Share\Share::removeProtocolFromUrl($server1)); + $normalizedServer2 = strtolower(\OC\Share\Share::removeProtocolFromUrl($server2)); + + if (rtrim($normalizedServer1, '/') === rtrim($normalizedServer2, '/')) { + // FIXME this should be a method in the user management instead + \OCP\Util::emitHook( + '\OCA\Files_Sharing\API\Server2Server', + 'preLoginNameUsedAsUserName', + array('uid' => &$user1) + ); + \OCP\Util::emitHook( + '\OCA\Files_Sharing\API\Server2Server', + 'preLoginNameUsedAsUserName', + array('uid' => &$user2) + ); + + if ($user1 === $user2) { + return true; + } + } + + return false; + } } diff --git a/lib/private/share/mailnotifications.php b/lib/private/share/mailnotifications.php index 4d282158ba4..f071c7f3a3c 100644 --- a/lib/private/share/mailnotifications.php +++ b/lib/private/share/mailnotifications.php @@ -170,7 +170,7 @@ class MailNotifications { * @param string $filename the shared file * @param string $link the public link * @param int $expiration expiration date (timestamp) - * @return array $result of failed recipients + * @return string[] $result of failed recipients */ public function sendLinkShareMail($recipient, $filename, $link, $expiration) { $subject = (string)$this->l->t('%s shared »%s« with you', [$this->senderDisplayName, $filename]); @@ -232,8 +232,8 @@ class MailNotifications { } /** - * @param $itemSource - * @param $itemType + * @param string $itemSource + * @param string $itemType * @param IUser $recipient * @return array */ diff --git a/lib/private/share/share.php b/lib/private/share/share.php index 3edffba8a3f..db27fa6a891 100644 --- a/lib/private/share/share.php +++ b/lib/private/share/share.php @@ -436,7 +436,7 @@ class Share extends Constants { // TODO: inject connection, hopefully one day in the future when this // class isn't static anymore... - $conn = \OC_DB::getConnection(); + $conn = \OC::$server->getDatabaseConnection(); $result = $conn->executeQuery( 'SELECT ' . $select . ' FROM `*PREFIX*share` ' . $where, $arguments, @@ -491,7 +491,7 @@ class Share extends Constants { public static function getShareByToken($token, $checkPasswordProtection = true) { $query = \OC_DB::prepare('SELECT * FROM `*PREFIX*share` WHERE `token` = ?', 1); $result = $query->execute(array($token)); - if (\OC_DB::isError($result)) { + if ($result === false) { \OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage() . ', token=' . $token, \OCP\Util::ERROR); } $row = $result->fetchRow(); @@ -849,11 +849,20 @@ class Share extends Constants { throw new \Exception($message_t); } + // don't allow federated shares if source and target server are the same + list($user, $remote) = Helper::splitUserRemote($shareWith); + $currentServer = self::removeProtocolFromUrl(\OC::$server->getURLGenerator()->getAbsoluteURL('/')); + $currentUser = \OC::$server->getUserSession()->getUser()->getUID(); + if (Helper::isSameUserOnSameServer($user, $remote, $currentUser, $currentServer)) { + $message = 'Not allowed to create a federated share with the same user.'; + $message_t = $l->t('Not allowed to create a federated share with the same user'); + \OCP\Util::writeLog('OCP\Share', $message, \OCP\Util::DEBUG); + throw new \Exception($message_t); + } $token = \OC::$server->getSecureRandom()->getMediumStrengthGenerator()->generate(self::TOKEN_LENGTH, \OCP\Security\ISecureRandom::CHAR_LOWER . \OCP\Security\ISecureRandom::CHAR_UPPER . \OCP\Security\ISecureRandom::CHAR_DIGITS); - list($user, $remote) = Helper::splitUserRemote($shareWith); $shareWith = $user . '@' . $remote; $shareId = self::put($itemType, $itemSource, $shareType, $shareWith, $uidOwner, $permissions, null, $token, $itemSourceName); @@ -1713,7 +1722,7 @@ class Share extends Constants { $root = strlen($root); $query = \OC_DB::prepare('SELECT '.$select.' FROM `*PREFIX*share` '.$where, $queryLimit); $result = $query->execute($queryArgs); - if (\OC_DB::isError($result)) { + if ($result === false) { \OCP\Util::writeLog('OCP\Share', \OC_DB::getErrorMessage() . ', select=' . $select . ' where=', \OCP\Util::ERROR); @@ -1777,7 +1786,7 @@ class Share extends Constants { if (isset($row['parent'])) { $query = \OC_DB::prepare('SELECT `file_target` FROM `*PREFIX*share` WHERE `id` = ?'); $parentResult = $query->execute(array($row['parent'])); - if (\OC_DB::isError($result)) { + if ($result === false) { \OCP\Util::writeLog('OCP\Share', 'Can\'t select parent: ' . \OC_DB::getErrorMessage() . ', select=' . $select . ' where=' . $where, \OCP\Util::ERROR); @@ -2182,7 +2191,7 @@ class Share extends Constants { if ($isGroupShare) { $id = self::insertShare($queriesToExecute['groupShare']); // Save this id, any extra rows for this group share will need to reference it - $parent = \OC_DB::insertid('*PREFIX*share'); + $parent = \OC::$server->getDatabaseConnection()->lastInsertId('*PREFIX*share'); unset($queriesToExecute['groupShare']); } @@ -2510,7 +2519,7 @@ class Share extends Constants { * @param string $url * @return string */ - private static function removeProtocolFromUrl($url) { + public static function removeProtocolFromUrl($url) { if (strpos($url, 'https://') === 0) { return substr($url, strlen('https://')); } else if (strpos($url, 'http://') === 0) { diff --git a/lib/private/share20/defaultshareprovider.php b/lib/private/share20/defaultshareprovider.php index bc3bc0ce9ed..a7155644920 100644 --- a/lib/private/share20/defaultshareprovider.php +++ b/lib/private/share20/defaultshareprovider.php @@ -64,11 +64,90 @@ class DefaultShareProvider implements IShareProvider { /** * Share a path - * + * * @param IShare $share * @return IShare The share object + * @throws ShareNotFound + * @throws \Exception */ public function create(IShare $share) { + $qb = $this->dbConn->getQueryBuilder(); + + $qb->insert('share'); + $qb->setValue('share_type', $qb->createNamedParameter($share->getShareType())); + + if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) { + //Set the UID of the user we share with + $qb->setValue('share_with', $qb->createNamedParameter($share->getSharedWith()->getUID())); + } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) { + //Set the GID of the group we share with + $qb->setValue('share_with', $qb->createNamedParameter($share->getSharedWith()->getGID())); + } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) { + //Set the token of the share + $qb->setValue('token', $qb->createNamedParameter($share->getToken())); + + //If a password is set store it + if ($share->getPassword() !== null) { + $qb->setValue('share_with', $qb->createNamedParameter($share->getPassword())); + } + + //If an expiration date is set store it + if ($share->getExpirationDate() !== null) { + $qb->setValue('expiration', $qb->createNamedParameter($share->getExpirationDate(), 'datetime')); + } + } else { + throw new \Exception('invalid share type!'); + } + + // Set what is shares + $qb->setValue('item_type', $qb->createParameter('itemType')); + if ($share->getPath() instanceof \OCP\Files\File) { + $qb->setParameter('itemType', 'file'); + } else { + $qb->setParameter('itemType', 'folder'); + } + + // Set the file id + $qb->setValue('item_source', $qb->createNamedParameter($share->getPath()->getId())); + $qb->setValue('file_source', $qb->createNamedParameter($share->getPath()->getId())); + + // set the permissions + $qb->setValue('permissions', $qb->createNamedParameter($share->getPermissions())); + + // Set who created this share + $qb->setValue('uid_initiator', $qb->createNamedParameter($share->getSharedBy()->getUID())); + + // Set who is the owner of this file/folder (and this the owner of the share) + $qb->setValue('uid_owner', $qb->createNamedParameter($share->getShareOwner()->getUID())); + + // Set the file target + $qb->setValue('file_target', $qb->createNamedParameter($share->getTarget())); + + // Set the time this share was created + $qb->setValue('stime', $qb->createNamedParameter(time())); + + // insert the data and fetch the id of the share + $this->dbConn->beginTransaction(); + $qb->execute(); + $id = $this->dbConn->lastInsertId('*PREFIX*share'); + $this->dbConn->commit(); + + // Now fetch the inserted share and create a complete share object + $qb = $this->dbConn->getQueryBuilder(); + $qb->select('*') + ->from('*PREFIX*share') + ->where($qb->expr()->eq('id', $qb->createNamedParameter($id))); + + $cursor = $qb->execute(); + $data = $cursor->fetch(); + $cursor->closeCursor(); + + if ($data === false) { + throw new ShareNotFound(); + } + + $share = $this->createShare($data); + return $share; } /** @@ -170,11 +249,29 @@ class DefaultShareProvider implements IShareProvider { /** * Get shares for a given path * - * @param \OCP\IUser $user * @param \OCP\Files\Node $path * @return IShare[] */ - public function getSharesByPath(IUser $user, Node $path) { + public function getSharesByPath(Node $path) { + $qb = $this->dbConn->getQueryBuilder(); + + $cursor = $qb->select('*') + ->from('share') + ->andWhere($qb->expr()->eq('file_source', $qb->createNamedParameter($path->getId()))) + ->andWhere( + $qb->expr()->orX( + $qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_USER)), + $qb->expr()->eq('share_type', $qb->createNamedParameter(\OCP\Share::SHARE_TYPE_GROUP)) + ) + )->execute(); + + $shares = []; + while($data = $cursor->fetch()) { + $shares[] = $this->createShare($data); + } + $cursor->closeCursor(); + + return $shares; } /** @@ -223,16 +320,21 @@ class DefaultShareProvider implements IShareProvider { $share->setSharedWith($data['share_with']); } - $share->setSharedBy($this->userManager->get($data['uid_owner'])); - - // TODO: getById can return an array. How to handle this properly?? - $folder = $this->rootFolder->getUserFolder($share->getSharedBy()->getUID()); - $path = $folder->getById((int)$data['file_source'])[0]; + if ($data['uid_initiator'] === null) { + //OLD SHARE + $share->setSharedBy($this->userManager->get($data['uid_owner'])); + $folder = $this->rootFolder->getUserFolder($share->getSharedBy()->getUID()); + $path = $folder->getById((int)$data['file_source'])[0]; - $owner = $path->getOwner(); - $share->setShareOwner($owner); + $owner = $path->getOwner(); + $share->setShareOwner($owner); + } else { + //New share! + $share->setSharedBy($this->userManager->get($data['uid_initiator'])); + $share->setShareOwner($this->userManager->get($data['uid_owner'])); + } - $path = $this->rootFolder->getUserFolder($owner->getUID())->getById((int)$data['file_source'])[0]; + $path = $this->rootFolder->getUserFolder($share->getShareOwner()->getUID())->getById((int)$data['file_source'])[0]; $share->setPath($path); if ($data['expiration'] !== null) { diff --git a/lib/private/share20/ishare.php b/lib/private/share20/ishare.php index 2e54da7a029..a149c578fb2 100644 --- a/lib/private/share20/ishare.php +++ b/lib/private/share20/ishare.php @@ -101,7 +101,7 @@ interface IShare { * @param \DateTime $expireDate * @return Share The modified object */ - public function setExpirationDate(\DateTime $expireDate); + public function setExpirationDate($expireDate); /** * Get the share expiration date @@ -111,6 +111,14 @@ interface IShare { public function getExpirationDate(); /** + * Set the sharer of the path + * + * @param IUser|string $sharedBy + * @return Share The modified object + */ + public function setSharedBy($sharedBy); + + /** * Get share sharer * * @return IUser|string @@ -118,6 +126,15 @@ interface IShare { public function getSharedBy(); /** + * Set the original share owner (who owns the path) + * + * @param IUser|string + * + * @return Share The modified object + */ + public function setShareOwner($shareOwner); + + /** * Get the original share owner (who owns the path) * * @return IUser|string @@ -141,6 +158,14 @@ interface IShare { public function getPassword(); /** + * Set the token + * + * @param string $token + * @return Share The modified object + */ + public function setToken($token); + + /** * Get the token * * @return string @@ -155,6 +180,14 @@ interface IShare { public function getParent(); /** + * Set the target of this share + * + * @param string $target + * @return Share The modified object + */ + public function setTarget($target); + + /** * Get the target of this share * * @return string diff --git a/lib/private/share20/ishareprovider.php b/lib/private/share20/ishareprovider.php index 56a550acf71..97a2b728d5f 100644 --- a/lib/private/share20/ishareprovider.php +++ b/lib/private/share20/ishareprovider.php @@ -81,11 +81,10 @@ interface IShareProvider { /** * Get shares for a given path * - * @param \OCP\IUser $user * @param \OCP\Files\Node $path * @return IShare[] */ - public function getSharesByPath(\OCP\IUser $user, \OCP\Files\Node $path); + public function getSharesByPath(\OCP\Files\Node $path); /** * Get shared with the given user diff --git a/lib/private/share20/manager.php b/lib/private/share20/manager.php index 882b281c490..8d753061c0c 100644 --- a/lib/private/share20/manager.php +++ b/lib/private/share20/manager.php @@ -21,53 +21,458 @@ namespace OC\Share20; -use OCP\IAppConfig; +use OCP\IConfig; +use OCP\IL10N; use OCP\ILogger; +use OCP\Security\ISecureRandom; +use OCP\Security\IHasher; +use OCP\Files\Mount\IMountManager; +use OCP\IGroupManager; +use OCP\Files\File; +use OCP\Files\Folder; +use OCP\IUser; use OC\Share20\Exception\ShareNotFound; +use OC\HintException; /** * This class is the communication hub for all sharing related operations. */ class Manager { - /** - * @var IShareProvider[] - */ + /** @var IShareProvider[] */ private $defaultProvider; /** @var ILogger */ private $logger; - /** @var IAppConfig */ - private $appConfig; + /** @var IConfig */ + private $config; + + /** @var ISecureRandom */ + private $secureRandom; + + /** @var IHasher */ + private $hasher; + + /** @var IMountManager */ + private $mountManager; + + /** @var IGroupManager */ + private $groupManager; + + /** @var IL10N */ + private $l; /** * Manager constructor. * * @param ILogger $logger - * @param IAppConfig $appConfig + * @param IConfig $config * @param IShareProvider $defaultProvider + * @param ISecureRandom $secureRandom + * @param IHasher $hasher + * @param IMountManager $mountManager + * @param IGroupManager $groupManager + * @param IL10N $l */ public function __construct( ILogger $logger, - IAppConfig $appConfig, - IShareProvider $defaultProvider + IConfig $config, + IShareProvider $defaultProvider, + ISecureRandom $secureRandom, + IHasher $hasher, + IMountManager $mountManager, + IGroupManager $groupManager, + IL10N $l ) { $this->logger = $logger; - $this->appConfig = $appConfig; + $this->config = $config; + $this->secureRandom = $secureRandom; + $this->hasher = $hasher; + $this->mountManager = $mountManager; + $this->groupManager = $groupManager; + $this->l = $l; // TEMP SOLUTION JUST TO GET STARTED $this->defaultProvider = $defaultProvider; } /** + * Verify if a password meets all requirements + * + * @param string $password + * @throws \Exception + */ + protected function verifyPassword($password) { + if ($password === null) { + // No password is set, check if this is allowed. + if ($this->shareApiLinkEnforcePassword()) { + throw new \InvalidArgumentException('Passwords are enforced for link shares'); + } + + return; + } + + // Let others verify the password + $accepted = true; + $message = ''; + \OCP\Util::emitHook('\OC\Share', 'verifyPassword', [ + 'password' => $password, + 'accepted' => &$accepted, + 'message' => &$message + ]); + + if (!$accepted) { + throw new \Exception($message); + } + } + + /** + * Check for generic requirements before creating a share + * + * @param IShare $share + * @throws \Exception + */ + protected function generalCreateChecks(IShare $share) { + if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) { + // We expect a valid user as sharedWith for user shares + if (!($share->getSharedWith() instanceof \OCP\IUser)) { + throw new \InvalidArgumentException('SharedWith should be an IUser'); + } + } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) { + // We expect a valid group as sharedWith for group shares + if (!($share->getSharedWith() instanceof \OCP\IGroup)) { + throw new \InvalidArgumentException('SharedWith should be an IGroup'); + } + } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) { + if ($share->getSharedWith() !== null) { + throw new \InvalidArgumentException('SharedWith should be empty'); + } + } else { + // We can't handle other types yet + throw new \InvalidArgumentException('unkown share type'); + } + + // Verify the initiator of the share is et + if ($share->getSharedBy() === null) { + throw new \InvalidArgumentException('SharedBy should be set'); + } + + // Cannot share with yourself + if ($share->getSharedWith() === $share->getSharedBy()) { + throw new \InvalidArgumentException('Can\'t share with yourself'); + } + + // The path should be set + if ($share->getPath() === null) { + throw new \InvalidArgumentException('Path should be set'); + } + // And it should be a file or a folder + if (!($share->getPath() instanceof \OCP\Files\File) && + !($share->getPath() instanceof \OCP\Files\Folder)) { + throw new \InvalidArgumentException('Path should be either a file or a folder'); + } + + // Check if we actually have share permissions + if (!$share->getPath()->isShareable()) { + $message_t = $this->l->t('You are not allowed to share %s', [$share->getPath()->getPath()]); + throw new HintException($message_t, $message_t, 404); + } + + // Permissions should be set + if ($share->getPermissions() === null) { + throw new \InvalidArgumentException('A share requires permissions'); + } + + // Check that we do not share with more permissions than we have + if ($share->getPermissions() & ~$share->getPath()->getPermissions()) { + $message_t = $this->l->t('Cannot increase permissions of %s', [$share->getPath()->getPath()]); + throw new HintException($message_t, $message_t, 404); + } + + // Check that read permissions are always set + if (($share->getPermissions() & \OCP\Constants::PERMISSION_READ) === 0) { + throw new \InvalidArgumentException('Shares need at least read permissions'); + } + } + + /** + * Validate if the expiration date fits the system settings + * + * @param \DateTime $expireDate The current expiration date (can be null) + * @return \DateTime|null The expiration date or null if $expireDate was null and it is not required + * @throws \OC\HintException + */ + protected function validateExpiredate($expireDate) { + + if ($expireDate !== null) { + //Make sure the expiration date is a date + $expireDate->setTime(0, 0, 0); + + $date = new \DateTime(); + $date->setTime(0, 0, 0); + if ($date >= $expireDate) { + $message = $this->l->t('Expiration date is in the past'); + throw new \OC\HintException($message, $message, 404); + } + } + + // If we enforce the expiration date check that is does not exceed + if ($this->shareApiLinkDefaultExpireDateEnforced()) { + if ($expireDate === null) { + throw new \InvalidArgumentException('Expiration date is enforced'); + } + + $date = new \DateTime(); + $date->setTime(0, 0, 0); + $date->add(new \DateInterval('P' . $this->shareApiLinkDefaultExpireDays() . 'D')); + if ($date < $expireDate) { + $message = $this->l->t('Cannot set expiration date more than %s days in the future', [$this->shareApiLinkDefaultExpireDays()]); + throw new \OC\HintException($message, $message, 404); + } + + return $expireDate; + } + + // If expiredate is empty set a default one if there is a default + if ($expireDate === null && $this->shareApiLinkDefaultExpireDate()) { + $date = new \DateTime(); + $date->setTime(0,0,0); + $date->add(new \DateInterval('P'.$this->shareApiLinkDefaultExpireDays().'D')); + return $date; + } + + return $expireDate; + } + + + /** + * Check for pre share requirements for use shares + * + * @param IShare $share + * @throws \Exception + */ + protected function userCreateChecks(IShare $share) { + // Check if we can share with group members only + if ($this->shareWithGroupMembersOnly()) { + // Verify we can share with this user + $groups = array_intersect( + $this->groupManager->getUserGroupIds($share->getSharedBy()), + $this->groupManager->getUserGroupIds($share->getSharedWith()) + ); + if (empty($groups)) { + throw new \Exception('Only sharing with group members is allowed'); + } + } + + /* + * TODO: Could be costly, fix + * + * Also this is not what we want in the future.. then we want to squash identical shares. + */ + $existingShares = $this->defaultProvider->getSharesByPath($share->getPath()); + foreach($existingShares as $existingShare) { + // Identical share already existst + if ($existingShare->getSharedWith() === $share->getSharedWith()) { + throw new \Exception('Path already shared with this user'); + } + + // The share is already shared with this user via a group share + if ($existingShare->getShareType() === \OCP\Share::SHARE_TYPE_GROUP && + $existingShare->getSharedWith()->inGroup($share->getSharedWith()) && + $existingShare->getShareOwner() !== $share->getShareOwner()) { + throw new \Exception('Path already shared with this user'); + } + } + } + + /** + * Check for pre share requirements for group shares + * + * @param IShare $share + * @throws \Exception + */ + protected function groupCreateChecks(IShare $share) { + // Verify if the user can share with this group + if ($this->shareWithGroupMembersOnly()) { + if (!$share->getSharedWith()->inGroup($share->getSharedBy())) { + throw new \Exception('Only sharing within your own groups is allowed'); + } + } + + /* + * TODO: Could be costly, fix + * + * Also this is not what we want in the future.. then we want to squash identical shares. + */ + $existingShares = $this->defaultProvider->getSharesByPath($share->getPath()); + foreach($existingShares as $existingShare) { + if ($existingShare->getSharedWith() === $share->getSharedWith()) { + throw new \Exception('Path already shared with this group'); + } + } + } + + /** + * Check for pre share requirements for link shares + * + * @param IShare $share + * @throws \Exception + */ + protected function linkCreateChecks(IShare $share) { + // Are link shares allowed? + if (!$this->shareApiAllowLinks()) { + throw new \Exception('Link sharing not allowed'); + } + + // Link shares by definition can't have share permissions + if ($share->getPermissions() & \OCP\Constants::PERMISSION_SHARE) { + throw new \InvalidArgumentException('Link shares can\'t have reshare permissions'); + } + + // We don't allow deletion on link shares + if ($share->getPermissions() & \OCP\Constants::PERMISSION_DELETE) { + throw new \InvalidArgumentException('Link shares can\'t have delete permissions'); + } + + // Check if public upload is allowed + if (!$this->shareApiLinkAllowPublicUpload() && + ($share->getPermissions() & (\OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE))) { + throw new \InvalidArgumentException('Public upload not allowed'); + } + } + + /** + * @param File|Folder $path + */ + protected function pathCreateChecks($path) { + // Make sure that we do not share a path that contains a shared mountpoint + if ($path instanceof \OCP\Files\Folder) { + $mounts = $this->mountManager->findIn($path->getPath()); + foreach($mounts as $mount) { + if ($mount->getStorage()->instanceOfStorage('\OCA\Files_Sharing\ISharedStorage')) { + throw new \InvalidArgumentException('Path contains files shared with you'); + } + } + } + } + + /** + * Check if the user that is sharing can actually share + * + * @param IShare $share + * @return bool + */ + protected function canShare(IShare $share) { + if (!$this->shareApiEnabled()) { + return false; + } + + if ($this->isSharingDisabledForUser($share->getSharedBy())) { + return false; + } + + return true; + } + + /** * Share a path * - * @param Share $share + * @param IShare $share * @return Share The share object + * @throws \Exception + * + * TODO: handle link share permissions or check them */ - public function createShare(Share $share) { + public function createShare(IShare $share) { + if (!$this->canShare($share)) { + throw new \Exception('The Share API is disabled'); + } + + $this->generalCreateChecks($share); + + //Verify share type + if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) { + $this->userCreateChecks($share); + } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_GROUP) { + $this->groupCreateChecks($share); + } else if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) { + $this->linkCreateChecks($share); + + /* + * For now ignore a set token. + */ + $share->setToken( + $this->secureRandom->generate( + \OC\Share\Constants::TOKEN_LENGTH, + \OCP\Security\ISecureRandom::CHAR_LOWER. + \OCP\Security\ISecureRandom::CHAR_UPPER. + \OCP\Security\ISecureRandom::CHAR_DIGITS + ) + ); + + //Verify the expiration date + $share->setExpirationDate($this->validateExpiredate($share->getExpirationDate())); + + //Verify the password + $this->verifyPassword($share->getPassword()); + + // If a password is set. Hash it! + if ($share->getPassword() !== null) { + $share->setPassword($this->hasher->hash($share->getPassword())); + } + } + + // Verify if there are any issues with the path + $this->pathCreateChecks($share->getPath()); + + // On creation of a share the owner is always the owner of the path + $share->setShareOwner($share->getPath()->getOwner()); + + // Generate the target + $target = $this->config->getSystemValue('share_folder', '/') .'/'. $share->getPath()->getName(); + $target = \OC\Files\Filesystem::normalizePath($target); + $share->setTarget($target); + + // Pre share hook + $run = true; + $error = ''; + $preHookData = [ + 'itemType' => $share->getPath() instanceof \OCP\Files\File ? 'file' : 'folder', + 'itemSource' => $share->getPath()->getId(), + 'shareType' => $share->getShareType(), + 'uidOwner' => $share->getSharedBy()->getUID(), + 'permissions' => $share->getPermissions(), + 'fileSource' => $share->getPath()->getId(), + 'expiration' => $share->getExpirationDate(), + 'token' => $share->getToken(), + 'run' => &$run, + 'error' => &$error + ]; + \OC_Hook::emit('OCP\Share', 'pre_shared', $preHookData); + + if ($run === false) { + throw new \Exception($error); + } + + $share = $this->defaultProvider->create($share); + + // Post share hook + $postHookData = [ + 'itemType' => $share->getPath() instanceof \OCP\Files\File ? 'file' : 'folder', + 'itemSource' => $share->getPath()->getId(), + 'shareType' => $share->getShareType(), + 'uidOwner' => $share->getSharedBy()->getUID(), + 'permissions' => $share->getPermissions(), + 'fileSource' => $share->getPath()->getId(), + 'expiration' => $share->getExpirationDate(), + 'token' => $share->getToken(), + 'id' => $share->getId(), + ]; + \OC_Hook::emit('OCP\Share', 'post_shared', $postHookData); + + return $share; } /** @@ -251,4 +656,115 @@ class Manager { */ public function getAccessList(\OCP\Files\Node $path) { } + + /** + * Create a new share + * @return IShare; + */ + public function newShare() { + return new \OC\Share20\Share(); + } + + /** + * Is the share API enabled + * + * @return bool + */ + public function shareApiEnabled() { + return $this->config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes'; + } + + /** + * Is public link sharing enabled + * + * @return bool + */ + public function shareApiAllowLinks() { + return $this->config->getAppValue('core', 'shareapi_allow_links', 'yes') === 'yes'; + } + + /** + * Is password on public link requires + * + * @return bool + */ + public function shareApiLinkEnforcePassword() { + return $this->config->getAppValue('core', 'shareapi_enforce_links_password', 'no') === 'yes'; + } + + /** + * Is default expire date enabled + * + * @return bool + */ + public function shareApiLinkDefaultExpireDate() { + return $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes'; + } + + /** + * Is default expire date enforced + *` + * @return bool + */ + public function shareApiLinkDefaultExpireDateEnforced() { + return $this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes'; + } + + /** + * Number of default expire days + *shareApiLinkAllowPublicUpload + * @return int + */ + public function shareApiLinkDefaultExpireDays() { + return (int)$this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7'); + } + + /** + * Allow public upload on link shares + * + * @return bool + */ + public function shareApiLinkAllowPublicUpload() { + return $this->config->getAppValue('core', 'shareapi_allow_public_upload', 'yes') === 'yes'; + } + + /** + * check if user can only share with group members + * @return bool + */ + public function shareWithGroupMembersOnly() { + return $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes'; + } + + + /** + * Copied from \OC_Util::isSharingDisabledForUser + * + * TODO: Deprecate fuction from OC_Util + * + * @param IUser $user + * @return bool + */ + public function isSharingDisabledForUser($user) { + if ($this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes') { + $groupsList = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', ''); + $excludedGroups = json_decode($groupsList); + if (is_null($excludedGroups)) { + $excludedGroups = explode(',', $groupsList); + $newValue = json_encode($excludedGroups); + $this->config->setAppValue('core', 'shareapi_exclude_groups_list', $newValue); + } + $usersGroups = $this->groupManager->getUserGroupIds($user); + if (!empty($usersGroups)) { + $remainingGroups = array_diff($usersGroups, $excludedGroups); + // if the user is only in groups which are disabled for sharing then + // sharing is also disabled for the user + if (empty($remainingGroups)) { + return true; + } + } + } + return false; + } + } diff --git a/lib/private/share20/share.php b/lib/private/share20/share.php index b7ce38ac61d..4827000eefa 100644 --- a/lib/private/share20/share.php +++ b/lib/private/share20/share.php @@ -163,7 +163,7 @@ class Share implements IShare { * @param \DateTime $expireDate * @return Share The modified object */ - public function setExpirationDate(\DateTime $expireDate) { + public function setExpirationDate($expireDate) { //TODO checks $this->expireDate = $expireDate; diff --git a/lib/private/systemconfig.php b/lib/private/systemconfig.php index 94b815aebd7..fb8c18123d7 100644 --- a/lib/private/systemconfig.php +++ b/lib/private/systemconfig.php @@ -44,12 +44,19 @@ class SystemConfig { 'objectstore' => ['arguments' => ['password' => true]], ]; + /** @var Config */ + private $config; + + public function __construct(Config $config) { + $this->config = $config; + } + /** * Lists all available config keys * @return array an array of key names */ public function getKeys() { - return \OC_Config::getKeys(); + return $this->config->getKeys(); } /** @@ -59,7 +66,7 @@ class SystemConfig { * @param mixed $value the value that should be stored */ public function setValue($key, $value) { - \OC_Config::setValue($key, $value); + $this->config->setValue($key, $value); } /** @@ -69,7 +76,7 @@ class SystemConfig { * If value is null, the config key will be deleted */ public function setValues(array $configs) { - \OC_Config::setValues($configs); + $this->config->setValues($configs); } /** @@ -80,7 +87,7 @@ class SystemConfig { * @return mixed the value or $default */ public function getValue($key, $default = '') { - return \OC_Config::getValue($key, $default); + return $this->config->getValue($key, $default); } /** @@ -106,7 +113,7 @@ class SystemConfig { * @param string $key the key of the value, under which it was saved */ public function deleteValue($key) { - \OC_Config::deleteKey($key); + $this->config->deleteKey($key); } /** diff --git a/lib/private/tags.php b/lib/private/tags.php index 09cb7618c02..c621aa3cf8f 100644 --- a/lib/private/tags.php +++ b/lib/private/tags.php @@ -215,7 +215,7 @@ class Tags implements \OCP\ITags { $entries = array(); try { - $conn = \OC_DB::getConnection(); + $conn = \OC::$server->getDatabaseConnection(); $chunks = array_chunk($objIds, 900, false); foreach ($chunks as $chunk) { $result = $conn->executeQuery( diff --git a/lib/private/template.php b/lib/private/template.php index d794dacac23..c2528c26851 100644 --- a/lib/private/template.php +++ b/lib/private/template.php @@ -119,7 +119,6 @@ class OC_Template extends \OC\Template\Base { // avatars if (\OC::$server->getSystemConfig()->getValue('enable_avatars', true) === true) { - \OC_Util::addScript('avatar', null, true); \OC_Util::addScript('jquery.avatar', null, true); \OC_Util::addScript('placeholder', null, true); } @@ -162,6 +161,8 @@ class OC_Template extends \OC\Template\Base { } if (\OC::$server->getRequest()->isUserAgent([\OC\AppFramework\Http\Request::USER_AGENT_IE])) { + // polyfill for btoa/atob for IE friends + OC_Util::addVendorScript('base64/base64'); // shim for the davclient.js library \OCP\Util::addScript('files/iedavclient'); } diff --git a/lib/private/template/functions.php b/lib/private/template/functions.php index d156d26f9ce..982ecde5850 100644 --- a/lib/private/template/functions.php +++ b/lib/private/template/functions.php @@ -146,7 +146,7 @@ function component($app, $file) { * For further information have a look at OC_Helper::linkTo */ function link_to( $app, $file, $args = array() ) { - return OC_Helper::linkTo( $app, $file, $args ); + return \OC::$server->getURLGenerator()->linkTo($app, $file, $args); } /** @@ -173,11 +173,9 @@ function image_path( $app, $image ) { * make OC_Helper::mimetypeIcon available as a simple function * @param string $mimetype mimetype * @return string link to the image - * - * For further information have a look at OC_Helper::mimetypeIcon */ function mimetype_icon( $mimetype ) { - return OC_Helper::mimetypeIcon( $mimetype ); + return \OC::$server->getMimeTypeDetector()->mimeTypeIcon( $mimetype ); } /** diff --git a/lib/private/templatelayout.php b/lib/private/templatelayout.php index 1a6a07ddc9f..bf25b2d31a9 100644 --- a/lib/private/templatelayout.php +++ b/lib/private/templatelayout.php @@ -136,7 +136,13 @@ class OC_TemplateLayout extends OC_Template { $this->assign('user_uid', OC_User::getUser()); $this->assign('appsmanagement_active', $appsMgmtActive); $this->assign('enableAvatars', $this->config->getSystemValue('enable_avatars', true)); - $this->assign('userAvatarSet', \OC_Helper::userAvatarSet(OC_User::getUser())); + + if (OC_User::getUser() === false) { + $this->assign('userAvatarSet', false); + } else { + $this->assign('userAvatarSet', \OC::$server->getAvatarManager()->getAvatar(OC_User::getUser())->exists()); + } + } else if ($renderAs == 'error') { parent::__construct('core', 'layout.guest', '', false); $this->assign('bodyid', 'body-login'); @@ -153,20 +159,20 @@ class OC_TemplateLayout extends OC_Template { if(empty(self::$versionHash)) { $v = OC_App::getAppVersions(); - $v['core'] = implode('.', \OC_Util::getVersion()); + $v['core'] = implode('.', \OCP\Util::getVersion()); self::$versionHash = md5(implode(',', $v)); } $useAssetPipeline = self::isAssetPipelineEnabled(); if ($useAssetPipeline) { - $this->append( 'jsfiles', OC_Helper::linkToRoute('js_config', array('v' => self::$versionHash))); + $this->append( 'jsfiles', \OC::$server->getURLGenerator()->linkToRoute('js_config', ['v' => self::$versionHash])); $this->generateAssets(); } else { // Add the js files $jsFiles = self::findJavascriptFiles(OC_Util::$scripts); $this->assign('jsfiles', array()); if ($this->config->getSystemValue('installed', false) && $renderAs != 'error') { - $this->append( 'jsfiles', OC_Helper::linkToRoute('js_config', array('v' => self::$versionHash))); + $this->append( 'jsfiles', \OC::$server->getURLGenerator()->linkToRoute('js_config', ['v' => self::$versionHash])); } foreach($jsFiles as $info) { $web = $info[1]; @@ -275,8 +281,8 @@ class OC_TemplateLayout extends OC_Template { $writer->writeAsset($cssCollection); } - $this->append('jsfiles', OC_Helper::linkTo('assets', "$jsHash.js")); - $this->append('cssfiles', OC_Helper::linkTo('assets', "$cssHash.css")); + $this->append('jsfiles', \OC::$server->getURLGenerator()->linkTo('assets', "$jsHash.js")); + $this->append('cssfiles', \OC::$server->getURLGenerator()->linkTo('assets', "$cssHash.css")); } /** diff --git a/lib/private/updater.php b/lib/private/updater.php index 366ad2555a8..9ec72bab2f9 100644 --- a/lib/private/updater.php +++ b/lib/private/updater.php @@ -155,7 +155,7 @@ class Updater extends BasicEmitter { $this->config->setAppValue('core', 'installedat', microtime(true)); } - $version = \OC_Util::getVersion(); + $version = \OCP\Util::getVersion(); $version['installed'] = $this->config->getAppValue('core', 'installedat'); $version['updated'] = $this->config->getAppValue('core', 'lastupdatedat'); $version['updatechannel'] = \OC_Util::getChannel(); @@ -177,6 +177,8 @@ class Updater extends BasicEmitter { $tmp['versionstring'] = (string)$data->versionstring; $tmp['url'] = (string)$data->url; $tmp['web'] = (string)$data->web; + } else { + libxml_clear_errors(); } } else { $data = []; @@ -206,7 +208,7 @@ class Updater extends BasicEmitter { } $installedVersion = $this->config->getSystemValue('version', '0.0.0'); - $currentVersion = implode('.', \OC_Util::getVersion()); + $currentVersion = implode('.', \OCP\Util::getVersion()); $this->log->debug('starting upgrade from ' . $installedVersion . ' to ' . $currentVersion, array('app' => 'core')); $success = true; @@ -254,7 +256,7 @@ class Updater extends BasicEmitter { */ public function isUpgradePossible($oldVersion, $newVersion, $allowedPreviousVersion) { return (version_compare($allowedPreviousVersion, $oldVersion, '<=') - && version_compare($oldVersion, $newVersion, '<=')); + && (version_compare($oldVersion, $newVersion, '<=') || $this->config->getSystemValue('debug', false))); } /** @@ -351,7 +353,7 @@ class Updater extends BasicEmitter { } // only set the final version if everything went well - $this->config->setSystemValue('version', implode('.', \OC_Util::getVersion())); + $this->config->setSystemValue('version', implode('.', \OCP\Util::getVersion())); } } @@ -470,7 +472,7 @@ class Updater extends BasicEmitter { private function checkAppsRequirements() { $isCoreUpgrade = $this->isCodeUpgrade(); $apps = OC_App::getEnabledApps(); - $version = OC_Util::getVersion(); + $version = \OCP\Util::getVersion(); $disabledApps = []; foreach ($apps as $app) { // check if the app is compatible with this version of ownCloud @@ -507,7 +509,7 @@ class Updater extends BasicEmitter { */ private function isCodeUpgrade() { $installedVersion = $this->config->getSystemValue('version', '0.0.0'); - $currentVersion = implode('.', OC_Util::getVersion()); + $currentVersion = implode('.', \OCP\Util::getVersion()); if (version_compare($currentVersion, $installedVersion, '>')) { return true; } diff --git a/lib/private/user.php b/lib/private/user.php index 74441d9175a..fa1cea9072f 100644 --- a/lib/private/user.php +++ b/lib/private/user.php @@ -66,14 +66,6 @@ class OC_User { return OC::$server->getUserSession(); } - /** - * @return \OC\User\Manager - * @deprecated Use \OC::$server->getUserManager() - */ - public static function getManager() { - return OC::$server->getUserManager(); - } - private static $_backends = array(); private static $_usedBackends = array(); @@ -84,44 +76,6 @@ class OC_User { private static $incognitoMode = false; /** - * registers backend - * - * @param string $backend name of the backend - * @deprecated Add classes by calling OC_User::useBackend() with a class instance instead - * @return bool - * - * Makes a list of backends that can be used by other modules - */ - public static function registerBackend($backend) { - self::$_backends[] = $backend; - return true; - } - - /** - * gets available backends - * - * @deprecated - * @return array an array of backends - * - * Returns the names of all backends. - */ - public static function getBackends() { - return self::$_backends; - } - - /** - * gets used backends - * - * @deprecated - * @return array an array of backends - * - * Returns the names of all used backends. - */ - public static function getUsedBackends() { - return array_keys(self::$_usedBackends); - } - - /** * Adds the backend to the list of used backends * * @param string|OC_User_Interface $backend default: database The backend to use for user management @@ -132,7 +86,7 @@ class OC_User { public static function useBackend($backend = 'database') { if ($backend instanceof OC_User_Interface) { self::$_usedBackends[get_class($backend)] = $backend; - self::getManager()->registerBackend($backend); + \OC::$server->getUserManager()->registerBackend($backend); } else { // You'll never know what happens if (null === $backend OR !is_string($backend)) { @@ -146,17 +100,17 @@ class OC_User { case 'sqlite': \OCP\Util::writeLog('core', 'Adding user backend ' . $backend . '.', \OCP\Util::DEBUG); self::$_usedBackends[$backend] = new OC_User_Database(); - self::getManager()->registerBackend(self::$_usedBackends[$backend]); + \OC::$server->getUserManager()->registerBackend(self::$_usedBackends[$backend]); break; case 'dummy': self::$_usedBackends[$backend] = new \Test\Util\User\Dummy(); - self::getManager()->registerBackend(self::$_usedBackends[$backend]); + \OC::$server->getUserManager()->registerBackend(self::$_usedBackends[$backend]); break; default: \OCP\Util::writeLog('core', 'Adding default user backend ' . $backend . '.', \OCP\Util::DEBUG); $className = 'OC_USER_' . strToUpper($backend); self::$_usedBackends[$backend] = new $className(); - self::getManager()->registerBackend(self::$_usedBackends[$backend]); + \OC::$server->getUserManager()->registerBackend(self::$_usedBackends[$backend]); break; } } @@ -168,7 +122,7 @@ class OC_User { */ public static function clearBackends() { self::$_usedBackends = array(); - self::getManager()->clearBackends(); + \OC::$server->getUserManager()->clearBackends(); } /** @@ -176,7 +130,7 @@ class OC_User { */ public static function setupBackends() { OC_App::loadApps(array('prelogin')); - $backends = OC_Config::getValue('user_backends', array()); + $backends = \OC::$server->getSystemConfig()->getValue('user_backends', array()); foreach ($backends as $i => $config) { $class = $config['class']; $arguments = $config['arguments']; @@ -199,42 +153,6 @@ class OC_User { } /** - * Create a new user - * - * @param string $uid The username of the user to create - * @param string $password The password of the new user - * @throws Exception - * @return bool true/false - * - * Creates a new user. Basic checking of username is done in OC_User - * itself, not in its subclasses. - * - * Allowed characters in the username are: "a-z", "A-Z", "0-9" and "_.@-" - * @deprecated Use \OC::$server->getUserManager()->createUser($uid, $password) - */ - public static function createUser($uid, $password) { - return self::getManager()->createUser($uid, $password); - } - - /** - * delete a user - * - * @param string $uid The username of the user to delete - * @return bool - * - * Deletes a user - * @deprecated Use \OC::$server->getUserManager()->get() and then run delete() on the return - */ - public static function deleteUser($uid) { - $user = self::getManager()->get($uid); - if ($user) { - return $user->delete(); - } else { - return false; - } - } - - /** * Try to login a user * * @param string $loginname The login name of the user to log in @@ -244,7 +162,6 @@ class OC_User { * Log in a user and regenerate a new session - if the password is ok */ public static function login($loginname, $password) { - session_regenerate_id(true); $result = self::getUserSession()->login($loginname, $password); if ($result) { //we need to pass the user name, which may differ from login name @@ -343,7 +260,7 @@ class OC_User { if (is_null($displayName)) { $displayName = $uid; } - $user = self::getManager()->get($uid); + $user = \OC::$server->getUserManager()->get($uid); if ($user) { return $user->setDisplayName($displayName); } else { @@ -452,7 +369,7 @@ class OC_User { */ public static function getDisplayName($uid = null) { if ($uid) { - $user = self::getManager()->get($uid); + $user = \OC::$server->getUserManager()->get($uid); if ($user) { return $user->getDisplayName(); } else { @@ -490,7 +407,7 @@ class OC_User { * Change the password of a user */ public static function setPassword($uid, $password, $recoveryPassword = null) { - $user = self::getManager()->get($uid); + $user = \OC::$server->getUserManager()->get($uid); if ($user) { return $user->setPassword($password, $recoveryPassword); } else { @@ -507,7 +424,7 @@ class OC_User { * Check whether a specified user can change his avatar */ public static function canUserChangeAvatar($uid) { - $user = self::getManager()->get($uid); + $user = \OC::$server->getUserManager()->get($uid); if ($user) { return $user->canChangeAvatar(); } else { @@ -524,7 +441,7 @@ class OC_User { * Check whether a specified user can change his password */ public static function canUserChangePassword($uid) { - $user = self::getManager()->get($uid); + $user = \OC::$server->getUserManager()->get($uid); if ($user) { return $user->canChangePassword(); } else { @@ -541,7 +458,7 @@ class OC_User { * Check whether a specified user can change his display name */ public static function canUserChangeDisplayName($uid) { - $user = self::getManager()->get($uid); + $user = \OC::$server->getUserManager()->get($uid); if ($user) { return $user->canChangeDisplayName(); } else { @@ -560,7 +477,7 @@ class OC_User { * returns the user id or false */ public static function checkPassword($uid, $password) { - $manager = self::getManager(); + $manager = \OC::$server->getUserManager(); $username = $manager->checkPassword($uid, $password); if ($username !== false) { return $username->getUID(); @@ -576,11 +493,11 @@ class OC_User { * @deprecated Use \OC::$server->getUserManager->getHome() */ public static function getHome($uid) { - $user = self::getManager()->get($uid); + $user = \OC::$server->getUserManager()->get($uid); if ($user) { return $user->getHome(); } else { - return OC_Config::getValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $uid; + return \OC::$server->getSystemConfig()->getValue('datadirectory', OC::$SERVERROOT . '/data') . '/' . $uid; } } @@ -595,7 +512,7 @@ class OC_User { * @param integer $offset */ public static function getUsers($search = '', $limit = null, $offset = null) { - $users = self::getManager()->search($search, $limit, $offset); + $users = \OC::$server->getUserManager()->search($search, $limit, $offset); $uids = array(); foreach ($users as $user) { $uids[] = $user->getUID(); @@ -616,7 +533,7 @@ class OC_User { */ public static function getDisplayNames($search = '', $limit = null, $offset = null) { $displayNames = array(); - $users = self::getManager()->searchDisplayName($search, $limit, $offset); + $users = \OC::$server->getUserManager()->searchDisplayName($search, $limit, $offset); foreach ($users as $user) { $displayNames[$user->getUID()] = $user->getDisplayName(); } @@ -630,7 +547,7 @@ class OC_User { * @return boolean */ public static function userExists($uid) { - return self::getManager()->userExists($uid); + return \OC::$server->getUserManager()->userExists($uid); } /** @@ -639,7 +556,7 @@ class OC_User { * @param string $uid the user to disable */ public static function disableUser($uid) { - $user = self::getManager()->get($uid); + $user = \OC::$server->getUserManager()->get($uid); if ($user) { $user->setEnabled(false); } @@ -651,7 +568,7 @@ class OC_User { * @param string $uid */ public static function enableUser($uid) { - $user = self::getManager()->get($uid); + $user = \OC::$server->getUserManager()->get($uid); if ($user) { $user->setEnabled(true); } @@ -664,7 +581,7 @@ class OC_User { * @return bool */ public static function isEnabled($uid) { - $user = self::getManager()->get($uid); + $user = \OC::$server->getUserManager()->get($uid); if ($user) { return $user->isEnabled(); } else { diff --git a/lib/private/user/database.php b/lib/private/user/database.php index 98850771212..5bee509e8bd 100644 --- a/lib/private/user/database.php +++ b/lib/private/user/database.php @@ -210,7 +210,7 @@ class OC_User_Database extends OC_User_Backend implements \OCP\IUserBackend { $query = OC_DB::prepare('SELECT `uid`, `displayname` FROM `*PREFIX*users` WHERE LOWER(`uid`) = LOWER(?)'); $result = $query->execute(array($uid)); - if (OC_DB::isError($result)) { + if ($result === false) { \OCP\Util::writeLog('core', OC_DB::getErrorMessage(), \OCP\Util::ERROR); return false; } @@ -287,7 +287,7 @@ class OC_User_Database extends OC_User_Backend implements \OCP\IUserBackend { public function countUsers() { $query = OC_DB::prepare('SELECT COUNT(*) FROM `*PREFIX*users`'); $result = $query->execute(); - if (OC_DB::isError($result)) { + if ($result === false) { \OCP\Util::writeLog('core', OC_DB::getErrorMessage(), \OCP\Util::ERROR); return false; } diff --git a/lib/private/user/session.php b/lib/private/user/session.php index ba702c9f365..be38b1b1d8e 100644 --- a/lib/private/user/session.php +++ b/lib/private/user/session.php @@ -213,6 +213,7 @@ class Session implements IUserSession, Emitter { * @throws LoginException */ public function login($uid, $password) { + $this->session->regenerateId(); $this->manager->emit('\OC\User', 'preLogin', array($uid, $password)); $user = $this->manager->checkPassword($uid, $password); if ($user !== false) { @@ -243,6 +244,7 @@ class Session implements IUserSession, Emitter { * @return bool */ public function loginWithCookie($uid, $currentToken) { + $this->session->regenerateId(); $this->manager->emit('\OC\User', 'preRememberedLogin', array($uid)); $user = $this->manager->get($uid); if (is_null($user)) { diff --git a/lib/private/util.php b/lib/private/util.php index 9016dc59751..6a9980fc129 100644 --- a/lib/private/util.php +++ b/lib/private/util.php @@ -72,7 +72,7 @@ class OC_Util { private static function initLocalStorageRootFS() { // mount local file backend as root - $configDataDirectory = OC_Config::getValue("datadirectory", OC::$SERVERROOT . "/data"); + $configDataDirectory = \OC::$server->getSystemConfig()->getValue("datadirectory", OC::$SERVERROOT . "/data"); //first set up the local "root" storage \OC\Files\Filesystem::initMountManager(); if (!self::$rootMounted) { @@ -184,7 +184,7 @@ class OC_Util { OC_Hook::emit('OC_Filesystem', 'preSetup', array('user' => $user)); //check if we are using an object storage - $objectStore = OC_Config::getValue('objectstore'); + $objectStore = \OC::$server->getSystemConfig()->getValue('objectstore', null); if (isset($objectStore)) { self::initObjectStoreRootFS($objectStore); } else { @@ -848,7 +848,7 @@ class OC_Util { public static function checkDatabaseVersion() { $l = \OC::$server->getL10N('lib'); $errors = array(); - $dbType = \OC_Config::getValue('dbtype', 'sqlite'); + $dbType = \OC::$server->getSystemConfig()->getValue('dbtype', 'sqlite'); if ($dbType === 'pgsql') { // check PostgreSQL version try { @@ -951,9 +951,11 @@ class OC_Util { $parameters['canResetPassword'] = true; if (!\OC::$server->getSystemConfig()->getValue('lost_password_link')) { - $user = \OC::$server->getUserManager()->get($_REQUEST['user']); - if ($user instanceof IUser) { - $parameters['canResetPassword'] = $user->canChangePassword(); + if (isset($_REQUEST['user'])) { + $user = \OC::$server->getUserManager()->get($_REQUEST['user']); + if ($user instanceof IUser) { + $parameters['canResetPassword'] = $user->canChangePassword(); + } } } @@ -972,7 +974,7 @@ class OC_Util { */ public static function checkAppEnabled($app) { if (!OC_App::isEnabled($app)) { - header('Location: ' . OC_Helper::linkToAbsolute('', 'index.php')); + header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php')); exit(); } } @@ -986,7 +988,7 @@ class OC_Util { public static function checkLoggedIn() { // Check if we are a user if (!OC_User::isLoggedIn()) { - header('Location: ' . OC_Helper::linkToAbsolute('', 'index.php', + header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php', [ 'redirect_url' => \OC::$server->getRequest()->getRequestUri() ] @@ -1004,7 +1006,7 @@ class OC_Util { public static function checkAdminUser() { OC_Util::checkLoggedIn(); if (!OC_User::isAdminUser(OC_User::getUser())) { - header('Location: ' . OC_Helper::linkToAbsolute('', 'index.php')); + header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php')); exit(); } } @@ -1044,7 +1046,7 @@ class OC_Util { } if (!$isSubAdmin) { - header('Location: ' . OC_Helper::linkToAbsolute('', 'index.php')); + header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php')); exit(); } return true; @@ -1106,11 +1108,11 @@ class OC_Util { * @return string */ public static function getInstanceId() { - $id = OC_Config::getValue('instanceid', null); + $id = \OC::$server->getSystemConfig()->getValue('instanceid', null); if (is_null($id)) { // We need to guarantee at least one letter in instanceid so it can be used as the session_name $id = 'oc' . \OC::$server->getSecureRandom()->getLowStrengthGenerator()->generate(10, \OCP\Security\ISecureRandom::CHAR_LOWER.\OCP\Security\ISecureRandom::CHAR_DIGITS); - OC_Config::$object->setValue('instanceid', $id); + \OC::$server->getSystemConfig()->setValue('instanceid', $id); } return $id; } @@ -1125,7 +1127,6 @@ class OC_Util { * Creates a 'request token' (random) and stores it inside the session. * Ever subsequent (ajax) request must use such a valid token to succeed, * otherwise the request will be denied as a protection against CSRF. - * @see OC_Util::isCallRegistered() */ public static function callRegister() { // Use existing token if function has already been called @@ -1153,27 +1154,6 @@ class OC_Util { } /** - * Check an ajax get/post call if the request token is valid. - * - * @return boolean False if request token is not set or is invalid. - * @see OC_Util::callRegister() - */ - public static function isCallRegistered() { - return \OC::$server->getRequest()->passesCSRFCheck(); - } - - /** - * Check an ajax get/post call if the request token is valid. Exit if not. - * - * @return void - */ - public static function callCheck() { - if (!OC_Util::isCallRegistered()) { - exit(); - } - } - - /** * Public function to sanitize HTML * * This function is used to sanitize HTML and should be applied on any @@ -1248,7 +1228,7 @@ class OC_Util { fclose($fp); // accessing the file via http - $url = OC_Helper::makeURLAbsolute(OC::$WEBROOT . '/data' . $fileName); + $url = \OC::$server->getURLGenerator()->getAbsoluteURL(OC::$WEBROOT . '/data' . $fileName); try { $content = \OC::$server->getHTTPClientService()->newClient()->get($url)->getBody(); } catch (\Exception $e) { @@ -1362,7 +1342,7 @@ class OC_Util { * @return string the theme */ public static function getTheme() { - $theme = OC_Config::getValue("theme", ''); + $theme = \OC::$server->getSystemConfig()->getValue("theme", ''); if ($theme === '') { if (is_dir(OC::$SERVERROOT . '/themes/default')) { @@ -1503,14 +1483,28 @@ class OC_Util { * * @param \OCP\IConfig $config * @return bool whether the core or any app needs an upgrade + * @throws \OC\HintException When the upgrade from the given version is not allowed */ public static function needUpgrade(\OCP\IConfig $config) { if ($config->getSystemValue('installed', false)) { $installedVersion = $config->getSystemValue('version', '0.0.0'); - $currentVersion = implode('.', OC_Util::getVersion()); + $currentVersion = implode('.', \OCP\Util::getVersion()); $versionDiff = version_compare($currentVersion, $installedVersion); if ($versionDiff > 0) { return true; + } else if ($config->getSystemValue('debug', false) && $versionDiff < 0) { + // downgrade with debug + $installedMajor = explode('.', $installedVersion); + $installedMajor = $installedMajor[0] . '.' . $installedMajor[1]; + $currentMajor = explode('.', $currentVersion); + $currentMajor = $currentMajor[0] . '.' . $currentMajor[1]; + if ($installedMajor === $currentMajor) { + // Same major, allow downgrade for developers + return true; + } else { + // downgrade attempt, throw exception + throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')'); + } } else if ($versionDiff < 0) { // downgrade attempt, throw exception throw new \OC\HintException('Downgrading is not supported and is likely to cause unpredictable issues (from ' . $installedVersion . ' to ' . $currentVersion . ')'); |