diff options
Diffstat (limited to 'lib/private')
-rw-r--r-- | lib/private/app/codechecker.php | 130 | ||||
-rw-r--r-- | lib/private/app/codecheckvisitor.php | 111 | ||||
-rw-r--r-- | lib/private/defaults.php | 3 | ||||
-rw-r--r-- | lib/private/installer.php | 60 | ||||
-rw-r--r-- | lib/private/memcache/arraycache.php | 71 | ||||
-rw-r--r-- | lib/private/memcache/factory.php | 2 | ||||
-rw-r--r-- | lib/private/security/securerandom.php | 3 | ||||
-rw-r--r-- | lib/private/user.php | 2 |
8 files changed, 326 insertions, 56 deletions
diff --git a/lib/private/app/codechecker.php b/lib/private/app/codechecker.php new file mode 100644 index 00000000000..dbec53579a8 --- /dev/null +++ b/lib/private/app/codechecker.php @@ -0,0 +1,130 @@ +<?php +/** + * Copyright (c) 2015 Thomas Müller <deepdiver@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\App; + +use OC\Hooks\BasicEmitter; +use PhpParser\Lexer; +use PhpParser\Node; +use PhpParser\Node\Name; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitorAbstract; +use PhpParser\Parser; +use RecursiveCallbackFilterIterator; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use RegexIterator; +use SplFileInfo; + +class CodeChecker extends BasicEmitter { + + const CLASS_EXTENDS_NOT_ALLOWED = 1000; + const CLASS_IMPLEMENTS_NOT_ALLOWED = 1001; + const STATIC_CALL_NOT_ALLOWED = 1002; + const CLASS_CONST_FETCH_NOT_ALLOWED = 1003; + const CLASS_NEW_FETCH_NOT_ALLOWED = 1004; + + /** @var Parser */ + private $parser; + + /** @var string[] */ + private $blackListedClassNames; + + public function __construct() { + $this->parser = new Parser(new Lexer); + $this->blackListedClassNames = [ + // classes replaced by the public api + 'OC_API', + 'OC_App', + 'OC_AppConfig', + 'OC_Avatar', + 'OC_BackgroundJob', + 'OC_Config', + 'OC_DB', + 'OC_Files', + 'OC_Helper', + 'OC_Hook', + 'OC_Image', + 'OC_JSON', + 'OC_L10N', + 'OC_Log', + 'OC_Mail', + 'OC_Preferences', + 'OC_Request', + 'OC_Response', + 'OC_Template', + 'OC_User', + 'OC_Util', + ]; + } + + /** + * @param string $appId + * @return array + */ + public function analyse($appId) { + $appPath = \OC_App::getAppPath($appId); + if ($appPath === false) { + throw new \RuntimeException("No app with given id <$appId> known."); + } + + return $this->analyseFolder($appPath); + } + + /** + * @param string $folder + * @return array + */ + public function analyseFolder($folder) { + $errors = []; + + $excludes = array_map(function($item) use ($folder) { + return $folder . '/' . $item; + }, ['vendor', '3rdparty', '.git', 'l10n']); + + $iterator = new RecursiveDirectoryIterator($folder, RecursiveDirectoryIterator::SKIP_DOTS); + $iterator = new RecursiveCallbackFilterIterator($iterator, function($item) use ($folder, $excludes){ + /** @var SplFileInfo $item */ + foreach($excludes as $exclude) { + if (substr($item->getPath(), 0, strlen($exclude)) === $exclude) { + return false; + } + } + return true; + }); + $iterator = new RecursiveIteratorIterator($iterator); + $iterator = new RegexIterator($iterator, '/^.+\.php$/i'); + + foreach ($iterator as $file) { + /** @var SplFileInfo $file */ + $this->emit('CodeChecker', 'analyseFileBegin', [$file->getPathname()]); + $errors = array_merge($this->analyseFile($file), $errors); + $this->emit('CodeChecker', 'analyseFileFinished', [$errors]); + } + + return $errors; + } + + + /** + * @param string $file + * @return array + */ + public function analyseFile($file) { + $code = file_get_contents($file); + $statements = $this->parser->parse($code); + + $visitor = new CodeCheckVisitor($this->blackListedClassNames); + $traverser = new NodeTraverser; + $traverser->addVisitor($visitor); + + $traverser->traverse($statements); + + return $visitor->errors; + } +} diff --git a/lib/private/app/codecheckvisitor.php b/lib/private/app/codecheckvisitor.php new file mode 100644 index 00000000000..939c905bcf6 --- /dev/null +++ b/lib/private/app/codecheckvisitor.php @@ -0,0 +1,111 @@ +<?php +/** + * Copyright (c) 2015 Thomas Müller <deepdiver@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\App; + +use OC\Hooks\BasicEmitter; +use PhpParser\Lexer; +use PhpParser\Node; +use PhpParser\Node\Name; +use PhpParser\NodeTraverser; +use PhpParser\NodeVisitorAbstract; +use PhpParser\Parser; +use RecursiveCallbackFilterIterator; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use RegexIterator; +use SplFileInfo; + +class CodeCheckVisitor extends NodeVisitorAbstract { + + public function __construct($blackListedClassNames) { + $this->blackListedClassNames = array_map('strtolower', $blackListedClassNames); + } + + public $errors = []; + + public function enterNode(Node $node) { + if ($node instanceof Node\Stmt\Class_) { + if (!is_null($node->extends)) { + $this->checkBlackList($node->extends->toString(), CodeChecker::CLASS_EXTENDS_NOT_ALLOWED, $node); + } + foreach ($node->implements as $implements) { + $this->checkBlackList($implements->toString(), CodeChecker::CLASS_IMPLEMENTS_NOT_ALLOWED, $node); + } + } + if ($node instanceof Node\Expr\StaticCall) { + if (!is_null($node->class)) { + if ($node->class instanceof Name) { + $this->checkBlackList($node->class->toString(), CodeChecker::STATIC_CALL_NOT_ALLOWED, $node); + } + if ($node->class instanceof Node\Expr\Variable) { + /** + * TODO: find a way to detect something like this: + * $c = "OC_API"; + * $n = $i::call(); + */ + } + } + } + if ($node instanceof Node\Expr\ClassConstFetch) { + if (!is_null($node->class)) { + if ($node->class instanceof Name) { + $this->checkBlackList($node->class->toString(), CodeChecker::CLASS_CONST_FETCH_NOT_ALLOWED, $node); + } + if ($node->class instanceof Node\Expr\Variable) { + /** + * TODO: find a way to detect something like this: + * $c = "OC_API"; + * $n = $i::ADMIN_AUTH; + */ + } + } + } + if ($node instanceof Node\Expr\New_) { + if (!is_null($node->class)) { + if ($node->class instanceof Name) { + $this->checkBlackList($node->class->toString(), CodeChecker::CLASS_NEW_FETCH_NOT_ALLOWED, $node); + } + if ($node->class instanceof Node\Expr\Variable) { + /** + * TODO: find a way to detect something like this: + * $c = "OC_API"; + * $n = new $i; + */ + } + } + } + } + + private function checkBlackList($name, $errorCode, Node $node) { + if (in_array(strtolower($name), $this->blackListedClassNames)) { + $this->errors[]= [ + 'disallowedToken' => $name, + 'errorCode' => $errorCode, + 'line' => $node->getLine(), + 'reason' => $this->buildReason($name, $errorCode) + ]; + } + } + + private function buildReason($name, $errorCode) { + static $errorMessages= [ + CodeChecker::CLASS_EXTENDS_NOT_ALLOWED => "used as base class", + CodeChecker::CLASS_IMPLEMENTS_NOT_ALLOWED => "used as interface", + CodeChecker::STATIC_CALL_NOT_ALLOWED => "static method call on private class", + CodeChecker::CLASS_CONST_FETCH_NOT_ALLOWED => "used to fetch a const from", + CodeChecker::CLASS_NEW_FETCH_NOT_ALLOWED => "is instanciated", + ]; + + if (isset($errorMessages[$errorCode])) { + return $errorMessages[$errorCode]; + } + + return "$name usage not allowed - error: $errorCode"; + } +} diff --git a/lib/private/defaults.php b/lib/private/defaults.php index dfcd97aedd6..a3902ee80de 100644 --- a/lib/private/defaults.php +++ b/lib/private/defaults.php @@ -215,7 +215,8 @@ class OC_Defaults { if ($this->themeExist('getShortFooter')) { $footer = $this->theme->getShortFooter(); } else { - $footer = '<a href="'. $this->getBaseUrl() . '" target="_blank">' .$this->getEntity() . '</a>'. + $footer = '<a href="'. $this->getBaseUrl() . '" target="_blank"' . + ' rel="noreferrer">' .$this->getEntity() . '</a>'. ' – ' . $this->getSlogan(); } diff --git a/lib/private/installer.php b/lib/private/installer.php index db8f27aeeab..aeac3497fd7 100644 --- a/lib/private/installer.php +++ b/lib/private/installer.php @@ -308,7 +308,7 @@ class OC_Installer{ } $info=OC_App::getAppInfo($extractDir.'/appinfo/info.xml', true); // check the code for not allowed calls - if(!$isShipped && !OC_Installer::checkCode($info['id'], $extractDir)) { + if(!$isShipped && !OC_Installer::checkCode($extractDir)) { OC_Helper::rmdirr($extractDir); throw new \Exception($l->t("App can't be installed because of not allowed code in the App")); } @@ -511,7 +511,7 @@ class OC_Installer{ OC_Appconfig::setValue($app, 'ocsid', $info['ocsid']); } - //set remote/public handelers + //set remote/public handlers foreach($info['remote'] as $name=>$path) { OCP\CONFIG::setAppValue('core', 'remote_'.$name, $app.'/'.$path); } @@ -529,58 +529,16 @@ class OC_Installer{ * @param string $folder the folder of the app to check * @return boolean true for app is o.k. and false for app is not o.k. */ - public static function checkCode($appname, $folder) { - $blacklist=array( - // classes replaced by the public api - 'OC_API::', - 'OC_App::', - 'OC_AppConfig::', - 'OC_Avatar', - 'OC_BackgroundJob::', - 'OC_Config::', - 'OC_DB::', - 'OC_Files::', - 'OC_Helper::', - 'OC_Hook::', - 'OC_Image::', - 'OC_JSON::', - 'OC_L10N::', - 'OC_Log::', - 'OC_Mail::', - 'OC_Request::', - 'OC_Response::', - 'OC_Template::', - 'OC_User::', - 'OC_Util::', - ); + public static function checkCode($folder) { // is the code checker enabled? - if(OC_Config::getValue('appcodechecker', false)) { - // check if grep is installed - $grep = \OC_Helper::findBinaryPath('grep'); - if (!$grep) { - OC_Log::write('core', - 'grep not installed. So checking the code of the app "'.$appname.'" was not possible', - OC_Log::ERROR); - return true; - } - - // iterate the bad patterns - foreach($blacklist as $bl) { - $cmd = 'grep --include \\*.php -ri '.escapeshellarg($bl).' '.$folder.''; - $result = exec($cmd); - // bad pattern found - if($result<>'') { - OC_Log::write('core', - 'App "'.$appname.'" is using a not allowed call "'.$bl.'". Installation refused.', - OC_Log::ERROR); - return false; - } - } - return true; - - }else{ + if(!OC_Config::getValue('appcodechecker', false)) { return true; } + + $codeChecker = new \OC\App\CodeChecker(); + $errors = $codeChecker->analyseFolder($folder); + + return empty($errors); } } diff --git a/lib/private/memcache/arraycache.php b/lib/private/memcache/arraycache.php new file mode 100644 index 00000000000..9456c0f80c6 --- /dev/null +++ b/lib/private/memcache/arraycache.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright (c) 2015 Joas Schilling <nickvergessen@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace OC\Memcache; + +class ArrayCache extends Cache { + /** @var array Array with the cached data */ + protected $cachedData = array(); + + /** + * {@inheritDoc} + */ + public function get($key) { + if ($this->hasKey($key)) { + return $this->cachedData[$key]; + } + return null; + } + + /** + * {@inheritDoc} + */ + public function set($key, $value, $ttl = 0) { + $this->cachedData[$key] = $value; + return true; + } + + /** + * {@inheritDoc} + */ + public function hasKey($key) { + return isset($this->cachedData[$key]); + } + + /** + * {@inheritDoc} + */ + public function remove($key) { + unset($this->cachedData[$key]); + return true; + } + + /** + * {@inheritDoc} + */ + public function clear($prefix = '') { + if ($prefix === '') { + $this->cachedData = []; + return true; + } + + foreach ($this->cachedData as $key => $value) { + if (strpos($key, $prefix) === 0) { + $this->remove($key); + } + } + return true; + } + + /** + * {@inheritDoc} + */ + static public function isAvailable() { + return true; + } +} diff --git a/lib/private/memcache/factory.php b/lib/private/memcache/factory.php index 1e663eecfe1..e8a91c52269 100644 --- a/lib/private/memcache/factory.php +++ b/lib/private/memcache/factory.php @@ -42,7 +42,7 @@ class Factory implements ICacheFactory { } elseif (Memcached::isAvailable()) { return new Memcached($prefix); } else { - return new Null($prefix); + return new ArrayCache($prefix); } } diff --git a/lib/private/security/securerandom.php b/lib/private/security/securerandom.php index 2402e863fb0..b1169bff289 100644 --- a/lib/private/security/securerandom.php +++ b/lib/private/security/securerandom.php @@ -64,8 +64,7 @@ class SecureRandom implements ISecureRandom { * Generate a random string of specified length. * @param string $length The length of the generated string * @param string $characters An optional list of characters to use if no characterlist is - * specified 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./ - * is used. + * specified all valid base64 characters are used. * @return string * @throws \Exception If the generator is not initialized. */ diff --git a/lib/private/user.php b/lib/private/user.php index d1fedffcaaf..10457c224f2 100644 --- a/lib/private/user.php +++ b/lib/private/user.php @@ -366,7 +366,7 @@ class OC_User { return $backend->getLogoutAttribute(); } - return 'href="' . link_to('', 'index.php') . '?logout=true&requesttoken=' . OC_Util::callRegister() . '"'; + return 'href="' . link_to('', 'index.php') . '?logout=true&requesttoken=' . urlencode(OC_Util::callRegister()) . '"'; } /** |