diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/AssetManager/UIAssetMerger/StylesheetUIAssetMerger.php | 7 | ||||
-rw-r--r-- | core/DataTable/Filter/ColumnDelete.php | 62 | ||||
-rw-r--r-- | core/Exception/StylesheetLessCompileException.php | 13 | ||||
-rw-r--r-- | core/FileIntegrity.php | 288 | ||||
-rw-r--r-- | core/Filechecks.php | 110 | ||||
-rw-r--r-- | core/FrontController.php | 30 | ||||
-rw-r--r-- | core/Http.php | 4 | ||||
-rw-r--r-- | core/SettingsPiwik.php | 4 | ||||
-rw-r--r-- | core/Updates/3.0.1-b1.php | 24 | ||||
-rw-r--r-- | core/Version.php | 2 |
10 files changed, 409 insertions, 135 deletions
diff --git a/core/AssetManager/UIAssetMerger/StylesheetUIAssetMerger.php b/core/AssetManager/UIAssetMerger/StylesheetUIAssetMerger.php index de32191b6b..3d5cad72b6 100644 --- a/core/AssetManager/UIAssetMerger/StylesheetUIAssetMerger.php +++ b/core/AssetManager/UIAssetMerger/StylesheetUIAssetMerger.php @@ -13,6 +13,7 @@ use lessc; use Piwik\AssetManager\UIAsset; use Piwik\AssetManager\UIAssetMerger; use Piwik\Common; +use Piwik\Exception\StylesheetLessCompileException; use Piwik\Piwik; class StylesheetUIAssetMerger extends UIAssetMerger @@ -41,7 +42,11 @@ class StylesheetUIAssetMerger extends UIAssetMerger $concatenatedAssets = $this->getConcatenatedAssets(); $this->lessCompiler->setFormatter('classic'); - $compiled = $this->lessCompiler->compile($concatenatedAssets); + try { + $compiled = $this->lessCompiler->compile($concatenatedAssets); + } catch(\Exception $e) { + throw new StylesheetLessCompileException($e->getMessage()); + } foreach ($this->cssAssetsToReplace as $asset) { // to fix #10173 diff --git a/core/DataTable/Filter/ColumnDelete.php b/core/DataTable/Filter/ColumnDelete.php index 60e53fd7dd..37f0b1bf97 100644 --- a/core/DataTable/Filter/ColumnDelete.php +++ b/core/DataTable/Filter/ColumnDelete.php @@ -101,25 +101,8 @@ class ColumnDelete extends BaseFilter // remove columns specified in $this->columnsToRemove if (!empty($this->columnsToRemove)) { - foreach ($table as $index => $row) { - foreach ($this->columnsToRemove as $column) { - if (!array_key_exists($column, $row)) { - continue; - } - - if ($this->deleteIfZeroOnly) { - $value = $row[$column]; - if ($value === false || !empty($value)) { - continue; - } - } - - unset($table[$index][$column]); - } - } - + $this->removeColumnsFromTable($table); $recurse = true; - } // remove columns not specified in $columnsToKeep @@ -128,6 +111,7 @@ class ColumnDelete extends BaseFilter $columnsToDelete = array(); foreach ($row as $name => $value) { $keep = false; + // @see self::APPEND_TO_COLUMN_NAME_TO_KEEP foreach ($this->columnsToKeep as $nameKeep => $true) { if (strpos($name, $nameKeep . self::APPEND_TO_COLUMN_NAME_TO_KEEP) === 0) { @@ -161,4 +145,46 @@ class ColumnDelete extends BaseFilter return $table; } + + /** + * @param $table + * @return array + */ + protected function removeColumnsFromTable(&$table) + { + if(!$this->isArrayAccess($table)) { + return; + } + foreach ($table as $index => &$row) { + if(!$this->isArrayAccess($row)) { + continue; + } + foreach ($this->columnsToRemove as $column) { + + if (!array_key_exists($column, $row)) { + continue; + } + + if ($this->deleteIfZeroOnly) { + $value = $row[$column]; + if ($value === false || !empty($value)) { + continue; + } + } + + unset($table[$index][$column]); + } + + $this->removeColumnsFromTable($row); + } + } + + /** + * @param $table + * @return bool + */ + protected function isArrayAccess(&$table) + { + return is_array($table) || $table instanceof \ArrayAccess; + } } diff --git a/core/Exception/StylesheetLessCompileException.php b/core/Exception/StylesheetLessCompileException.php new file mode 100644 index 0000000000..3e3124eb77 --- /dev/null +++ b/core/Exception/StylesheetLessCompileException.php @@ -0,0 +1,13 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + */ +namespace Piwik\Exception; + +class StylesheetLessCompileException extends Exception +{ +} diff --git a/core/FileIntegrity.php b/core/FileIntegrity.php new file mode 100644 index 0000000000..b457f1d05f --- /dev/null +++ b/core/FileIntegrity.php @@ -0,0 +1,288 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + */ + +namespace Piwik; + +use Piwik\Plugins\CustomPiwikJs\Exception\AccessDeniedException; +use Piwik\Plugins\CustomPiwikJs\TrackerUpdater; + +class FileIntegrity +{ + + /** + * Get file integrity information + * + * @return array(bool $success, array $messages) + */ + public static function getFileIntegrityInformation() + { + $messages = array(); + + $manifest = PIWIK_INCLUDE_PATH . '/config/manifest.inc.php'; + + if (file_exists($manifest)) { + require_once $manifest; + } + + if (!class_exists('Piwik\\Manifest')) { + $messages[] = Piwik::translate('General_WarningFileIntegrityNoManifest') + . '<br/>' + . Piwik::translate('General_WarningFileIntegrityNoManifestDeployingFromGit'); + + return array( + $success = false, + $messages + ); + } + + $messages = self::getMessagesFilesFoundButNotExpected($messages); + + $messages = self::getMessagesFilesMismatch($messages); + + return array( + $success = empty($messages), + $messages + ); + } + + protected static function getFilesNotInManifestButExpectedAnyway() + { + return array( + '*/.htaccess', + '*/web.config', + 'bootstrap.php', + 'favicon.ico', + 'robots.txt', + 'config/config.ini.php', + 'config/common.ini.php', + 'config/*.config.ini.php', + 'config/manifest.inc.php', + 'misc/*.dat', + 'misc/*.dat.gz', + 'misc/user/*png', + 'misc/package/WebAppGallery/*.xml', + 'misc/package/WebAppGallery/install.sql', + 'vendor/autoload.php', + 'vendor/composer/autoload_real.php', + 'tmp/*', + ); + } + + + /** + * @param $messages + * @return array + */ + protected static function getMessagesFilesFoundButNotExpected($messages) + { + $filesFoundButNotExpected = self::getFilesFoundButNotExpected(); + if (count($filesFoundButNotExpected) > 0) { + + $messageFilesToDelete = ''; + foreach ($filesFoundButNotExpected as $fileFoundNotExpected) { + $messageFilesToDelete .= Piwik::translate('General_ExceptionFileToDelete', $fileFoundNotExpected) . '<br/>'; + } + $messages[] = Piwik::translate('General_ExceptionUnexpectedFile') + . '<br/>' + . '--> ' . Piwik::translate('General_ExceptionUnexpectedFilePleaseDelete') . ' <--' + . '<br/><br/>' + . $messageFilesToDelete + . '<br/>'; + return $messages; + + } + return $messages; + } + + /** + * Look for files which are in the filesystem, but should not be + * + * @return array + */ + protected static function getFilesFoundButNotExpected() + { + $files = \Piwik\Manifest::$files; + $pluginsInManifest = self::getPluginsFoundInManifest(); + + $filesFoundButNotExpected = array(); + + $filesToInvestigate = array_merge( + // all normal files + Filesystem::globr('.', '*'), + // all hidden files + Filesystem::globr('.', '.*') + ); + foreach ($filesToInvestigate as $file) { + if (is_dir($file)) { + continue; + } + $file = substr($file, 2); // remove starting characters ./ to match format in manifest.inc.php + + if (self::isFileFromPluginNotInManifest($file, $pluginsInManifest)) { + continue; + } + if (self::isFileNotInManifestButExpectedAnyway($file)) { + continue; + } + + if (!isset($files[$file])) { + $filesFoundButNotExpected[] = $file; + } + } + + return $filesFoundButNotExpected; + } + + + protected static function getPluginsFoundInManifest() + { + $files = \Piwik\Manifest::$files; + + $pluginsInManifest = array(); + foreach($files as $file => $manifestIntegrityInfo) { + if(strpos($file, 'plugins/') === 0) { + $pluginName = self::getPluginNameFromFilepath($file); + $pluginsInManifest[] = $pluginName; + } + } + return $pluginsInManifest; + } + + /** + * If a plugin folder is not tracked in the manifest then we don't try to report any files in this folder + * Could be a third party plugin or any plugin from the Marketplace + * + * @param $file + * @param $pluginsInManifest + * @return bool + */ + protected static function isFileFromPluginNotInManifest($file, $pluginsInManifest) + { + if (strpos($file, 'plugins/') !== 0) { + return false; + } + + if (substr_count($file, '/') < 2) { + // must be a file plugins/abc.xyz and not a plugin directory + return false; + } + + $pluginName = self::getPluginNameFromFilepath($file); + if(in_array($pluginName, $pluginsInManifest)) { + return false; + } + + return true; + } + + protected static function isFileNotInManifestButExpectedAnyway($file) + { + $expected = self::getFilesNotInManifestButExpectedAnyway(); + foreach ($expected as $expectedPattern) { + if (fnmatch($expectedPattern, $file)) { + return true; + } + } + return false; + } + + protected static function getMessagesFilesMismatch($messages) + { + $messagesMismatch = array(); + $hasMd5file = function_exists('md5_file'); + $files = \Piwik\Manifest::$files; + $hasMd5 = function_exists('md5'); + foreach ($files as $path => $props) { + $file = PIWIK_INCLUDE_PATH . '/' . $path; + + if (!file_exists($file) || !is_readable($file)) { + $messagesMismatch[] = Piwik::translate('General_ExceptionMissingFile', $file); + } elseif (filesize($file) != $props[0]) { + + if (self::isModifiedPathValid($path)) { + continue; + } + + if (!$hasMd5 || in_array(substr($path, -4), array('.gif', '.ico', '.jpg', '.png', '.swf'))) { + // files that contain binary data (e.g., images) must match the file size + $messagesMismatch[] = Piwik::translate('General_ExceptionFilesizeMismatch', array($file, $props[0], filesize($file))); + } else { + // convert end-of-line characters and re-test text files + $content = @file_get_contents($file); + $content = str_replace("\r\n", "\n", $content); + if ((strlen($content) != $props[0]) + || (@md5($content) !== $props[1]) + ) { + $messagesMismatch[] = Piwik::translate('General_ExceptionFilesizeMismatch', array($file, $props[0], filesize($file))); + } + } + } elseif ($hasMd5file && (@md5_file($file) !== $props[1])) { + if (self::isModifiedPathValid($path)) { + continue; + } + + $messagesMismatch[] = Piwik::translate('General_ExceptionFileIntegrity', $file); + } + } + + if (!$hasMd5file) { + $messages[] = Piwik::translate('General_WarningFileIntegrityNoMd5file'); + } + + if (!empty($messagesMismatch)) { + $messages[] = Piwik::translate('General_FileIntegrityWarningReupload'); + $messages[] = Piwik::translate('General_FileIntegrityWarningReuploadBis') . '<br/>'; + $messages = array_merge($messages, $messagesMismatch); + } + + return $messages; + } + + protected static function isModifiedPathValid($path) + { + if ($path === 'piwik.js') { + // we could have used a postEvent hook to enrich "\Piwik\Manifest::$files;" which would also benefit plugins + // that want to check for file integrity but we do not want to risk to break anything right now. It is not + // as trivial because piwik.js might be already updated, or updated on the next request. We cannot define + // 2 or 3 different filesizes and md5 hashes for one file so we check it here. + + if (Plugin\Manager::getInstance()->isPluginActivated('CustomPiwikJs')) { + $trackerUpdater = new TrackerUpdater(); + + if ($trackerUpdater->getCurrentTrackerFileContent() === $trackerUpdater->getUpdatedTrackerFileContent()) { + // file was already updated, eg manually or via custom piwik.js, this is a valid piwik.js file as + // it was enriched by tracker plugins + return true; + } + + try { + // the piwik.js tracker file was not updated yet, but may be updated just after the update by + // one of the events CustomPiwikJs is listening to or by a scheduled task. + // In this case, we check whether such an update will succeed later and if it will, the file is + // valid as well as it will be updated on the next request + $trackerUpdater->checkWillSucceed(); + return true; + } catch (AccessDeniedException $e) { + return false; + } + + } + } + + return false; + } + + protected static function getPluginNameFromFilepath($file) + { + $pathRelativeToPlugins = substr($file, strlen('plugins/')); + $pluginName = substr($pathRelativeToPlugins, 0, strpos($pathRelativeToPlugins, '/')); + return $pluginName; + } + +}
\ No newline at end of file diff --git a/core/Filechecks.php b/core/Filechecks.php index 33e65c9055..5f98fd228d 100644 --- a/core/Filechecks.php +++ b/core/Filechecks.php @@ -9,8 +9,6 @@ namespace Piwik; use Piwik\Exception\MissingFilePermissionException; -use Piwik\Plugins\CustomPiwikJs\Exception\AccessDeniedException; -use Piwik\Plugins\CustomPiwikJs\TrackerUpdater; class Filechecks { @@ -104,112 +102,6 @@ class Filechecks throw $ex; } - private static function isModifiedPathValid($path) - { - if ($path === 'piwik.js') { - // we could have used a postEvent hook to enrich "\Piwik\Manifest::$files;" which would also benefit plugins - // that want to check for file integrity but we do not want to risk to break anything right now. It is not - // as trivial because piwik.js might be already updated, or updated on the next request. We cannot define - // 2 or 3 different filesizes and md5 hashes for one file so we check it here. - - if (Plugin\Manager::getInstance()->isPluginActivated('CustomPiwikJs')) { - $trackerUpdater = new TrackerUpdater(); - - if ($trackerUpdater->getCurrentTrackerFileContent() === $trackerUpdater->getUpdatedTrackerFileContent()) { - // file was already updated, eg manually or via custom piwik.js, this is a valid piwik.js file as - // it was enriched by tracker plugins - return true; - } - - try { - // the piwik.js tracker file was not updated yet, but may be updated just after the update by - // one of the events CustomPiwikJs is listening to or by a scheduled task. - // In this case, we check whether such an update will succeed later and if it will, the file is - // valid as well as it will be updated on the next request - $trackerUpdater->checkWillSucceed(); - return true; - } catch (AccessDeniedException $e) { - return false; - } - - } - } - - return false; - } - - /** - * Get file integrity information (in PIWIK_INCLUDE_PATH). - * - * @return array(bool, string, ...) Return code (true/false), followed by zero or more error messages - */ - public static function getFileIntegrityInformation() - { - $messages = array(); - $messages[] = true; - - $manifest = PIWIK_INCLUDE_PATH . '/config/manifest.inc.php'; - - if (file_exists($manifest)) { - require_once $manifest; - } - - if (!class_exists('Piwik\\Manifest')) { - $messages[] = Piwik::translate('General_WarningFileIntegrityNoManifest') - . ' ' - . Piwik::translate('General_WarningFileIntegrityNoManifestDeployingFromGit'); - - return $messages; - } - - $files = \Piwik\Manifest::$files; - - $hasMd5file = function_exists('md5_file'); - $hasMd5 = function_exists('md5'); - foreach ($files as $path => $props) { - $file = PIWIK_INCLUDE_PATH . '/' . $path; - - if (!file_exists($file) || !is_readable($file)) { - $messages[] = Piwik::translate('General_ExceptionMissingFile', $file); - } elseif (filesize($file) != $props[0]) { - - if (self::isModifiedPathValid($path)) { - continue; - } - - if (!$hasMd5 || in_array(substr($path, -4), array('.gif', '.ico', '.jpg', '.png', '.swf'))) { - // files that contain binary data (e.g., images) must match the file size - $messages[] = Piwik::translate('General_ExceptionFilesizeMismatch', array($file, $props[0], filesize($file))); - } else { - // convert end-of-line characters and re-test text files - $content = @file_get_contents($file); - $content = str_replace("\r\n", "\n", $content); - if ((strlen($content) != $props[0]) - || (@md5($content) !== $props[1]) - ) { - $messages[] = Piwik::translate('General_ExceptionFilesizeMismatch', array($file, $props[0], filesize($file))); - } - } - } elseif ($hasMd5file && (@md5_file($file) !== $props[1])) { - if (self::isModifiedPathValid($path)) { - continue; - } - - $messages[] = Piwik::translate('General_ExceptionFileIntegrity', $file); - } - } - - if (count($messages) > 1) { - $messages[0] = false; - } - - if (!$hasMd5file) { - $messages[] = Piwik::translate('General_WarningFileIntegrityNoMd5file'); - } - - return $messages; - } - /** * Returns the help message when the auto update can't run because of missing permissions * @@ -326,4 +218,6 @@ class Filechecks return "$user:$group"; } + + } diff --git a/core/FrontController.php b/core/FrontController.php index 569f541dfa..c08ad5a6c2 100644 --- a/core/FrontController.php +++ b/core/FrontController.php @@ -15,6 +15,7 @@ use Piwik\Container\StaticContainer; use Piwik\Exception\AuthenticationFailedException; use Piwik\Exception\DatabaseSchemaIsNewerThanCodebaseException; use Piwik\Exception\PluginDeactivatedException; +use Piwik\Exception\StylesheetLessCompileException; use Piwik\Http\ControllerResolver; use Piwik\Http\Router; use Piwik\Plugins\CoreAdminHome\CustomLogo; @@ -73,11 +74,11 @@ class FrontController extends Singleton /** * @param $lastError - * @return mixed|void + * @return string * @throws AuthenticationFailedException * @throws Exception */ - private static function generateSafeModeOutput($lastError) + private static function generateSafeModeOutputFromError($lastError) { Common::sendResponseCode(500); @@ -94,6 +95,20 @@ class FrontController extends Singleton } /** + * @param Exception $e + * @return string + */ + private static function generateSafeModeOutputFromException($e) + { + $error = array( + 'message' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine() + ); + return self::generateSafeModeOutputFromError($error); + } + + /** * Executes the requested plugin controller method. * * @throws Exception|\Piwik\Exception\PluginDeactivatedException in case the plugin doesn't exist, the action doesn't exist, @@ -133,6 +148,15 @@ class FrontController extends Singleton * @param \Piwik\NoAccessException $exception The exception that was caught. */ Piwik::postEvent('User.isNotAuthorized', array($exception), $pending = true); + } catch (\Twig_Error_Runtime $e) { + echo $this->generateSafeModeOutputFromException($e); + exit; + } catch(StylesheetLessCompileException $e) { + echo $this->generateSafeModeOutputFromException($e); + exit; + } catch(\Error $e) { + echo $this->generateSafeModeOutputFromException($e); + exit; } } @@ -202,7 +226,7 @@ class FrontController extends Singleton { $lastError = error_get_last(); if (!empty($lastError) && $lastError['type'] == E_ERROR) { - $message = self::generateSafeModeOutput($lastError); + $message = self::generateSafeModeOutputFromError($lastError); echo $message; } } diff --git a/core/Http.php b/core/Http.php index 79ee0d2af3..48e1fa14fa 100644 --- a/core/Http.php +++ b/core/Http.php @@ -792,9 +792,7 @@ class Http */ public static function configCurlCertificate(&$ch) { - if (file_exists(PIWIK_INCLUDE_PATH . '/core/DataFiles/cacert.pem')) { - @curl_setopt($ch, CURLOPT_CAINFO, PIWIK_INCLUDE_PATH . '/core/DataFiles/cacert.pem'); - } + @curl_setopt($ch, CURLOPT_CAINFO, PIWIK_INCLUDE_PATH . '/core/DataFiles/cacert.pem'); } public static function getUserAgent() diff --git a/core/SettingsPiwik.php b/core/SettingsPiwik.php index 52c4f3e16f..446712703a 100644 --- a/core/SettingsPiwik.php +++ b/core/SettingsPiwik.php @@ -20,7 +20,9 @@ class SettingsPiwik const OPTION_PIWIK_URL = 'piwikUrl'; /** - * Get salt from [General] section + * Get salt from [General] section. Should ONLY be used as a seed to create hashes + * + * NOTE: Keep this salt secret! Never output anywhere or share it etc. * * @return string */ diff --git a/core/Updates/3.0.1-b1.php b/core/Updates/3.0.1-b1.php new file mode 100644 index 0000000000..6222e27b2c --- /dev/null +++ b/core/Updates/3.0.1-b1.php @@ -0,0 +1,24 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + */ + +namespace Piwik\Updates; + +use Piwik\Plugins\Installation\ServerFilesGenerator; +use Piwik\Updater; +use Piwik\Updates as PiwikUpdates; + +class Updates_3_0_1_b1 extends PiwikUpdates +{ + public function doUpdate(Updater $updater) + { + // Allow IIS to serve .woff files (https://github.com/piwik/piwik/pull/11091). + // Re-generate .htaccess without 'Options -Indexes' because it does not always work on some servers + ServerFilesGenerator::createFilesForSecurity(); + } +} diff --git a/core/Version.php b/core/Version.php index 964c773ece..4c3cef867f 100644 --- a/core/Version.php +++ b/core/Version.php @@ -20,7 +20,7 @@ final class Version * The current Piwik version. * @var string */ - const VERSION = '3.0.0'; + const VERSION = '3.0.1-b1'; public function isStableVersion($version) { |