diff options
Diffstat (limited to 'lib/private')
35 files changed, 677 insertions, 347 deletions
diff --git a/lib/private/App/DependencyAnalyzer.php b/lib/private/App/DependencyAnalyzer.php index 7adb5d1c574..67268981e99 100644 --- a/lib/private/App/DependencyAnalyzer.php +++ b/lib/private/App/DependencyAnalyzer.php @@ -197,6 +197,9 @@ class DependencyAnalyzer { if (!is_array($commands)) { $commands = array($commands); } + if (isset($commands['@value'])) { + $commands = [$commands]; + } $os = $this->platform->getOS(); foreach ($commands as $command) { if (isset($command['@attributes']['os']) && $command['@attributes']['os'] !== $os) { @@ -224,6 +227,9 @@ class DependencyAnalyzer { if (!is_array($libs)) { $libs = array($libs); } + if (isset($libs['@value'])) { + $libs = [$libs]; + } foreach ($libs as $lib) { $libName = $this->getValue($lib); $libVersion = $this->platform->getLibraryVersion($libName); diff --git a/lib/private/AppFramework/Db/Db.php b/lib/private/AppFramework/Db/Db.php index 5fea09747af..450549ffdbb 100644 --- a/lib/private/AppFramework/Db/Db.php +++ b/lib/private/AppFramework/Db/Db.php @@ -31,6 +31,7 @@ use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDb; use OCP\IDBConnection; use OCP\PreConditionNotMetException; +use Doctrine\DBAL\Platforms\MySqlPlatform; /** * @deprecated use IDBConnection directly, will be removed in ownCloud 10 @@ -300,4 +301,14 @@ class Db implements IDb { public function escapeLikeParameter($param) { return $this->connection->escapeLikeParameter($param); } + + /** + * Check whether or not the current database support 4byte wide unicode + * + * @return bool + * @since 9.2.0 + */ + public function supports4ByteText() { + return $this->connection->supports4ByteText(); + } } diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php index b2fdf86d9c2..671093ff08b 100644 --- a/lib/private/AppFramework/DependencyInjection/DIContainer.php +++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php @@ -385,7 +385,9 @@ class DIContainer extends SimpleContainer implements IAppContainer { $c['AppName'], $app->isLoggedIn(), $app->isAdminUser(), - $app->getServer()->getContentSecurityPolicyManager() + $app->getServer()->getContentSecurityPolicyManager(), + $app->getServer()->getCsrfTokenManager(), + $app->getServer()->getContentSecurityPolicyNonceManager() ); }); diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index ba8a48381bd..c7a3be163fe 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -61,7 +61,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { // Firefox User Agent from https://developer.mozilla.org/en-US/docs/Web/HTTP/Gecko_user_agent_string_reference const USER_AGENT_FIREFOX = '/^Mozilla\/5\.0 \([^)]+\) Gecko\/[0-9.]+ Firefox\/[0-9.]+$/'; // Chrome User Agent from https://developer.chrome.com/multidevice/user-agent - const USER_AGENT_CHROME = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+$/'; + const USER_AGENT_CHROME = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)( Ubuntu Chromium\/[0-9.]+|) Chrome\/[0-9.]+ (Mobile Safari|Safari)\/[0-9.]+$/'; // Safari User Agent from http://www.useragentstring.com/pages/Safari/ const USER_AGENT_SAFARI = '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/[0-9.]+ Safari\/[0-9.A-Z]+$/'; // Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent diff --git a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php index 5e253d0954a..183e55740ea 100644 --- a/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php +++ b/lib/private/AppFramework/Middleware/Security/SecurityMiddleware.php @@ -36,6 +36,8 @@ use OC\AppFramework\Middleware\Security\Exceptions\NotLoggedInException; use OC\AppFramework\Middleware\Security\Exceptions\StrictCookieMissingException; use OC\AppFramework\Utility\ControllerMethodReflector; use OC\Security\CSP\ContentSecurityPolicyManager; +use OC\Security\CSP\ContentSecurityPolicyNonceManager; +use OC\Security\CSRF\CsrfTokenManager; use OCP\AppFramework\Http\ContentSecurityPolicy; use OCP\AppFramework\Http\EmptyContentSecurityPolicy; use OCP\AppFramework\Http\RedirectResponse; @@ -77,6 +79,10 @@ class SecurityMiddleware extends Middleware { private $isAdminUser; /** @var ContentSecurityPolicyManager */ private $contentSecurityPolicyManager; + /** @var CsrfTokenManager */ + private $csrfTokenManager; + /** @var ContentSecurityPolicyNonceManager */ + private $cspNonceManager; /** * @param IRequest $request @@ -88,6 +94,8 @@ class SecurityMiddleware extends Middleware { * @param bool $isLoggedIn * @param bool $isAdminUser * @param ContentSecurityPolicyManager $contentSecurityPolicyManager + * @param CSRFTokenManager $csrfTokenManager + * @param ContentSecurityPolicyNonceManager $cspNonceManager */ public function __construct(IRequest $request, ControllerMethodReflector $reflector, @@ -97,7 +105,9 @@ class SecurityMiddleware extends Middleware { $appName, $isLoggedIn, $isAdminUser, - ContentSecurityPolicyManager $contentSecurityPolicyManager) { + ContentSecurityPolicyManager $contentSecurityPolicyManager, + CsrfTokenManager $csrfTokenManager, + ContentSecurityPolicyNonceManager $cspNonceManager) { $this->navigationManager = $navigationManager; $this->request = $request; $this->reflector = $reflector; @@ -107,6 +117,8 @@ class SecurityMiddleware extends Middleware { $this->isLoggedIn = $isLoggedIn; $this->isAdminUser = $isAdminUser; $this->contentSecurityPolicyManager = $contentSecurityPolicyManager; + $this->csrfTokenManager = $csrfTokenManager; + $this->cspNonceManager = $cspNonceManager; } @@ -190,6 +202,10 @@ class SecurityMiddleware extends Middleware { $defaultPolicy = $this->contentSecurityPolicyManager->getDefaultPolicy(); $defaultPolicy = $this->contentSecurityPolicyManager->mergePolicies($defaultPolicy, $policy); + if($this->cspNonceManager->browserSupportsCspV3()) { + $defaultPolicy->useJsNonce($this->csrfTokenManager->getToken()->getEncryptedValue()); + } + $response->setContentSecurityPolicy($defaultPolicy); return $response; diff --git a/lib/private/Avatar.php b/lib/private/Avatar.php index c3a068701df..fc1909c3bda 100644 --- a/lib/private/Avatar.php +++ b/lib/private/Avatar.php @@ -131,7 +131,7 @@ class Avatar implements IAvatar { } if (!($img->height() === $img->width())) { - throw new NotSquareException(); + throw new NotSquareException($this->l->t("Avatar image is not square")); } $this->remove(); diff --git a/lib/private/Comments/Comment.php b/lib/private/Comments/Comment.php index f6f0801c683..b5f063be323 100644 --- a/lib/private/Comments/Comment.php +++ b/lib/private/Comments/Comment.php @@ -204,6 +204,43 @@ class Comment implements IComment { } /** + * returns an array containing mentions that are included in the comment + * + * @return array each mention provides a 'type' and an 'id', see example below + * @since 9.2.0 + * + * The return array looks like: + * [ + * [ + * 'type' => 'user', + * 'id' => 'citizen4' + * ], + * [ + * 'type' => 'group', + * 'id' => 'media' + * ], + * … + * ] + * + */ + public function getMentions() { + $ok = preg_match_all('/\B@[a-z0-9_\-@\.\']+/i', $this->getMessage(), $mentions); + if(!$ok || !isset($mentions[0]) || !is_array($mentions[0])) { + return []; + } + $uids = array_unique($mentions[0]); + $result = []; + foreach ($uids as $uid) { + // exclude author, no self-mentioning + if($uid === '@' . $this->getActorId()) { + continue; + } + $result[] = ['type' => 'user', 'id' => substr($uid, 1)]; + } + return $result; + } + + /** * returns the verb of the comment * * @return string diff --git a/lib/private/Comments/Manager.php b/lib/private/Comments/Manager.php index b3ecab731e1..001f4f9441c 100644 --- a/lib/private/Comments/Manager.php +++ b/lib/private/Comments/Manager.php @@ -55,6 +55,9 @@ class Manager implements ICommentsManager { /** @var ICommentsEventHandler[] */ protected $eventHandlers = []; + /** @var \Closure[] */ + protected $displayNameResolvers = []; + /** * Manager constructor. * @@ -760,6 +763,50 @@ class Manager implements ICommentsManager { } /** + * registers a method that resolves an ID to a display name for a given type + * + * @param string $type + * @param \Closure $closure + * @throws \OutOfBoundsException + * @since 9.2.0 + * + * Only one resolver shall be registered per type. Otherwise a + * \OutOfBoundsException has to thrown. + */ + public function registerDisplayNameResolver($type, \Closure $closure) { + if(!is_string($type)) { + throw new \InvalidArgumentException('String expected.'); + } + if(isset($this->displayNameResolvers[$type])) { + throw new \OutOfBoundsException('Displayname resolver for this type already registered'); + } + $this->displayNameResolvers[$type] = $closure; + } + + /** + * resolves a given ID of a given Type to a display name. + * + * @param string $type + * @param string $id + * @return string + * @throws \OutOfBoundsException + * @since 9.2.0 + * + * If a provided type was not registered, an \OutOfBoundsException shall + * be thrown. It is upon the resolver discretion what to return of the + * provided ID is unknown. It must be ensured that a string is returned. + */ + public function resolveDisplayName($type, $id) { + if(!is_string($type)) { + throw new \InvalidArgumentException('String expected.'); + } + if(!isset($this->displayNameResolvers[$type])) { + throw new \OutOfBoundsException('No Displayname resolver for this type registered'); + } + return (string)$this->displayNameResolvers[$type]($id); + } + + /** * returns valid, registered entities * * @return \OCP\Comments\ICommentsEventHandler[] diff --git a/lib/private/Console/Application.php b/lib/private/Console/Application.php index 299b23714b6..cd76b43f095 100644 --- a/lib/private/Console/Application.php +++ b/lib/private/Console/Application.php @@ -26,6 +26,7 @@ */ namespace OC\Console; +use OC\NeedsUpdateException; use OC_App; use OCP\AppFramework\QueryException; use OCP\Console\ConsoleEvent; @@ -84,39 +85,43 @@ class Application { if ($input->getOption('no-warnings')) { $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); } - require_once __DIR__ . '/../../../core/register_command.php'; - if ($this->config->getSystemValue('installed', false)) { - if (\OCP\Util::needUpgrade()) { - if ($input->getArgument('command') !== '_completion') { - $output->writeln("Nextcloud or one of the apps require upgrade - only a limited number of commands are available"); - $output->writeln("You may use your browser or the occ upgrade command to do the upgrade"); - } - } elseif ($this->config->getSystemValue('maintenance', false)) { - if ($input->getArgument('command') !== '_completion') { - $output->writeln("Nextcloud is in maintenance mode - no apps have been loaded"); - } - } else { - OC_App::loadApps(); - foreach (\OC::$server->getAppManager()->getInstalledApps() as $app) { - $appPath = \OC_App::getAppPath($app); - if($appPath === false) { - continue; - } - // load commands using info.xml - $info = \OC_App::getAppInfo($app); - if (isset($info['commands'])) { - $this->loadCommandsFromInfoXml($info['commands']); + try { + require_once __DIR__ . '/../../../core/register_command.php'; + if ($this->config->getSystemValue('installed', false)) { + if (\OCP\Util::needUpgrade()) { + throw new NeedsUpdateException(); + } elseif ($this->config->getSystemValue('maintenance', false)) { + if ($input->getArgument('command') !== '_completion') { + $output->writeln("Nextcloud is in maintenance mode - no apps have been loaded"); } - // load from register_command.php - \OC_App::registerAutoloading($app, $appPath); - $file = $appPath . '/appinfo/register_command.php'; - if (file_exists($file)) { - require $file; + } else { + OC_App::loadApps(); + foreach (\OC::$server->getAppManager()->getInstalledApps() as $app) { + $appPath = \OC_App::getAppPath($app); + if ($appPath === false) { + continue; + } + // load commands using info.xml + $info = \OC_App::getAppInfo($app); + if (isset($info['commands'])) { + $this->loadCommandsFromInfoXml($info['commands']); + } + // load from register_command.php + \OC_App::registerAutoloading($app, $appPath); + $file = $appPath . '/appinfo/register_command.php'; + if (file_exists($file)) { + require $file; + } } } + } else if ($input->getArgument('command') !== '_completion') { + $output->writeln("Nextcloud is not installed - only a limited number of commands are available"); + } + } catch(NeedsUpdateException $e) { + if ($input->getArgument('command') !== '_completion') { + $output->writeln("Nextcloud or one of the apps require upgrade - only a limited number of commands are available"); + $output->writeln("You may use your browser or the occ upgrade command to do the upgrade"); } - } else if ($input->getArgument('command') !== '_completion') { - $output->writeln("Nextcloud is not installed - only a limited number of commands are available"); } if ($input->getFirstArgument() !== 'check') { diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php index 4fa25aae08d..dfe2e86b617 100644 --- a/lib/private/DB/Connection.php +++ b/lib/private/DB/Connection.php @@ -33,6 +33,7 @@ use Doctrine\DBAL\Driver; use Doctrine\DBAL\Configuration; use Doctrine\DBAL\Cache\QueryCacheProfile; use Doctrine\Common\EventManager; +use Doctrine\DBAL\Platforms\MySqlPlatform; use OC\DB\QueryBuilder\QueryBuilder; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; @@ -402,4 +403,14 @@ class Connection extends \Doctrine\DBAL\Connection implements IDBConnection { public function escapeLikeParameter($param) { return addcslashes($param, '\\_%'); } + + /** + * Check whether or not the current database support 4byte wide unicode + * + * @return bool + * @since 9.2.0 + */ + public function supports4ByteText() { + return ! ($this->getDatabasePlatform() instanceof MySqlPlatform && $this->getParams()['charset'] !== 'utf8mb4'); + } } diff --git a/lib/private/Encryption/DecryptAll.php b/lib/private/Encryption/DecryptAll.php index b84395b9e17..caf4237ab9c 100644 --- a/lib/private/Encryption/DecryptAll.php +++ b/lib/private/Encryption/DecryptAll.php @@ -211,7 +211,7 @@ class DecryptAll { $content = $this->rootView->getDirectoryContent($root); foreach ($content as $file) { // only decrypt files owned by the user - if($file->getStorage()->instanceOfStorage('OC\Files\Storage\Shared')) { + if($file->getStorage()->instanceOfStorage('OCA\Files_Sharing\SharedStorage')) { continue; } $path = $root . '/' . $file['name']; diff --git a/lib/private/Encryption/EncryptionWrapper.php b/lib/private/Encryption/EncryptionWrapper.php index 233390f8739..573fe0159ea 100644 --- a/lib/private/Encryption/EncryptionWrapper.php +++ b/lib/private/Encryption/EncryptionWrapper.php @@ -81,7 +81,7 @@ class EncryptionWrapper { 'mount' => $mount ]; - if (!$storage->instanceOfStorage('OC\Files\Storage\Shared') + if (!$storage->instanceOfStorage('OCA\Files_Sharing\SharedStorage') && !$storage->instanceOfStorage('OCA\Files_Sharing\External\Storage') && !$storage->instanceOfStorage('OC\Files\Storage\OwnCloud')) { diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php index 353b89068cb..288a02ef207 100644 --- a/lib/private/Files/Node/Folder.php +++ b/lib/private/Files/Node/Folder.php @@ -42,7 +42,7 @@ class Folder extends Node implements \OCP\Files\Folder { */ public function getFullPath($path) { if (!$this->isValidPath($path)) { - throw new NotPermittedException(); + throw new NotPermittedException('Invalid path'); } return $this->path . $this->normalizePath($path); } @@ -152,7 +152,7 @@ class Folder extends Node implements \OCP\Files\Folder { $this->root->emit('\OC\Files', 'postCreate', array($node)); return $node; } else { - throw new NotPermittedException(); + throw new NotPermittedException('No create permission for folder'); } } @@ -173,7 +173,7 @@ class Folder extends Node implements \OCP\Files\Folder { $this->root->emit('\OC\Files', 'postCreate', array($node)); return $node; } else { - throw new NotPermittedException(); + throw new NotPermittedException('No create permission for path'); } } @@ -321,7 +321,7 @@ class Folder extends Node implements \OCP\Files\Folder { $this->root->emit('\OC\Files', 'postDelete', array($nonExisting)); $this->exists = false; } else { - throw new NotPermittedException(); + throw new NotPermittedException('No delete permission for path'); } } @@ -343,7 +343,7 @@ class Folder extends Node implements \OCP\Files\Folder { $this->root->emit('\OC\Files', 'postWrite', array($targetNode)); return $targetNode; } else { - throw new NotPermittedException(); + throw new NotPermittedException('No permission to copy to path'); } } @@ -366,7 +366,7 @@ class Folder extends Node implements \OCP\Files\Folder { $this->path = $targetPath; return $targetNode; } else { - throw new NotPermittedException(); + throw new NotPermittedException('No permission to move to path'); } } diff --git a/lib/private/Files/Storage/Common.php b/lib/private/Files/Storage/Common.php index 63d3c004fd2..c975791295d 100644 --- a/lib/private/Files/Storage/Common.php +++ b/lib/private/Files/Storage/Common.php @@ -466,6 +466,10 @@ abstract class Common implements Storage, ILockingStorage { * @return bool */ public function instanceOfStorage($class) { + if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') { + // FIXME Temporary fix to keep existing checks working + $class = '\OCA\Files_Sharing\SharedStorage'; + } return is_a($this, $class); } diff --git a/lib/private/Files/Storage/Local.php b/lib/private/Files/Storage/Local.php index 0d63fd46ecc..4fe7dcafbbf 100644 --- a/lib/private/Files/Storage/Local.php +++ b/lib/private/Files/Storage/Local.php @@ -155,7 +155,7 @@ class Local extends \OC\Files\Storage\Common { $fullPath = $this->getSourcePath($path); if (PHP_INT_SIZE === 4) { $helper = new \OC\LargeFileHelper; - return $helper->getFilesize($fullPath); + return $helper->getFileSize($fullPath); } return filesize($fullPath); } @@ -173,8 +173,16 @@ class Local extends \OC\Files\Storage\Common { } public function filemtime($path) { - clearstatcache($this->getSourcePath($path)); - return $this->file_exists($path) ? filemtime($this->getSourcePath($path)) : false; + $fullPath = $this->getSourcePath($path); + clearstatcache($fullPath); + if (!$this->file_exists($path)) { + return false; + } + if (PHP_INT_SIZE === 4) { + $helper = new \OC\LargeFileHelper(); + return $helper->getFileMtime($fullPath); + } + return filemtime($fullPath); } public function touch($path, $mtime = null) { diff --git a/lib/private/Files/Storage/Wrapper/PermissionsMask.php b/lib/private/Files/Storage/Wrapper/PermissionsMask.php index 39375602c34..7bcb1087fef 100644 --- a/lib/private/Files/Storage/Wrapper/PermissionsMask.php +++ b/lib/private/Files/Storage/Wrapper/PermissionsMask.php @@ -78,6 +78,14 @@ class PermissionsMask extends Wrapper { } public function rename($path1, $path2) { + $p = strpos($path1, $path2); + if ($p === 0) { + $part = substr($path1, strlen($path2)); + //This is a rename of the transfer file to the original file + if (strpos($part, '.ocTransferId') === 0) { + return $this->checkMask(Constants::PERMISSION_CREATE) and parent::rename($path1, $path2); + } + } return $this->checkMask(Constants::PERMISSION_UPDATE) and parent::rename($path1, $path2); } diff --git a/lib/private/Files/Storage/Wrapper/Wrapper.php b/lib/private/Files/Storage/Wrapper/Wrapper.php index c52b3394832..71b64d8c82c 100644 --- a/lib/private/Files/Storage/Wrapper/Wrapper.php +++ b/lib/private/Files/Storage/Wrapper/Wrapper.php @@ -483,6 +483,10 @@ class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage { * @return bool */ public function instanceOfStorage($class) { + if (ltrim($class, '\\') === 'OC\Files\Storage\Shared') { + // FIXME Temporary fix to keep existing checks working + $class = '\OCA\Files_Sharing\SharedStorage'; + } return is_a($this, $class) or $this->getWrapperStorage()->instanceOfStorage($class); } diff --git a/lib/private/Files/Stream/OC.php b/lib/private/Files/Stream/OC.php deleted file mode 100644 index f415bc13b15..00000000000 --- a/lib/private/Files/Stream/OC.php +++ /dev/null @@ -1,154 +0,0 @@ -<?php -/** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> - * @author Robin McCorkell <robin@mccorkell.me.uk> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * - */ - -namespace OC\Files\Stream; - -/** - * a stream wrappers for ownCloud's virtual filesystem - */ -class OC { - /** - * @var \OC\Files\View - */ - static private $rootView; - - private $path; - - /** - * @var resource - */ - private $dirSource; - - /** - * @var resource - */ - private $fileSource; - private $meta; - - private function setup(){ - if (!self::$rootView) { - self::$rootView = new \OC\Files\View(''); - } - } - - public function stream_open($path, $mode, $options, &$opened_path) { - $this->setup(); - $path = substr($path, strlen('oc://')); - $this->path = $path; - $this->fileSource = self::$rootView->fopen($path, $mode); - if (is_resource($this->fileSource)) { - $this->meta = stream_get_meta_data($this->fileSource); - } - return is_resource($this->fileSource); - } - - public function stream_seek($offset, $whence = SEEK_SET) { - return fseek($this->fileSource, $offset, $whence) === 0; - } - - public function stream_tell() { - return ftell($this->fileSource); - } - - public function stream_read($count) { - return fread($this->fileSource, $count); - } - - public function stream_write($data) { - return fwrite($this->fileSource, $data); - } - - public function stream_set_option($option, $arg1, $arg2) { - switch ($option) { - case STREAM_OPTION_BLOCKING: - stream_set_blocking($this->fileSource, $arg1); - break; - case STREAM_OPTION_READ_TIMEOUT: - stream_set_timeout($this->fileSource, $arg1, $arg2); - break; - case STREAM_OPTION_WRITE_BUFFER: - stream_set_write_buffer($this->fileSource, $arg1, $arg2); - } - } - - public function stream_stat() { - return fstat($this->fileSource); - } - - public function stream_lock($mode) { - flock($this->fileSource, $mode); - } - - public function stream_flush() { - return fflush($this->fileSource); - } - - public function stream_eof() { - return feof($this->fileSource); - } - - public function url_stat($path) { - $this->setup(); - $path = substr($path, strlen('oc://')); - if (self::$rootView->file_exists($path)) { - return self::$rootView->stat($path); - } else { - return false; - } - } - - public function stream_close() { - fclose($this->fileSource); - } - - public function unlink($path) { - $this->setup(); - $path = substr($path, strlen('oc://')); - return self::$rootView->unlink($path); - } - - public function dir_opendir($path, $options) { - $this->setup(); - $path = substr($path, strlen('oc://')); - $this->path = $path; - $this->dirSource = self::$rootView->opendir($path); - if (is_resource($this->dirSource)) { - $this->meta = stream_get_meta_data($this->dirSource); - } - return is_resource($this->dirSource); - } - - public function dir_readdir() { - return readdir($this->dirSource); - } - - public function dir_closedir() { - closedir($this->dirSource); - } - - public function dir_rewinddir() { - rewinddir($this->dirSource); - } -} diff --git a/lib/private/Files/View.php b/lib/private/Files/View.php index fa6ba20c342..f36e2c2c64f 100644 --- a/lib/private/Files/View.php +++ b/lib/private/Files/View.php @@ -1357,7 +1357,7 @@ class View { $subStorage = $mount->getStorage(); if ($subStorage) { // exclude shared storage ? - if ($extOnly && $subStorage instanceof \OC\Files\Storage\Shared) { + if ($extOnly && $subStorage instanceof \OCA\Files_Sharing\SharedStorage) { continue; } $subCache = $subStorage->getCache(''); @@ -1806,13 +1806,15 @@ class View { throw new InvalidPathException($l10n->t('Dot files are not allowed')); } - // verify database - e.g. mysql only 3-byte chars - if (preg_match('%(?: + if (!\OC::$server->getDatabaseConnection()->supports4ByteText()) { + // verify database - e.g. mysql only 3-byte chars + if (preg_match('%(?: \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 )%xs', $fileName)) { - throw new InvalidPathException($l10n->t('4-byte characters are not supported in file names')); + throw new InvalidPathException($l10n->t('4-byte characters are not supported in file names')); + } } try { diff --git a/lib/private/LargeFileHelper.php b/lib/private/LargeFileHelper.php index 9d0fe864033..9f18a6acd6b 100644 --- a/lib/private/LargeFileHelper.php +++ b/lib/private/LargeFileHelper.php @@ -1,6 +1,7 @@ <?php /** * @copyright Copyright (c) 2016, ownCloud, Inc. + * @copyright Copyright (c) 2016, Lukas Reschke <lukas@statuscode.ch> * * @author Andreas Fischer <bantu@owncloud.com> * @author Lukas Reschke <lukas@statuscode.ch> @@ -51,7 +52,7 @@ class LargeFileHelper { public function __construct() { $pow_2_53 = floatval(self::POW_2_53_MINUS_1) + 1.0; if ($this->formatUnsignedInteger($pow_2_53) !== self::POW_2_53) { - throw new \RunTimeException( + throw new \RuntimeException( 'This class assumes floats to be double precision or "better".' ); } @@ -98,10 +99,6 @@ class LargeFileHelper { if (!is_null($fileSize)) { return $fileSize; } - $fileSize = $this->getFileSizeViaCOM($filename); - if (!is_null($fileSize)) { - return $fileSize; - } $fileSize = $this->getFileSizeViaExec($filename); if (!is_null($fileSize)) { return $fileSize; @@ -154,12 +151,6 @@ class LargeFileHelper { $result = $this->exec("stat -c %s $arg"); } else if (strpos($os, 'bsd') !== false || strpos($os, 'darwin') !== false) { $result = $this->exec("stat -f %z $arg"); - } else if (strpos($os, 'win') !== false) { - $result = $this->exec("for %F in ($arg) do @echo %~zF"); - if (is_null($result)) { - // PowerShell - $result = $this->exec("(Get-Item $arg).length"); - } } return $result; } @@ -187,6 +178,23 @@ class LargeFileHelper { return $result; } + /** + * Returns the current mtime for $fullPath + * + * @param string $fullPath + * @return int + */ + public function getFileMtime($fullPath) { + if (\OC_Helper::is_function_enabled('exec')) { + $os = strtolower(php_uname('s')); + if (strpos($os, 'linux') !== false) { + return $this->exec('stat -c %Y ' . escapeshellarg($fullPath)); + } + } + + return filemtime($fullPath); + } + protected function exec($cmd) { $result = trim(exec($cmd)); return ctype_digit($result) ? 0 + $result : null; diff --git a/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php b/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php new file mode 100644 index 00000000000..284700566d6 --- /dev/null +++ b/lib/private/Security/CSP/ContentSecurityPolicyNonceManager.php @@ -0,0 +1,82 @@ +<?php +/** + * @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OC\Security\CSP; + +use OC\AppFramework\Http\Request; +use OC\Security\CSRF\CsrfTokenManager; +use OCP\IRequest; + +/** + * @package OC\Security\CSP + */ +class ContentSecurityPolicyNonceManager { + /** @var CsrfTokenManager */ + private $csrfTokenManager; + /** @var IRequest */ + private $request; + /** @var string */ + private $nonce = ''; + + /** + * @param CsrfTokenManager $csrfTokenManager + * @param IRequest $request + */ + public function __construct(CsrfTokenManager $csrfTokenManager, + IRequest $request) { + $this->csrfTokenManager = $csrfTokenManager; + $this->request = $request; + } + + /** + * Returns the current CSP nounce + * + * @return string + */ + public function getNonce() { + if($this->nonce === '') { + $this->nonce = base64_encode($this->csrfTokenManager->getToken()->getEncryptedValue()); + } + + return $this->nonce; + } + + /** + * Check if the browser supports CSP v3 + * + * @return bool + */ + public function browserSupportsCspV3() { + $browserWhitelist = [ + Request::USER_AGENT_CHROME, + // Firefox 45+ + '/^Mozilla\/5\.0 \([^)]+\) Gecko\/[0-9.]+ Firefox\/(4[5-9]|[5-9][0-9])\.[0-9.]+$/', + // Safari 10+ + '/^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Version\/1[0-9.]+ Safari\/[0-9.A-Z]+$/', + ]; + + if($this->request->isUserAgent($browserWhitelist)) { + return true; + } + + return false; + } +} diff --git a/lib/private/Security/CSRF/CsrfToken.php b/lib/private/Security/CSRF/CsrfToken.php index bf61e339f77..dce9a83b727 100644 --- a/lib/private/Security/CSRF/CsrfToken.php +++ b/lib/private/Security/CSRF/CsrfToken.php @@ -33,6 +33,8 @@ namespace OC\Security\CSRF; class CsrfToken { /** @var string */ private $value; + /** @var string */ + private $encryptedValue = ''; /** * @param string $value Value of the token. Can be encrypted or not encrypted. @@ -48,8 +50,12 @@ class CsrfToken { * @return string */ public function getEncryptedValue() { - $sharedSecret = base64_encode(random_bytes(strlen($this->value))); - return base64_encode($this->value ^ $sharedSecret) .':'.$sharedSecret; + if($this->encryptedValue === '') { + $sharedSecret = base64_encode(random_bytes(strlen($this->value))); + $this->encryptedValue = base64_encode($this->value ^ $sharedSecret) . ':' . $sharedSecret; + } + + return $this->encryptedValue; } /** diff --git a/lib/private/Security/CSRF/CsrfTokenManager.php b/lib/private/Security/CSRF/CsrfTokenManager.php index d621cc2c29f..b43ca3d3679 100644 --- a/lib/private/Security/CSRF/CsrfTokenManager.php +++ b/lib/private/Security/CSRF/CsrfTokenManager.php @@ -34,6 +34,8 @@ class CsrfTokenManager { private $tokenGenerator; /** @var SessionStorage */ private $sessionStorage; + /** @var CsrfToken|null */ + private $csrfToken = null; /** * @param CsrfTokenGenerator $tokenGenerator @@ -51,6 +53,10 @@ class CsrfTokenManager { * @return CsrfToken */ public function getToken() { + if(!is_null($this->csrfToken)) { + return $this->csrfToken; + } + if($this->sessionStorage->hasToken()) { $value = $this->sessionStorage->getToken(); } else { @@ -58,7 +64,8 @@ class CsrfTokenManager { $this->sessionStorage->setToken($value); } - return new CsrfToken($value); + $this->csrfToken = new CsrfToken($value); + return $this->csrfToken; } /** @@ -69,13 +76,15 @@ class CsrfTokenManager { public function refreshToken() { $value = $this->tokenGenerator->generateToken(); $this->sessionStorage->setToken($value); - return new CsrfToken($value); + $this->csrfToken = new CsrfToken($value); + return $this->csrfToken; } /** * Remove the current token from the storage. */ public function removeToken() { + $this->csrfToken = null; $this->sessionStorage->removeToken(); } diff --git a/lib/private/Server.php b/lib/private/Server.php index 11558118d52..21ec311401d 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -73,6 +73,7 @@ use OC\Security\Bruteforce\Throttler; use OC\Security\CertificateManager; use OC\Security\CSP\ContentSecurityPolicyManager; use OC\Security\Crypto; +use OC\Security\CSP\ContentSecurityPolicyNonceManager; use OC\Security\CSRF\CsrfTokenGenerator; use OC\Security\CSRF\CsrfTokenManager; use OC\Security\CSRF\TokenStorage\SessionStorage; @@ -708,6 +709,12 @@ class Server extends ServerContainer implements IServerContainer { $this->registerService('ContentSecurityPolicyManager', function (Server $c) { return new ContentSecurityPolicyManager(); }); + $this->registerService('ContentSecurityPolicyNonceManager', function(Server $c) { + return new ContentSecurityPolicyNonceManager( + $c->getCsrfTokenManager(), + $c->getRequest() + ); + }); $this->registerService('ShareManager', function(Server $c) { $config = $c->getConfig(); $factoryClass = $config->getSystemValue('sharing.managerFactory', '\OC\Share20\ProviderFactory'); @@ -1406,6 +1413,13 @@ class Server extends ServerContainer implements IServerContainer { } /** + * @return ContentSecurityPolicyNonceManager + */ + public function getContentSecurityPolicyNonceManager() { + return $this->query('ContentSecurityPolicyNonceManager'); + } + + /** * Not a public API as of 8.2, wait for 9.0 * * @return \OCA\Files_External\Service\BackendService diff --git a/lib/private/Share/MailNotifications.php b/lib/private/Share/MailNotifications.php index aaecd5353e5..1bbd365699c 100644 --- a/lib/private/Share/MailNotifications.php +++ b/lib/private/Share/MailNotifications.php @@ -89,75 +89,6 @@ class MailNotifications { } /** - * inform users if a file was shared with them - * - * @param IUser[] $recipientList list of recipients - * @param string $itemSource shared item source - * @param string $itemType shared item type - * @return array list of user to whom the mail send operation failed - */ - public function sendInternalShareMail($recipientList, $itemSource, $itemType) { - $noMail = []; - - foreach ($recipientList as $recipient) { - $recipientDisplayName = $recipient->getDisplayName(); - $to = $recipient->getEMailAddress(); - - if ($to === '') { - $noMail[] = $recipientDisplayName; - continue; - } - - $items = $this->getItemSharedWithUser($itemSource, $itemType, $recipient); - $filename = trim($items[0]['file_target'], '/'); - $subject = (string) $this->l->t('%s shared »%s« with you', array($this->senderDisplayName, $filename)); - $expiration = null; - if (isset($items[0]['expiration'])) { - try { - $date = new DateTime($items[0]['expiration']); - $expiration = $date->getTimestamp(); - } catch (\Exception $e) { - $this->logger->error("Couldn't read date: ".$e->getMessage(), ['app' => 'sharing']); - } - } - - $link = $this->urlGenerator->linkToRouteAbsolute( - 'files.viewcontroller.showFile', - ['fileId' => $items[0]['item_source']] - ); - - list($htmlBody, $textBody) = $this->createMailBody($filename, $link, $expiration, 'internal'); - - // send it out now - try { - $message = $this->mailer->createMessage(); - $message->setSubject($subject); - $message->setTo([$to => $recipientDisplayName]); - $message->setHtmlBody($htmlBody); - $message->setPlainBody($textBody); - $message->setFrom([ - Util::getDefaultEmailAddress('sharing-noreply') => - (string)$this->l->t('%s via %s', [ - $this->senderDisplayName, - $this->defaults->getName() - ]), - ]); - if(!is_null($this->replyTo)) { - $message->setReplyTo([$this->replyTo]); - } - - $this->mailer->send($message); - } catch (\Exception $e) { - $this->logger->error("Can't send mail to inform the user about an internal share: ".$e->getMessage(), ['app' => 'sharing']); - $noMail[] = $recipientDisplayName; - } - } - - return $noMail; - - } - - /** * inform recipient about public link share * * @param string $recipient recipient email address @@ -224,15 +155,4 @@ class MailNotifications { return [$htmlMail, $plainTextMail]; } - - /** - * @param string $itemSource - * @param string $itemType - * @param IUser $recipient - * @return array - */ - protected function getItemSharedWithUser($itemSource, $itemType, $recipient) { - return Share::getItemSharedWithUser($itemType, $itemSource, $recipient->getUID()); - } - } diff --git a/lib/private/Share/Share.php b/lib/private/Share/Share.php index 9210dfd1fd1..33801cd6347 100644 --- a/lib/private/Share/Share.php +++ b/lib/private/Share/Share.php @@ -1059,7 +1059,7 @@ class Share extends Constants { if (isset($groupShare['file_target'])) { $shareTmp['fileTarget'] = $groupShare['file_target']; } - $listOfUnsharedItems = array_merge($listOfUnsharedItems, array($groupShare)); + $listOfUnsharedItems = array_merge($listOfUnsharedItems, [$shareTmp]); $itemUnshared = true; } elseif (!$itemUnshared && isset($uniqueGroupShare)) { $query = \OC_DB::prepare('UPDATE `*PREFIX*share` SET `permissions` = ? WHERE `id` = ?'); @@ -1074,7 +1074,7 @@ class Share extends Constants { if (isset($uniqueGroupShare['file_target'])) { $shareTmp['fileTarget'] = $uniqueGroupShare['file_target']; } - $listOfUnsharedItems = array_merge($listOfUnsharedItems, array($uniqueGroupShare)); + $listOfUnsharedItems = array_merge($listOfUnsharedItems, [$shareTmp]); $itemUnshared = true; } diff --git a/lib/private/Template/JSConfigHelper.php b/lib/private/Template/JSConfigHelper.php new file mode 100644 index 00000000000..a7f8c251cee --- /dev/null +++ b/lib/private/Template/JSConfigHelper.php @@ -0,0 +1,243 @@ +<?php +/** + * @copyright Copyright (c) 2016, Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +namespace OC\Template; + +use bantu\IniGetWrapper\IniGetWrapper; +use OCP\App\IAppManager; +use OCP\IConfig; +use OCP\IGroupManager; +use OCP\IL10N; +use OCP\IURLGenerator; +use OCP\IUser; + +class JSConfigHelper { + + /** @var IL10N */ + private $l; + + /** @var \OC_Defaults */ + private $defaults; + + /** @var IAppManager */ + private $appManager; + + /** @var IUser */ + private $currentUser; + + /** @var IConfig */ + private $config; + + /** @var IGroupManager */ + private $groupManager; + + /** @var IniGetWrapper */ + private $iniWrapper; + + /** @var IURLGenerator */ + private $urlGenerator; + + /** + * @param IL10N $l + * @param \OC_Defaults $defaults + * @param IAppManager $appManager + * @param IUser|null $currentUser + * @param IConfig $config + * @param IGroupManager $groupManager + * @param IniGetWrapper $iniWrapper + * @param IURLGenerator $urlGenerator + */ + public function __construct(IL10N $l, + \OC_Defaults $defaults, + IAppManager $appManager, + $currentUser, + IConfig $config, + IGroupManager $groupManager, + IniGetWrapper $iniWrapper, + IURLGenerator $urlGenerator) { + $this->l = $l; + $this->defaults = $defaults; + $this->appManager = $appManager; + $this->currentUser = $currentUser; + $this->config = $config; + $this->groupManager = $groupManager; + $this->iniWrapper = $iniWrapper; + $this->urlGenerator = $urlGenerator; + } + + public function getConfig() { + + if ($this->currentUser !== null) { + $uid = $this->currentUser->getUID(); + } else { + $uid = null; + } + + // Get the config + $apps_paths = []; + + if ($this->currentUser === null) { + $apps = $this->appManager->getInstalledApps(); + } else { + $apps = $this->appManager->getEnabledAppsForUser($this->currentUser); + } + + foreach($apps as $app) { + $apps_paths[$app] = \OC_App::getAppWebPath($app); + } + + $defaultExpireDateEnabled = $this->config->getAppValue('core', 'shareapi_default_expire_date', 'no') === 'yes'; + $defaultExpireDate = $enforceDefaultExpireDate = null; + if ($defaultExpireDateEnabled) { + $defaultExpireDate = (int) $this->config->getAppValue('core', 'shareapi_expire_after_n_days', '7'); + $enforceDefaultExpireDate = $this->config->getAppValue('core', 'shareapi_enforce_expire_date', 'no') === 'yes'; + } + $outgoingServer2serverShareEnabled = $this->config->getAppValue('files_sharing', 'outgoing_server2server_share_enabled', 'yes') === 'yes'; + + $countOfDataLocation = 0; + $dataLocation = str_replace(\OC::$SERVERROOT .'/', '', $this->config->getSystemValue('datadirectory', ''), $countOfDataLocation); + if($countOfDataLocation !== 1 || !$this->groupManager->isAdmin($uid)) { + $dataLocation = false; + } + + $array = [ + "oc_debug" => $this->config->getSystemValue('debug', false) ? 'true' : 'false', + "oc_isadmin" => $this->groupManager->isAdmin($uid) ? 'true' : 'false', + "oc_dataURL" => is_string($dataLocation) ? "\"".$dataLocation."\"" : 'false', + "oc_webroot" => "\"".\OC::$WEBROOT."\"", + "oc_appswebroots" => str_replace('\\/', '/', json_encode($apps_paths)), // Ugly unescape slashes waiting for better solution + "datepickerFormatDate" => json_encode($this->l->l('jsdate', null)), + "dayNames" => json_encode([ + (string)$this->l->t('Sunday'), + (string)$this->l->t('Monday'), + (string)$this->l->t('Tuesday'), + (string)$this->l->t('Wednesday'), + (string)$this->l->t('Thursday'), + (string)$this->l->t('Friday'), + (string)$this->l->t('Saturday') + ]), + "dayNamesShort" => json_encode([ + (string)$this->l->t('Sun.'), + (string)$this->l->t('Mon.'), + (string)$this->l->t('Tue.'), + (string)$this->l->t('Wed.'), + (string)$this->l->t('Thu.'), + (string)$this->l->t('Fri.'), + (string)$this->l->t('Sat.') + ]), + "dayNamesMin" => json_encode([ + (string)$this->l->t('Su'), + (string)$this->l->t('Mo'), + (string)$this->l->t('Tu'), + (string)$this->l->t('We'), + (string)$this->l->t('Th'), + (string)$this->l->t('Fr'), + (string)$this->l->t('Sa') + ]), + "monthNames" => json_encode([ + (string)$this->l->t('January'), + (string)$this->l->t('February'), + (string)$this->l->t('March'), + (string)$this->l->t('April'), + (string)$this->l->t('May'), + (string)$this->l->t('June'), + (string)$this->l->t('July'), + (string)$this->l->t('August'), + (string)$this->l->t('September'), + (string)$this->l->t('October'), + (string)$this->l->t('November'), + (string)$this->l->t('December') + ]), + "monthNamesShort" => json_encode([ + (string)$this->l->t('Jan.'), + (string)$this->l->t('Feb.'), + (string)$this->l->t('Mar.'), + (string)$this->l->t('Apr.'), + (string)$this->l->t('May.'), + (string)$this->l->t('Jun.'), + (string)$this->l->t('Jul.'), + (string)$this->l->t('Aug.'), + (string)$this->l->t('Sep.'), + (string)$this->l->t('Oct.'), + (string)$this->l->t('Nov.'), + (string)$this->l->t('Dec.') + ]), + "firstDay" => json_encode($this->l->l('firstday', null)) , + "oc_config" => json_encode([ + 'session_lifetime' => min($this->config->getSystemValue('session_lifetime', $this->iniWrapper->getNumeric('session.gc_maxlifetime')), $this->iniWrapper->getNumeric('session.gc_maxlifetime')), + 'session_keepalive' => $this->config->getSystemValue('session_keepalive', true), + 'version' => implode('.', \OCP\Util::getVersion()), + 'versionstring' => \OC_Util::getVersionString(), + 'enable_avatars' => $this->config->getSystemValue('enable_avatars', true) === true, + 'lost_password_link'=> $this->config->getSystemValue('lost_password_link', null), + 'modRewriteWorking' => (getenv('front_controller_active') === 'true'), + ]), + "oc_appconfig" => json_encode([ + 'core' => [ + 'defaultExpireDateEnabled' => $defaultExpireDateEnabled, + 'defaultExpireDate' => $defaultExpireDate, + 'defaultExpireDateEnforced' => $enforceDefaultExpireDate, + 'enforcePasswordForPublicLink' => \OCP\Util::isPublicLinkPasswordRequired(), + 'sharingDisabledForUser' => \OCP\Util::isSharingDisabledForUser(), + 'resharingAllowed' => \OCP\Share::isResharingAllowed(), + 'remoteShareAllowed' => $outgoingServer2serverShareEnabled, + 'federatedCloudShareDoc' => $this->urlGenerator->linkToDocs('user-sharing-federated'), + 'allowGroupSharing' => \OC::$server->getShareManager()->allowGroupSharing() + ] + ]), + "oc_defaults" => json_encode([ + 'entity' => $this->defaults->getEntity(), + 'name' => $this->defaults->getName(), + 'title' => $this->defaults->getTitle(), + 'baseUrl' => $this->defaults->getBaseUrl(), + 'syncClientUrl' => $this->defaults->getSyncClientUrl(), + 'docBaseUrl' => $this->defaults->getDocBaseUrl(), + 'docPlaceholderUrl' => $this->defaults->buildDocLinkToKey('PLACEHOLDER'), + 'slogan' => $this->defaults->getSlogan(), + 'logoClaim' => $this->defaults->getLogoClaim(), + 'shortFooter' => $this->defaults->getShortFooter(), + 'longFooter' => $this->defaults->getLongFooter(), + 'folder' => \OC_Util::getTheme(), + ]), + ]; + + if ($this->currentUser !== null) { + $array['oc_userconfig'] = json_encode([ + 'avatar' => [ + 'version' => (int)$this->config->getUserValue($uid, 'avatar', 'version', 0), + ] + ]); + } + + // Allow hooks to modify the output values + \OC_Hook::emit('\OCP\Config', 'js', array('array' => &$array)); + + $result = ''; + + // Echo it + foreach ($array as $setting => $value) { + $result .= 'var '. $setting . '='. $value . ';' . PHP_EOL; + } + + return $result; + } +} diff --git a/lib/private/TemplateLayout.php b/lib/private/TemplateLayout.php index da845d80d04..9f89174e7f9 100644 --- a/lib/private/TemplateLayout.php +++ b/lib/private/TemplateLayout.php @@ -43,6 +43,7 @@ use Assetic\Filter\CssMinFilter; use Assetic\Filter\CssRewriteFilter; use Assetic\Filter\JSqueezeFilter; use Assetic\Filter\SeparatorFilter; +use OC\Template\JSConfigHelper; class TemplateLayout extends \OC_Template { @@ -142,7 +143,22 @@ class TemplateLayout extends \OC_Template { $jsFiles = self::findJavascriptFiles(\OC_Util::$scripts); $this->assign('jsfiles', array()); if ($this->config->getSystemValue('installed', false) && $renderAs != 'error') { - $this->append( 'jsfiles', \OC::$server->getURLGenerator()->linkToRoute('js_config', ['v' => self::$versionHash])); + if (\OC::$server->getContentSecurityPolicyNonceManager()->browserSupportsCspV3()) { + $jsConfigHelper = new JSConfigHelper( + \OC::$server->getL10N('core'), + \OC::$server->getThemingDefaults(), + \OC::$server->getAppManager(), + \OC::$server->getUserSession()->getUser(), + \OC::$server->getConfig(), + \OC::$server->getGroupManager(), + \OC::$server->getIniWrapper(), + \OC::$server->getURLGenerator() + ); + $this->assign('inline_ocjs', $jsConfigHelper->getConfig()); + $this->assign('foo', 'bar'); + } else { + $this->append('jsfiles', \OC::$server->getURLGenerator()->linkToRoute('core.OCJS.getConfig', ['v' => self::$versionHash])); + } } foreach($jsFiles as $info) { $web = $info[1]; diff --git a/lib/private/User/Session.php b/lib/private/User/Session.php index 4b56609ccfc..a213ee48c2a 100644 --- a/lib/private/User/Session.php +++ b/lib/private/User/Session.php @@ -362,6 +362,9 @@ class Session implements IUserSession, Emitter { $user = $this->manager->get($username); if (is_null($user)) { $users = $this->manager->getByEmail($username); + if (empty($users)) { + return false; + } if (count($users) !== 1) { return true; } diff --git a/lib/private/legacy/app.php b/lib/private/legacy/app.php index 5e05884f5c0..d25534aa822 100644 --- a/lib/private/legacy/app.php +++ b/lib/private/legacy/app.php @@ -334,9 +334,16 @@ class OC_App { * This function set an app as enabled in appconfig. */ public static function enable($app, $groups = null) { - self::$enabledAppsCache = array(); // flush + self::$enabledAppsCache = []; // flush if (!Installer::isInstalled($app)) { $app = self::installApp($app); + } else { + // check for required dependencies + $config = \OC::$server->getConfig(); + $l = \OC::$server->getL10N('core'); + $info = self::getAppInfo($app); + + self::checkAppDependencies($config, $l, $info); } $appManager = \OC::$server->getAppManager(); @@ -1186,16 +1193,7 @@ class OC_App { } // check for required dependencies - $dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l); - $missing = $dependencyAnalyzer->analyze($info); - if (!empty($missing)) { - $missingMsg = join(PHP_EOL, $missing); - throw new \Exception( - $l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s', - array($info['name'], $missingMsg) - ) - ); - } + self::checkAppDependencies($config, $l, $info); $config->setAppValue($app, 'enabled', 'yes'); if (isset($appData['id'])) { @@ -1438,4 +1436,23 @@ class OC_App { return $data; } + + /** + * @param $config + * @param $l + * @param $info + * @throws Exception + */ + protected static function checkAppDependencies($config, $l, $info) { + $dependencyAnalyzer = new DependencyAnalyzer(new Platform($config), $l); + $missing = $dependencyAnalyzer->analyze($info); + if (!empty($missing)) { + $missingMsg = join(PHP_EOL, $missing); + throw new \Exception( + $l->t('App "%s" cannot be installed because the following dependencies are not fulfilled: %s', + [$info['name'], $missingMsg] + ) + ); + } + } } diff --git a/lib/private/legacy/helper.php b/lib/private/legacy/helper.php index 0b9477dacd4..9c4bc895fb9 100644 --- a/lib/private/legacy/helper.php +++ b/lib/private/legacy/helper.php @@ -254,16 +254,9 @@ class OC_Helper { if ($path === false) { $path = getenv("PATH"); } - // check method depends on operating system - if (!strncmp(PHP_OS, "WIN", 3)) { - // on Windows an appropriate COM or EXE file needs to exist - $exts = array(".exe", ".com"); - $check_fn = "file_exists"; - } else { - // anywhere else we look for an executable file of that name - $exts = array(""); - $check_fn = "is_executable"; - } + // we look for an executable file of that name + $exts = [""]; + $check_fn = "is_executable"; // Default check will be done with $path directories : $dirs = explode(PATH_SEPARATOR, $path); // WARNING : We have to check if open_basedir is enabled : @@ -498,7 +491,6 @@ class OC_Helper { /** * Try to find a program - * Note: currently windows is not supported * * @param string $program * @return null|string @@ -557,7 +549,7 @@ class OC_Helper { $quota = \OCP\Files\FileInfo::SPACE_UNLIMITED; $storage = $rootInfo->getStorage(); $sourceStorage = $storage; - if ($storage->instanceOfStorage('\OC\Files\Storage\Shared')) { + if ($storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) { $includeExtStorage = false; $sourceStorage = $storage->getSourceStorage(); } diff --git a/lib/private/legacy/image.php b/lib/private/legacy/image.php index fee1a805c40..5403cccd026 100644 --- a/lib/private/legacy/image.php +++ b/lib/private/legacy/image.php @@ -54,6 +54,8 @@ class OC_Image implements \OCP\IImage { private $fileInfo; /** @var \OCP\ILogger */ private $logger; + /** @var array */ + private $exif; /** * Get mime type for an image file. @@ -347,6 +349,10 @@ class OC_Image implements \OCP\IImage { * @return int The orientation or -1 if no EXIF data is available. */ public function getOrientation() { + if ($this->exif !== null) { + return $this->exif['Orientation']; + } + if ($this->imageType !== IMAGETYPE_JPEG) { $this->logger->debug('OC_Image->fixOrientation() Image is not a JPEG.', array('app' => 'core')); return -1; @@ -370,9 +376,30 @@ class OC_Image implements \OCP\IImage { if (!isset($exif['Orientation'])) { return -1; } + $this->exif = $exif; return $exif['Orientation']; } + public function readExif($data) { + if (!is_callable('exif_read_data')) { + $this->logger->debug('OC_Image->fixOrientation() Exif module not enabled.', array('app' => 'core')); + return; + } + if (!$this->valid()) { + $this->logger->debug('OC_Image->fixOrientation() No image loaded.', array('app' => 'core')); + return; + } + + $exif = @exif_read_data('data://image/jpeg;base64,' . base64_encode($data)); + if (!$exif) { + return; + } + if (!isset($exif['Orientation'])) { + return; + } + $this->exif = $exif; + } + /** * (I'm open for suggestions on better method name ;) * Fixes orientation based on EXIF data. diff --git a/lib/private/legacy/response.php b/lib/private/legacy/response.php index 0ec27251ba5..69c84e2df68 100644 --- a/lib/private/legacy/response.php +++ b/lib/private/legacy/response.php @@ -33,6 +33,7 @@ class OC_Response { const STATUS_NOT_MODIFIED = 304; const STATUS_TEMPORARY_REDIRECT = 307; const STATUS_BAD_REQUEST = 400; + const STATUS_FORBIDDEN = 403; const STATUS_NOT_FOUND = 404; const STATUS_INTERNAL_SERVER_ERROR = 500; const STATUS_SERVICE_UNAVAILABLE = 503; @@ -246,7 +247,7 @@ class OC_Response { * @see \OCP\AppFramework\Http\Response::getHeaders */ $policy = 'default-src \'self\'; ' - . 'script-src \'self\' \'unsafe-eval\'; ' + . 'script-src \'self\' \'unsafe-eval\' \'nonce-'.\OC::$server->getContentSecurityPolicyNonceManager()->getNonce().'\'; ' . 'style-src \'self\' \'unsafe-inline\'; ' . 'frame-src *; ' . 'img-src * data: blob:; ' diff --git a/lib/private/legacy/template.php b/lib/private/legacy/template.php index 67a8b3ec6e1..7c0b58db0c0 100644 --- a/lib/private/legacy/template.php +++ b/lib/private/legacy/template.php @@ -117,7 +117,7 @@ class OC_Template extends \OC\Template\Base { OC_Util::addStyle("fonts",null,true); OC_Util::addStyle("icons",null,true); OC_Util::addStyle("header",null,true); - OC_Util::addStyle("inputs",null,true); + OC_Util::addStyle("inputs"); OC_Util::addStyle("styles",null,true); // avatars @@ -126,6 +126,10 @@ class OC_Template extends \OC\Template\Base { \OC_Util::addScript('placeholder', null, true); } + OC_Util::addVendorScript('select2/select2'); + OC_Util::addVendorStyle('select2/select2', null, true); + OC_Util::addScript('select2-toggleselect'); + OC_Util::addScript('oc-backbone', null, true); OC_Util::addVendorScript('core', 'backbone/backbone', true); OC_Util::addVendorScript('snapjs/dist/latest/snap', null, true); @@ -135,6 +139,7 @@ class OC_Template extends \OC\Template\Base { OC_Util::addScript("oc-requesttoken", null, true); OC_Util::addScript('search', 'search', true); OC_Util::addScript("config", null, true); + OC_Util::addScript("public/appconfig", null, true); OC_Util::addScript("eventsource", null, true); OC_Util::addScript("octemplate", null, true); OC_Util::addTranslations("core", null, true); @@ -143,7 +148,6 @@ class OC_Template extends \OC\Template\Base { OC_Util::addScript("oc-dialogs", null, true); OC_Util::addScript("jquery.ocdialog", null, true); OC_Util::addStyle("jquery.ocdialog"); - OC_Util::addScript("compatibility", null, true); OC_Util::addScript('files/fileinfo'); OC_Util::addScript('files/client'); diff --git a/lib/private/legacy/util.php b/lib/private/legacy/util.php index b8f3a93ba50..42fd0ba7db3 100644 --- a/lib/private/legacy/util.php +++ b/lib/private/legacy/util.php @@ -165,15 +165,14 @@ class OC_Util { // install storage availability wrapper, before most other wrappers \OC\Files\Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, $storage) { - /** @var \OCP\Files\Storage $storage */ - if (!$storage->instanceOfStorage('\OC\Files\Storage\Shared') && !$storage->isLocal()) { + if (!$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) { return new \OC\Files\Storage\Wrapper\Availability(['storage' => $storage]); } return $storage; }); \OC\Files\Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, \OCP\Files\Storage $storage, \OCP\Files\Mount\IMountPoint $mount) { - if ($mount->getOption('encoding_compatibility', false) && !$storage->instanceOfStorage('\OC\Files\Storage\Shared') && !$storage->isLocal()) { + if ($mount->getOption('encoding_compatibility', false) && !$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage') && !$storage->isLocal()) { return new \OC\Files\Storage\Wrapper\Encoding(['storage' => $storage]); } return $storage; @@ -668,15 +667,6 @@ class OC_Util { $webServerRestart = true; } - // Check if server running on Windows platform - if(OC_Util::runningOnWindows()) { - $errors[] = [ - 'error' => $l->t('Microsoft Windows Platform is not supported'), - 'hint' => $l->t('Running Nextcloud Server on the Microsoft Windows platform is not supported. We suggest you ' . - 'use a Linux server in a virtual machine if you have no option for migrating the server itself.') - ]; - } - // Check if config folder is writable. if(!OC_Helper::isReadOnlyConfigEnabled()) { if (!is_writable(OC::$configDir) or !is_readable(OC::$configDir)) { @@ -1269,15 +1259,6 @@ class OC_Util { } /** - * Checks whether the server is running on Windows - * - * @return bool true if running on Windows, false otherwise - */ - public static function runningOnWindows() { - return (substr(PHP_OS, 0, 3) === "WIN"); - } - - /** * Checks whether the server is running on Mac OS X * * @return bool true if running on Mac OS X, false otherwise |