From e802eae2a2690a80f217bcc2952a1adb100a7a77 Mon Sep 17 00:00:00 2001 From: mattab <matthieu.aubry@gmail.com> Date: Sat, 14 Sep 2013 12:38:59 +1200 Subject: [PATCH] Adding Filesystem class which contains file operations And factored out code in ServerFilesGenerator.php --- core/AssetManager.php | 2 +- core/CacheFile.php | 8 +- core/Common.php | 95 +----- core/Filesystem.php | 266 ++++++++++++++++ core/Http.php | 7 +- core/Log.php | 8 +- core/Piwik.php | 291 +----------------- core/PluginsManager.php | 12 +- core/ReportRenderer/Pdf.php | 7 +- core/Session.php | 4 +- core/Translate.php | 4 +- core/Translate/Writer.php | 6 +- core/Updates/0.2.10.php | 4 +- core/Updates/0.6.2.php | 3 +- js/index.php | 1 + plugins/API/API.php | 26 +- plugins/CorePluginsAdmin/Controller.php | 7 +- plugins/CorePluginsAdmin/PluginInstaller.php | 6 +- plugins/CoreUpdater/Controller.php | 32 +- plugins/ImageGraph/API.php | 7 +- plugins/Installation/Controller.php | 20 +- plugins/Installation/FormDatabaseSetup.php | 12 +- plugins/Installation/ServerFilesGenerator.php | 134 ++++++++ plugins/LanguagesManager/API.php | 5 +- tests/PHPUnit/Core/CommonTest.php | 5 +- tests/PHPUnit/Core/PiwikTest.php | 5 +- tests/PHPUnit/Core/ReleaseCheckListTest.php | 6 +- 27 files changed, 504 insertions(+), 479 deletions(-) create mode 100644 core/Filesystem.php create mode 100644 plugins/Installation/ServerFilesGenerator.php diff --git a/core/AssetManager.php b/core/AssetManager.php index af8e8a4bd4..1f4a190475 100644 --- a/core/AssetManager.php +++ b/core/AssetManager.php @@ -540,7 +540,7 @@ class AssetManager $mergedFileDirectory = PIWIK_USER_PATH . '/' . self::MERGED_FILE_DIR; if (!is_dir($mergedFileDirectory)) { - Common::mkdir($mergedFileDirectory); + Filesystem::mkdir($mergedFileDirectory); } if (!is_writable($mergedFileDirectory)) { diff --git a/core/CacheFile.php b/core/CacheFile.php index addb926d6c..4666ee69a1 100644 --- a/core/CacheFile.php +++ b/core/CacheFile.php @@ -11,8 +11,6 @@ namespace Piwik; use Exception; -use Piwik\Piwik; -use Piwik\Common; /** * Code originally inspired from OpenX @@ -94,7 +92,7 @@ class CacheFile protected function cleanupId($id) { - if (!Common::isValidFilename($id)) { + if (!Filesystem::isValidFilename($id)) { throw new Exception("Invalid cache ID request $id"); } return $id; @@ -113,7 +111,7 @@ class CacheFile return false; } if (!is_dir($this->cachePath)) { - Common::mkdir($this->cachePath); + Filesystem::mkdir($this->cachePath); } if (!is_writable($this->cachePath)) { return false; @@ -176,6 +174,6 @@ class CacheFile */ public function deleteAll() { - Piwik::unlinkRecursive($this->cachePath, $deleteRootToo = false); + Filesystem::unlinkRecursive($this->cachePath, $deleteRootToo = false); } } diff --git a/core/Common.php b/core/Common.php index c9116c8cc1..311e3fdb84 100644 --- a/core/Common.php +++ b/core/Common.php @@ -11,11 +11,9 @@ namespace Piwik; use Exception; -use Piwik\IP; +use Piwik\Plugins\UserCountry\LocationProvider\DefaultProvider; use Piwik\Tracker; use Piwik\Tracker\Cache; -use Piwik\PluginsManager; -use Piwik\Plugins\UserCountry\LocationProvider\DefaultProvider; /** * Static class providing functions used by both the CORE of Piwik and the visitor Tracking engine. @@ -268,97 +266,6 @@ class Common && strlen($matches[2]) > 0; } - /* - * File operations - */ - - /** - * ending WITHOUT slash - * - * @return string - */ - public static function getPathToPiwikRoot() - { - return realpath(dirname(__FILE__) . "/.."); - } - - /** - * Create directory if permitted - * - * @param string $path - * @param bool $denyAccess - */ - public static function mkdir($path, $denyAccess = true) - { - if (!is_dir($path)) { - // the mode in mkdir is modified by the current umask - @mkdir($path, $mode = 0755, $recursive = true); - } - - // try to overcome restrictive umask (mis-)configuration - if (!is_writable($path)) { - @chmod($path, 0755); - if (!is_writable($path)) { - @chmod($path, 0775); - - // enough! we're not going to make the directory world-writeable - } - } - - if ($denyAccess) { - self::createHtAccess($path, $overwrite = false); - } - } - - /** - * Create .htaccess file in specified directory - * - * Apache-specific; for IIS @see web.config - * - * @param string $path without trailing slash - * @param bool $overwrite whether to overwrite an existing file or not - * @param string $content - */ - public static function createHtAccess($path, $overwrite = true, $content = "<Files \"*\">\n<IfModule mod_access.c>\nDeny from all\n</IfModule>\n<IfModule !mod_access_compat>\n<IfModule mod_authz_host.c>\nDeny from all\n</IfModule>\n</IfModule>\n<IfModule mod_access_compat>\nDeny from all\n</IfModule>\n</Files>\n") - { - if (self::isApache()) { - $file = $path . '/.htaccess'; - if ($overwrite || !file_exists($file)) { - @file_put_contents($file, $content); - } - } - } - - /** - * Get canonicalized absolute path - * See http://php.net/realpath - * - * @param string $path - * @return string canonicalized absolute path - */ - public static function realpath($path) - { - if (file_exists($path)) { - return realpath($path); - } - return $path; - } - - /** - * Returns true if the string is a valid filename - * File names that start with a-Z or 0-9 and contain a-Z, 0-9, underscore(_), dash(-), and dot(.) will be accepted. - * File names beginning with anything but a-Z or 0-9 will be rejected (including .htaccess for example). - * File names containing anything other than above mentioned will also be rejected (file names with spaces won't be accepted). - * - * @param string $filename - * @return bool - * - */ - public static function isValidFilename($filename) - { - return (0 !== preg_match('/(^[a-zA-Z0-9]+([a-zA-Z_0-9.-]*))$/D', $filename)); - } - /* * String operations */ diff --git a/core/Filesystem.php b/core/Filesystem.php new file mode 100644 index 0000000000..ab5be2bca0 --- /dev/null +++ b/core/Filesystem.php @@ -0,0 +1,266 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik + * @package Piwik + */ +namespace Piwik; + +use Exception; + +class Filesystem +{ + /** + * ending WITHOUT slash + * + * @return string + */ + public static function getPathToPiwikRoot() + { + return realpath(dirname(__FILE__) . "/.."); + } + + /** + * Create .htaccess file in specified directory + * + * Apache-specific; for IIS @see web.config + * + * @param string $path without trailing slash + * @param bool $overwrite whether to overwrite an existing file or not + * @param string $content + */ + public static function createHtAccess($path, $overwrite = true, $content = "<Files \"*\">\n<IfModule mod_access.c>\nDeny from all\n</IfModule>\n<IfModule !mod_access_compat>\n<IfModule mod_authz_host.c>\nDeny from all\n</IfModule>\n</IfModule>\n<IfModule mod_access_compat>\nDeny from all\n</IfModule>\n</Files>\n") + { + if (Common::isApache()) { + $file = $path . '/.htaccess'; + if ($overwrite || !file_exists($file)) { + @file_put_contents($file, $content); + } + } + } + + /** + * Returns true if the string is a valid filename + * File names that start with a-Z or 0-9 and contain a-Z, 0-9, underscore(_), dash(-), and dot(.) will be accepted. + * File names beginning with anything but a-Z or 0-9 will be rejected (including .htaccess for example). + * File names containing anything other than above mentioned will also be rejected (file names with spaces won't be accepted). + * + * @param string $filename + * @return bool + * + */ + public static function isValidFilename($filename) + { + return (0 !== preg_match('/(^[a-zA-Z0-9]+([a-zA-Z_0-9.-]*))$/D', $filename)); + } + + /** + * Get canonicalized absolute path + * See http://php.net/realpath + * + * @param string $path + * @return string canonicalized absolute path + */ + public static function realpath($path) + { + if (file_exists($path)) { + return realpath($path); + } + return $path; + } + + /** + * Create directory if permitted + * + * @param string $path + * @param bool $denyAccess + */ + public static function mkdir($path, $denyAccess = true) + { + if (!is_dir($path)) { + // the mode in mkdir is modified by the current umask + @mkdir($path, $mode = 0755, $recursive = true); + } + + // try to overcome restrictive umask (mis-)configuration + if (!is_writable($path)) { + @chmod($path, 0755); + if (!is_writable($path)) { + @chmod($path, 0775); + // enough! we're not going to make the directory world-writeable + } + } + + if ($denyAccess) { + self::createHtAccess($path, $overwrite = false); + } + } + + /** + * Checks if the filesystem Piwik stores sessions in is NFS or not. This + * check is done in order to avoid using file based sessions on NFS system, + * since on such a filesystem file locking can make file based sessions + * incredibly slow. + * + * Note: In order to figure this out, we try to run the 'df' program. If + * the 'exec' or 'shell_exec' functions are not available, we can't do + * the check. + * + * @return bool True if on an NFS filesystem, false if otherwise or if we + * can't use shell_exec or exec. + */ + public static function checkIfFileSystemIsNFS() + { + $sessionsPath = Session::getSessionsDirectory(); + + // this command will display details for the filesystem that holds the $sessionsPath + // path, but only if its type is NFS. if not NFS, df will return one or less lines + // and the return code 1. if NFS, it will return 0 and at least 2 lines of text. + $command = "df -T -t nfs \"$sessionsPath\" 2>&1"; + + if (function_exists('exec')) // use exec + { + $output = $returnCode = null; + @exec($command, $output, $returnCode); + + // check if filesystem is NFS + if ($returnCode == 0 + && count($output) > 1 + ) { + return true; + } + } else if (function_exists('shell_exec')) // use shell_exec + { + $output = @shell_exec($command); + if ($output) { + $output = explode("\n", $output); + if (count($output) > 1) // check if filesystem is NFS + { + return true; + } + } + } + + return false; // not NFS, or we can't run a program to find out + } + + /** + * Recursively find pathnames that match a pattern + * @see glob() + * + * @param string $sDir directory + * @param string $sPattern pattern + * @param int $nFlags glob() flags + * @return array + */ + public static function globr($sDir, $sPattern, $nFlags = null) + { + if (($aFiles = \_glob("$sDir/$sPattern", $nFlags)) == false) { + $aFiles = array(); + } + if (($aDirs = \_glob("$sDir/*", GLOB_ONLYDIR)) != false) { + foreach ($aDirs as $sSubDir) { + if (is_link($sSubDir)) { + continue; + } + + $aSubFiles = self::globr($sSubDir, $sPattern, $nFlags); + $aFiles = array_merge($aFiles, $aSubFiles); + } + } + return $aFiles; + } + + /** + * Recursively delete a directory + * + * @param string $dir Directory name + * @param boolean $deleteRootToo Delete specified top-level directory as well + */ + public static function unlinkRecursive($dir, $deleteRootToo) + { + if (!$dh = @opendir($dir)) { + return; + } + while (false !== ($obj = readdir($dh))) { + if ($obj == '.' || $obj == '..') { + continue; + } + + if (!@unlink($dir . '/' . $obj)) { + self::unlinkRecursive($dir . '/' . $obj, true); + } + } + closedir($dh); + if ($deleteRootToo) { + @rmdir($dir); + } + return; + } + + /** + * Copy individual file from $source to $target. + * + * @param string $source eg. './tmp/latest/index.php' + * @param string $dest eg. './index.php' + * @param bool $excludePhp + * @throws Exception + * @return bool + */ + public static function copy($source, $dest, $excludePhp = false) + { + static $phpExtensions = array('php', 'tpl', 'twig'); + + if ($excludePhp) { + $path_parts = pathinfo($source); + if (in_array($path_parts['extension'], $phpExtensions)) { + return true; + } + } + + if (!@copy($source, $dest)) { + @chmod($dest, 0755); + if (!@copy($source, $dest)) { + $message = "Error while creating/copying file to <code>$dest</code>. <br />" + . Piwik::getErrorMessageMissingPermissions(self::getPathToPiwikRoot()); + throw new Exception($message); + } + } + return true; + } + + /** + * Copy recursively from $source to $target. + * + * @param string $source eg. './tmp/latest' + * @param string $target eg. '.' + * @param bool $excludePhp + */ + public static function copyRecursive($source, $target, $excludePhp = false) + { + if (is_dir($source)) { + self::mkdir($target, false); + $d = dir($source); + while (false !== ($entry = $d->read())) { + if ($entry == '.' || $entry == '..') { + continue; + } + + $sourcePath = $source . '/' . $entry; + if (is_dir($sourcePath)) { + self::copyRecursive($sourcePath, $target . '/' . $entry, $excludePhp); + continue; + } + $destPath = $target . '/' . $entry; + self::copy($sourcePath, $destPath, $excludePhp); + } + $d->close(); + } else { + self::copy($source, $target, $excludePhp); + } + } +} diff --git a/core/Http.php b/core/Http.php index 9f3d52045d..0da5b04503 100644 --- a/core/Http.php +++ b/core/Http.php @@ -11,11 +11,6 @@ namespace Piwik; use Exception; -use Piwik\Config; -use Piwik\Piwik; -use Piwik\Common; -use Piwik\IP; -use Piwik\Version; /** * Server-side http client to retrieve content from remote servers, and optionally save to a local file. @@ -80,7 +75,7 @@ class Http $file = null; if ($destinationPath) { // Ensure destination directory exists - Common::mkdir(dirname($destinationPath)); + Filesystem::mkdir(dirname($destinationPath)); if (($file = @fopen($destinationPath, 'wb')) === false || !is_resource($file)) { throw new Exception('Error while creating the file: ' . $destinationPath); } diff --git a/core/Log.php b/core/Log.php index 48a9f6c429..a2720098dd 100644 --- a/core/Log.php +++ b/core/Log.php @@ -9,12 +9,10 @@ * @package Piwik */ namespace Piwik; -use Piwik\Config; -use Piwik\Common; -use Piwik\Log\Exception; -use Piwik\Log\Error; use Piwik\Log\APICall; +use Piwik\Log\Error; +use Piwik\Log\Exception; use Piwik\Log\Message; /** @@ -64,7 +62,7 @@ abstract class Log extends \Zend_Log function addWriteToFile() { - Common::mkdir(dirname($this->logToFileFilename)); + Filesystem::mkdir(dirname($this->logToFileFilename)); $writerFile = new \Zend_Log_Writer_Stream($this->logToFileFilename); $writerFile->setFormatter($this->fileFormatter); $this->addWriter($writerFile); diff --git a/core/Piwik.php b/core/Piwik.php index 469b1f80e6..d2f760ed7f 100644 --- a/core/Piwik.php +++ b/core/Piwik.php @@ -182,68 +182,6 @@ class Piwik * File and directory operations */ - /** - * Copy recursively from $source to $target. - * - * @param string $source eg. './tmp/latest' - * @param string $target eg. '.' - * @param bool $excludePhp - */ - static public function copyRecursive($source, $target, $excludePhp = false) - { - if (is_dir($source)) { - Common::mkdir($target, false); - $d = dir($source); - while (false !== ($entry = $d->read())) { - if ($entry == '.' || $entry == '..') { - continue; - } - - $sourcePath = $source . '/' . $entry; - if (is_dir($sourcePath)) { - self::copyRecursive($sourcePath, $target . '/' . $entry, $excludePhp); - continue; - } - $destPath = $target . '/' . $entry; - self::copy($sourcePath, $destPath, $excludePhp); - } - $d->close(); - } else { - self::copy($source, $target, $excludePhp); - } - } - - /** - * Copy individual file from $source to $target. - * - * @param string $source eg. './tmp/latest/index.php' - * @param string $dest eg. './index.php' - * @param bool $excludePhp - * @throws Exception - * @return bool - */ - static public function copy($source, $dest, $excludePhp = false) - { - static $phpExtensions = array('php', 'tpl', 'twig'); - - if ($excludePhp) { - $path_parts = pathinfo($source); - if (in_array($path_parts['extension'], $phpExtensions)) { - return true; - } - } - - if (!@copy($source, $dest)) { - @chmod($dest, 0755); - if (!@copy($source, $dest)) { - $message = "Error while creating/copying file to <code>$dest</code>. <br />" - . self::getErrorMessageMissingPermissions(Common::getPathToPiwikRoot()); - throw new Exception($message); - } - } - return true; - } - /** * Returns friendly error message explaining how to fix permissions * @@ -268,60 +206,6 @@ class Piwik return $message; } - /** - * Recursively delete a directory - * - * @param string $dir Directory name - * @param boolean $deleteRootToo Delete specified top-level directory as well - */ - static public function unlinkRecursive($dir, $deleteRootToo) - { - if (!$dh = @opendir($dir)) { - return; - } - while (false !== ($obj = readdir($dh))) { - if ($obj == '.' || $obj == '..') { - continue; - } - - if (!@unlink($dir . '/' . $obj)) { - self::unlinkRecursive($dir . '/' . $obj, true); - } - } - closedir($dh); - if ($deleteRootToo) { - @rmdir($dir); - } - return; - } - - /** - * Recursively find pathnames that match a pattern - * @see glob() - * - * @param string $sDir directory - * @param string $sPattern pattern - * @param int $nFlags glob() flags - * @return array - */ - public static function globr($sDir, $sPattern, $nFlags = null) - { - if (($aFiles = \_glob("$sDir/$sPattern", $nFlags)) == false) { - $aFiles = array(); - } - if (($aDirs = \_glob("$sDir/*", GLOB_ONLYDIR)) != false) { - foreach ($aDirs as $sSubDir) { - if (is_link($sSubDir)) { - continue; - } - - $aSubFiles = self::globr($sSubDir, $sPattern, $nFlags); - $aFiles = array_merge($aFiles, $aSubFiles); - } - } - return $aFiles; - } - /** * Returns the help text displayed to suggest which command to run to give writable access to a file or directory * @@ -351,7 +235,7 @@ class Piwik $directoryList = ''; foreach ($resultCheck as $dir => $bool) { - $realpath = Common::realpath($dir); + $realpath = Filesystem::realpath($dir); if (!empty($realpath) && $bool === false) { $directoryList .= self::getMakeWritableCommand($realpath); } @@ -359,7 +243,7 @@ class Piwik // Also give the chown since the chmod is only 755 if (!Common::isWindows()) { - $realpath = Common::realpath(PIWIK_INCLUDE_PATH . '/'); + $realpath = Filesystem::realpath(PIWIK_INCLUDE_PATH . '/'); $directoryList = "<code>chown -R www-data:www-data " . $realpath . "</code><br/>" . $directoryList; } @@ -391,10 +275,10 @@ class Piwik // Create an empty directory $isFile = strpos($directoryToCheck, '.') !== false; if (!$isFile && !file_exists($directoryToCheck)) { - Common::mkdir($directoryToCheck); + Filesystem::mkdir($directoryToCheck); } - $directory = Common::realpath($directoryToCheck); + $directory = Filesystem::realpath($directoryToCheck); $resultCheck[$directory] = false; if ($directory !== false // realpath() returns FALSE on failure && is_writable($directoryToCheck) @@ -430,7 +314,7 @@ class Piwik */ static public function getAutoUpdateMakeWritableMessage() { - $realpath = Common::realpath(PIWIK_INCLUDE_PATH . '/'); + $realpath = Filesystem::realpath(PIWIK_INCLUDE_PATH . '/'); $message = ''; $message .= "<code>chown -R www-data:www-data " . $realpath . "</code><br />"; $message .= "<code>chmod -R 0755 " . $realpath . "</code><br />"; @@ -438,123 +322,6 @@ class Piwik return $message; } - /** - * Generate default robots.txt, favicon.ico, etc to suppress - * 404 (Not Found) errors in the web server logs, if Piwik - * is installed in the web root (or top level of subdomain). - * - * @see misc/crossdomain.xml - */ - static public function createWebRootFiles() - { - $filesToCreate = array( - '/robots.txt', - '/favicon.ico', - ); - foreach ($filesToCreate as $file) { - @file_put_contents(PIWIK_DOCUMENT_ROOT . $file, ''); - } - } - - /** - * Generate Apache .htaccess files to restrict access - */ - static public function createHtAccessFiles() - { - // deny access to these folders - $directoriesToProtect = array( - '/config', - '/core', - '/lang', - '/tmp', - ); - foreach ($directoriesToProtect as $directoryToProtect) { - Common::createHtAccess(PIWIK_INCLUDE_PATH . $directoryToProtect, $overwrite = true); - } - - // Allow/Deny lives in different modules depending on the Apache version - $allow = "<IfModule mod_access.c>\nAllow from all\n</IfModule>\n<IfModule !mod_access_compat>\n<IfModule mod_authz_host.c>\nAllow from all\n</IfModule>\n</IfModule>\n<IfModule mod_access_compat>\nAllow from all\n</IfModule>\n"; - $deny = "<IfModule mod_access.c>\nDeny from all\n</IfModule>\n<IfModule !mod_access_compat>\n<IfModule mod_authz_host.c>\nDeny from all\n</IfModule>\n</IfModule>\n<IfModule mod_access_compat>\nDeny from all\n</IfModule>\n"; - - // more selective allow/deny filters - $allowAny = "<Files \"*\">\n" . $allow . "Satisfy any\n</Files>\n"; - $allowStaticAssets = "<Files ~ \"\\.(test\.php|gif|ico|jpg|png|svg|js|css|swf)$\">\n" . $allow . "Satisfy any\n</Files>\n"; - $denyDirectPhp = "<Files ~ \"\\.(php|php4|php5|inc|tpl|in|twig)$\">\n" . $deny . "</Files>\n"; - - $directoriesToProtect = array( - '/js' => $allowAny, - '/libs' => $denyDirectPhp . $allowStaticAssets, - '/vendor' => $denyDirectPhp . $allowStaticAssets, - '/plugins' => $denyDirectPhp . $allowStaticAssets, - '/misc/user' => $denyDirectPhp . $allowStaticAssets, - ); - foreach ($directoriesToProtect as $directoryToProtect => $content) { - Common::createHtAccess(PIWIK_INCLUDE_PATH . $directoryToProtect, $overwrite = true, $content); - } - } - - /** - * Generate IIS web.config files to restrict access - * - * Note: for IIS 7 and above - */ - static public function createWebConfigFiles() - { - @file_put_contents(PIWIK_INCLUDE_PATH . '/web.config', - '<?xml version="1.0" encoding="UTF-8"?> -<configuration> - <system.webServer> - <security> - <requestFiltering> - <hiddenSegments> - <add segment="config" /> - <add segment="core" /> - <add segment="lang" /> - <add segment="tmp" /> - </hiddenSegments> - <fileExtensions> - <add fileExtension=".tpl" allowed="false" /> - <add fileExtension=".twig" allowed="false" /> - <add fileExtension=".php4" allowed="false" /> - <add fileExtension=".php5" allowed="false" /> - <add fileExtension=".inc" allowed="false" /> - <add fileExtension=".in" allowed="false" /> - </fileExtensions> - </requestFiltering> - </security> - <directoryBrowse enabled="false" /> - <defaultDocument> - <files> - <remove value="index.php" /> - <add value="index.php" /> - </files> - </defaultDocument> - </system.webServer> -</configuration>'); - - // deny direct access to .php files - $directoriesToProtect = array( - '/libs', - '/vendor', - '/plugins', - ); - foreach ($directoriesToProtect as $directoryToProtect) { - @file_put_contents(PIWIK_INCLUDE_PATH . $directoryToProtect . '/web.config', - '<?xml version="1.0" encoding="UTF-8"?> -<configuration> - <system.webServer> - <security> - <requestFiltering> - <denyUrlSequences> - <add sequence=".php" /> - </denyUrlSequences> - </requestFiltering> - </security> - </system.webServer> -</configuration>'); - } - } - /** * Get file integrity information (in PIWIK_INCLUDE_PATH). * @@ -2011,54 +1778,6 @@ class Piwik return false; } - /** - * Checks if the filesystem Piwik stores sessions in is NFS or not. This - * check is done in order to avoid using file based sessions on NFS system, - * since on such a filesystem file locking can make file based sessions - * incredibly slow. - * - * Note: In order to figure this out, we try to run the 'df' program. If - * the 'exec' or 'shell_exec' functions are not available, we can't do - * the check. - * - * @return bool True if on an NFS filesystem, false if otherwise or if we - * can't use shell_exec or exec. - */ - public static function checkIfFileSystemIsNFS() - { - $sessionsPath = Session::getSessionsDirectory(); - - // this command will display details for the filesystem that holds the $sessionsPath - // path, but only if its type is NFS. if not NFS, df will return one or less lines - // and the return code 1. if NFS, it will return 0 and at least 2 lines of text. - $command = "df -T -t nfs \"$sessionsPath\" 2>&1"; - - if (function_exists('exec')) // use exec - { - $output = $returnCode = null; - @exec($command, $output, $returnCode); - - // check if filesystem is NFS - if ($returnCode == 0 - && count($output) > 1 - ) { - return true; - } - } else if (function_exists('shell_exec')) // use shell_exec - { - $output = @shell_exec($command); - if ($output) { - $output = explode("\n", $output); - if (count($output) > 1) // check if filesystem is NFS - { - return true; - } - } - } - - return false; // not NFS, or we can't run a program to find out - } - /** * Returns the option name of the option that stores the time the archive.php * script was last run. diff --git a/core/PluginsManager.php b/core/PluginsManager.php index b12ab0cd45..2df4707994 100644 --- a/core/PluginsManager.php +++ b/core/PluginsManager.php @@ -11,12 +11,8 @@ namespace Piwik; -use Piwik\Config; -use Piwik\Piwik; -use Piwik\Common; -use Piwik\EventDispatcher; -use Piwik\Translate; use Piwik\Plugin\MetadataLoader; +use Piwik\Translate; require_once PIWIK_INCLUDE_PATH . '/core/EventDispatcher.php'; @@ -210,7 +206,7 @@ class PluginsManager public static function deletePluginFromFilesystem($plugin) { - Piwik::unlinkRecursive(PIWIK_INCLUDE_PATH . '/plugins/' . $plugin, $deleteRootToo = true); + Filesystem::unlinkRecursive(PIWIK_INCLUDE_PATH . '/plugins/' . $plugin, $deleteRootToo = true); } /** @@ -321,7 +317,7 @@ class PluginsManager { $existingPlugins = $this->readPluginsDirectory(); $isPluginInFilesystem = array_search($pluginName, $existingPlugins) !== false; - return Common::isValidFilename($pluginName) + return Filesystem::isValidFilename($pluginName) && $isPluginInFilesystem; } @@ -497,7 +493,7 @@ class PluginsManager $pluginFileName = sprintf("%s/%s.php", $pluginName, $pluginName); $pluginClassName = $pluginName; - if (!Common::isValidFilename($pluginName)) { + if (!Filesystem::isValidFilename($pluginName)) { throw new \Exception(sprintf("The plugin filename '%s' is not a valid filename", $pluginFileName)); } diff --git a/core/ReportRenderer/Pdf.php b/core/ReportRenderer/Pdf.php index 842908de72..d548523eae 100644 --- a/core/ReportRenderer/Pdf.php +++ b/core/ReportRenderer/Pdf.php @@ -11,9 +11,10 @@ namespace Piwik\ReportRenderer; use Piwik\Common; -use Piwik\TCPDF; -use Piwik\ReportRenderer; +use Piwik\Filesystem; use Piwik\Plugins\API\API; +use Piwik\ReportRenderer; +use Piwik\TCPDF; /** * @see libs/tcpdf @@ -378,7 +379,7 @@ class Pdf extends ReportRenderer if ($logoHeight < 16) { $topMargin = 2; } - $path = Common::getPathToPiwikRoot() . "/" . $rowMetadata['logo']; + $path = Filesystem::getPathToPiwikRoot() . "/" . $rowMetadata['logo']; if (file_exists($path)) { $this->TCPDF->Image($path, $posX + ($leftMargin = 2), $posY + $topMargin, $logoWidth / 4); } diff --git a/core/Session.php b/core/Session.php index 59e02a89a2..d89df96ea1 100644 --- a/core/Session.php +++ b/core/Session.php @@ -84,7 +84,7 @@ class Session extends Zend_Session // for "files", use our own folder to prevent local session file hijacking $sessionPath = self::getSessionsDirectory(); // We always call mkdir since it also chmods the directory which might help when permissions were reverted for some reasons - Common::mkdir($sessionPath); + Filesystem::mkdir($sessionPath); @ini_set('session.save_handler', 'files'); @ini_set('session.save_path', $sessionPath); @@ -131,7 +131,7 @@ class Session extends Zend_Session $message = sprintf("Error: %s %s %s\n<pre>Debug: the original error was \n%s</pre>", Piwik_Translate('General_ExceptionUnableToStartSession'), - Piwik::getErrorMessageMissingPermissions(Common::getPathToPiwikRoot() . '/tmp/sessions/'), + Piwik::getErrorMessageMissingPermissions(Filesystem::getPathToPiwikRoot() . '/tmp/sessions/'), $enableDbSessions, $e->getMessage() ); diff --git a/core/Translate.php b/core/Translate.php index dbb6c6d3f6..c36410f505 100644 --- a/core/Translate.php +++ b/core/Translate.php @@ -10,8 +10,6 @@ */ namespace Piwik; use Exception; -use Piwik\Config; -use Piwik\Common; /** * @package Piwik @@ -104,7 +102,7 @@ class Translate private function loadCoreTranslationFile($language) { $path = PIWIK_INCLUDE_PATH . '/lang/' . $language . '.json'; - if (!Common::isValidFilename($language) || !is_readable($path)) { + if (!Filesystem::isValidFilename($language) || !is_readable($path)) { throw new Exception(Piwik_TranslateException('General_ExceptionLanguageFileNotFound', array($language))); } $data = file_get_contents($path); diff --git a/core/Translate/Writer.php b/core/Translate/Writer.php index 914914955e..4360b1ecbf 100644 --- a/core/Translate/Writer.php +++ b/core/Translate/Writer.php @@ -12,7 +12,7 @@ namespace Piwik\Translate; use Exception; -use Piwik\Common; +use Piwik\Filesystem; use Piwik\PluginsManager; use Piwik\Translate\Filter\FilterAbstract; use Piwik\Translate\Validate\ValidateAbstract; @@ -249,7 +249,7 @@ class Writer $path = $this->getTranslationPath(); - Common::mkdir(dirname($path)); + Filesystem::mkdir(dirname($path)); return file_put_contents($path, $this->__toString()); } @@ -270,7 +270,7 @@ class Writer $path = $this->getTemporaryTranslationPath(); - Common::mkdir(dirname($path)); + Filesystem::mkdir(dirname($path)); return file_put_contents($path, $this->__toString()); } diff --git a/core/Updates/0.2.10.php b/core/Updates/0.2.10.php index 588c3f98ff..a378dccd51 100644 --- a/core/Updates/0.2.10.php +++ b/core/Updates/0.2.10.php @@ -8,8 +8,8 @@ * @category Piwik * @package Updates */ -use Piwik\Piwik; use Piwik\Common; +use Piwik\Filesystem; use Piwik\Updater; use Piwik\Updates; @@ -66,7 +66,7 @@ class Piwik_Updates_0_2_10 extends Updates ); foreach ($obsoleteDirectories as $dir) { if (file_exists(PIWIK_INCLUDE_PATH . $dir)) { - Piwik::unlinkRecursive(PIWIK_INCLUDE_PATH . $dir, true); + Filesystem::unlinkRecursive(PIWIK_INCLUDE_PATH . $dir, true); } } } diff --git a/core/Updates/0.6.2.php b/core/Updates/0.6.2.php index af541713a2..0651f2f676 100644 --- a/core/Updates/0.6.2.php +++ b/core/Updates/0.6.2.php @@ -8,6 +8,7 @@ * @category Piwik * @package Updates */ +use Piwik\Filesystem; use Piwik\Piwik; use Piwik\Plugins\SitesManager\API; use Piwik\Tracker\Cache; @@ -34,7 +35,7 @@ class Piwik_Updates_0_6_2 extends Updates ); foreach ($obsoleteDirectories as $dir) { if (file_exists($dir)) { - Piwik::unlinkRecursive($dir, true); + Filesystem::unlinkRecursive($dir, true); } } diff --git a/js/index.php b/js/index.php index 721e3353ef..5d8ec0a41b 100644 --- a/js/index.php +++ b/js/index.php @@ -26,6 +26,7 @@ define('PIWIK_USER_PATH', '..'); require_once PIWIK_INCLUDE_PATH . '/libs/upgradephp/upgrade.php'; require_once PIWIK_INCLUDE_PATH . '/core/Piwik.php'; +require_once PIWIK_INCLUDE_PATH . '/core/ProxyHttp.php'; $file = '../piwik.js'; diff --git a/plugins/API/API.php b/plugins/API/API.php index d26b46db47..19cade72f1 100644 --- a/plugins/API/API.php +++ b/plugins/API/API.php @@ -10,19 +10,19 @@ */ namespace Piwik\Plugins\API; -use Piwik\API\Request; use Piwik\API\Proxy; +use Piwik\API\Request; +use Piwik\Config; use Piwik\DataTable\Filter\ColumnDelete; use Piwik\DataTable\Row; +use Piwik\DataTable; +use Piwik\Date; +use Piwik\Filesystem; use Piwik\Metrics; use Piwik\Piwik; -use Piwik\Common; -use Piwik\Config; -use Piwik\Date; -use Piwik\DataTable; use Piwik\Tracker\GoalManager; -use Piwik\Version; use Piwik\Translate; +use Piwik\Version; require_once PIWIK_INCLUDE_PATH . '/core/Config.php'; @@ -284,14 +284,14 @@ class API { $logo = 'plugins/Zeitgeist/images/logo.png'; if (Config::getInstance()->branding['use_custom_logo'] == 1 - && file_exists(Common::getPathToPiwikRoot() . '/misc/user/logo.png') + && file_exists(Filesystem::getPathToPiwikRoot() . '/misc/user/logo.png') ) { $logo = 'misc/user/logo.png'; } if (!$pathOnly) { return Piwik::getPiwikUrl() . $logo; } - return Common::getPathToPiwikRoot() . '/' . $logo; + return Filesystem::getPathToPiwikRoot() . '/' . $logo; } /** @@ -304,14 +304,14 @@ class API { $logo = 'plugins/Zeitgeist/images/logo-header.png'; if (Config::getInstance()->branding['use_custom_logo'] == 1 - && file_exists(Common::getPathToPiwikRoot() . '/misc/user/logo-header.png') + && file_exists(Filesystem::getPathToPiwikRoot() . '/misc/user/logo-header.png') ) { $logo = 'misc/user/logo-header.png'; } if (!$pathOnly) { return Piwik::getPiwikUrl() . $logo; } - return Common::getPathToPiwikRoot() . '/' . $logo; + return Filesystem::getPathToPiwikRoot() . '/' . $logo; } /** @@ -325,14 +325,14 @@ class API { $logo = 'plugins/Zeitgeist/images/logo.svg'; if (Config::getInstance()->branding['use_custom_logo'] == 1 - && file_exists(Common::getPathToPiwikRoot() . '/misc/user/logo.svg') + && file_exists(Filesystem::getPathToPiwikRoot() . '/misc/user/logo.svg') ) { $logo = 'misc/user/logo.svg'; } if (!$pathOnly) { return Piwik::getPiwikUrl() . $logo; } - return Common::getPathToPiwikRoot() . '/' . $logo; + return Filesystem::getPathToPiwikRoot() . '/' . $logo; } /** @@ -346,7 +346,7 @@ class API /* We always have our application logo */ return true; } else if (Config::getInstance()->branding['use_custom_logo'] == 1 - && file_exists(Common::getPathToPiwikRoot() . '/misc/user/logo.svg') + && file_exists(Filesystem::getPathToPiwikRoot() . '/misc/user/logo.svg') ) { return true; } diff --git a/plugins/CorePluginsAdmin/Controller.php b/plugins/CorePluginsAdmin/Controller.php index d5ae70cf41..867a645953 100644 --- a/plugins/CorePluginsAdmin/Controller.php +++ b/plugins/CorePluginsAdmin/Controller.php @@ -10,12 +10,13 @@ */ namespace Piwik\Plugins\CorePluginsAdmin; -use Piwik\Piwik; use Piwik\Common; use Piwik\Config; +use Piwik\Filesystem; +use Piwik\Piwik; use Piwik\Plugin; -use Piwik\View; use Piwik\Url; +use Piwik\View; /** * @@ -195,7 +196,7 @@ class Controller extends \Piwik\Controller\Admin $pluginName = $this->initPluginModification(); $uninstalled = \Piwik\PluginsManager::getInstance()->uninstallPlugin($pluginName); if (!$uninstalled) { - $path = Common::getPathToPiwikRoot() . '/plugins/' . $pluginName . '/'; + $path = Filesystem::getPathToPiwikRoot() . '/plugins/' . $pluginName . '/'; $messagePermissions = Piwik::getErrorMessageMissingPermissions($path); $messageIntro = Piwik_Translate("Warning: \"%s\" could not be uninstalled. Piwik did not have enough permission to delete the files in $path. ", diff --git a/plugins/CorePluginsAdmin/PluginInstaller.php b/plugins/CorePluginsAdmin/PluginInstaller.php index 0fa791c795..eb7cf7095c 100644 --- a/plugins/CorePluginsAdmin/PluginInstaller.php +++ b/plugins/CorePluginsAdmin/PluginInstaller.php @@ -9,7 +9,7 @@ * @package CorePluginsAdmin */ namespace Piwik\Plugins\CorePluginsAdmin; -use Piwik\Http; +use Piwik\Filesystem; use Piwik\Piwik; use Piwik\Unzip; @@ -93,7 +93,7 @@ class PluginInstaller $pluginTargetPath = PIWIK_USER_PATH . self::PATH_TO_EXTRACT . $this->pluginName; $this->removeFolderIfExists($pluginTargetPath); - Piwik::copyRecursive($tmpPluginFolder, $pluginTargetPath); + Filesystem::copyRecursive($tmpPluginFolder, $pluginTargetPath); } /** @@ -102,7 +102,7 @@ class PluginInstaller private function removeFolderIfExists($pathExtracted) { if (file_exists($pathExtracted)) { - Piwik::unlinkRecursive($pathExtracted, true); + Filesystem::unlinkRecursive($pathExtracted, true); } } diff --git a/plugins/CoreUpdater/Controller.php b/plugins/CoreUpdater/Controller.php index 6853dc3cc9..ae48b8e8ce 100644 --- a/plugins/CoreUpdater/Controller.php +++ b/plugins/CoreUpdater/Controller.php @@ -13,19 +13,19 @@ namespace Piwik\Plugins\CoreUpdater; use Exception; use Piwik\API\Request; use Piwik\ArchiveProcessor\Rules; -use Piwik\Config; -use Piwik\Piwik; use Piwik\Common; +use Piwik\Config; +use Piwik\Filesystem; use Piwik\Http; +use Piwik\Piwik; +use Piwik\Plugins\LanguagesManager\LanguagesManager; +use Piwik\Unzip; +use Piwik\UpdateCheck; use Piwik\Updater; -use Piwik\View; +use Piwik\Updater_UpdateErrorException; use Piwik\Version; -use Piwik\UpdateCheck; -use Piwik\Unzip; +use Piwik\View; use Piwik\View\OneClickDone; -use Piwik\Plugins\CoreUpdater\CoreUpdater; -use Piwik\Plugins\LanguagesManager\LanguagesManager; -use Piwik\Updater_UpdateErrorException; /** * @@ -138,7 +138,7 @@ class Controller extends \Piwik\Controller $this->pathRootExtractedPiwik = $pathExtracted . 'piwik'; if (file_exists($this->pathRootExtractedPiwik)) { - Piwik::unlinkRecursive($this->pathRootExtractedPiwik, true); + Filesystem::unlinkRecursive($this->pathRootExtractedPiwik, true); } $archive = Unzip::factory('PclZip', $this->pathPiwikZip); @@ -173,7 +173,7 @@ class Controller extends \Piwik\Controller { $configFileBefore = PIWIK_USER_PATH . '/config/global.ini.php'; $configFileAfter = PIWIK_USER_PATH . self::CONFIG_FILE_BACKUP; - Piwik::copy($configFileBefore, $configFileAfter); + Filesystem::copy($configFileBefore, $configFileAfter); } private function oneClick_Copy() @@ -189,7 +189,7 @@ class Controller extends \Piwik\Controller * Copy all files to PIWIK_INCLUDE_PATH. * These files are accessed through the dispatcher. */ - Piwik::copyRecursive($this->pathRootExtractedPiwik, PIWIK_INCLUDE_PATH); + Filesystem::copyRecursive($this->pathRootExtractedPiwik, PIWIK_INCLUDE_PATH); /* * These files are visible in the web root and are generally @@ -206,23 +206,23 @@ class Controller extends \Piwik\Controller ); foreach ($specialCases as $file) { - Piwik::copy($this->pathRootExtractedPiwik . $file, PIWIK_DOCUMENT_ROOT . $file); + Filesystem::copy($this->pathRootExtractedPiwik . $file, PIWIK_DOCUMENT_ROOT . $file); } /* * Copy the non-PHP files (e.g., images, css, javascript) */ - Piwik::copyRecursive($this->pathRootExtractedPiwik, PIWIK_DOCUMENT_ROOT, true); + Filesystem::copyRecursive($this->pathRootExtractedPiwik, PIWIK_DOCUMENT_ROOT, true); } /* * Config files may be user (account) specific */ if (PIWIK_INCLUDE_PATH !== PIWIK_USER_PATH) { - Piwik::copyRecursive($this->pathRootExtractedPiwik . '/config', PIWIK_USER_PATH . '/config'); + Filesystem::copyRecursive($this->pathRootExtractedPiwik . '/config', PIWIK_USER_PATH . '/config'); } - Piwik::unlinkRecursive($this->pathRootExtractedPiwik, true); + Filesystem::unlinkRecursive($this->pathRootExtractedPiwik, true); if (function_exists('apc_clear_cache')) { apc_clear_cache(); // clear the system (aka 'opcode') cache @@ -289,7 +289,7 @@ class Controller extends \Piwik\Controller private function doWelcomeUpdates($view, $componentsWithUpdateFile) { $view->new_piwik_version = Version::VERSION; - $view->commandUpgradePiwik = "<br /><code>php " . Common::getPathToPiwikRoot() . "/index.php -- \"module=CoreUpdater\" </code>"; + $view->commandUpgradePiwik = "<br /><code>php " . Filesystem::getPathToPiwikRoot() . "/index.php -- \"module=CoreUpdater\" </code>"; $pluginNamesToUpdate = array(); $coreToUpdate = false; diff --git a/plugins/ImageGraph/API.php b/plugins/ImageGraph/API.php index 6266bf9ed2..8cf9850ce1 100644 --- a/plugins/ImageGraph/API.php +++ b/plugins/ImageGraph/API.php @@ -11,12 +11,13 @@ namespace Piwik\Plugins\ImageGraph; use Exception; +use Piwik\Common; +use Piwik\Filesystem; use Piwik\Period; use Piwik\Piwik; -use Piwik\Common; -use Piwik\Translate; use Piwik\Plugins\API\API as MetaAPI; use Piwik\Plugins\ImageGraph\StaticGraph; +use Piwik\Translate; /** * The ImageGraph.get API call lets you generate beautiful static PNG Graphs for any existing Piwik report. @@ -510,7 +511,7 @@ class API $fileName = self::$DEFAULT_PARAMETERS[$graphType][self::FILENAME_KEY] . '_' . $apiModule . '_' . $apiAction . $idGoal . ' ' . str_replace(',', '-', $date) . ' ' . $idSite . '.png'; $fileName = str_replace(array(' ', '/'), '_', $fileName); - if (!Common::isValidFilename($fileName)) { + if (!Filesystem::isValidFilename($fileName)) { throw new Exception('Error: Image graph filename ' . $fileName . ' is not valid.'); } diff --git a/plugins/Installation/Controller.php b/plugins/Installation/Controller.php index 5df88f94f4..106023195d 100644 --- a/plugins/Installation/Controller.php +++ b/plugins/Installation/Controller.php @@ -18,6 +18,7 @@ use Piwik\Config; use Piwik\DataAccess\ArchiveTableCreator; use Piwik\Db\Adapter; use Piwik\Db; +use Piwik\Filesystem; use Piwik\Http; use Piwik\Piwik; use Piwik\Plugins\LanguagesManager\LanguagesManager; @@ -63,6 +64,16 @@ class Controller extends \Piwik\Controller\Admin Piwik_PostEvent('InstallationController.construct', array($this)); } + protected static function initServerFilesForSecurity() + { + if (Common::isIIS()) { + ServerFilesGenerator::createWebConfigFiles(); + } else { + ServerFilesGenerator::createHtAccessFiles(); + } + ServerFilesGenerator::createWebRootFiles(); + } + /** * Get installation steps * @@ -720,12 +731,7 @@ class Controller extends \Piwik\Controller\Admin $infos['can_auto_update'] = Piwik::canAutoUpdate(); - if (Common::isIIS()) { - Piwik::createWebConfigFiles(); - } else { - Piwik::createHtAccessFiles(); - } - Piwik::createWebRootFiles(); + self::initServerFilesForSecurity(); $infos['phpVersion_minimum'] = $piwik_minimumPHPVersion; $infos['phpVersion'] = PHP_VERSION; @@ -858,7 +864,7 @@ class Controller extends \Piwik\Controller\Admin } // check if filesystem is NFS, if it is file based sessions won't work properly - $infos['is_nfs'] = Piwik::checkIfFileSystemIsNFS(); + $infos['is_nfs'] = Filesystem::checkIfFileSystemIsNFS(); // determine whether there are any errors/warnings from the checks done above $infos['has_errors'] = false; diff --git a/plugins/Installation/FormDatabaseSetup.php b/plugins/Installation/FormDatabaseSetup.php index 71f4643c8a..84efb6e698 100644 --- a/plugins/Installation/FormDatabaseSetup.php +++ b/plugins/Installation/FormDatabaseSetup.php @@ -10,14 +10,14 @@ */ namespace Piwik\Plugins\Installation; -use Piwik\Db\Adapter; -use Piwik\Piwik; -use Piwik\Common; -use Piwik\QuickForm2; use Exception; -use HTML_QuickForm2_Rule; use HTML_QuickForm2_DataSource_Array; use HTML_QuickForm2_Factory; +use HTML_QuickForm2_Rule; +use Piwik\Db\Adapter; +use Piwik\Filesystem; +use Piwik\Piwik; +use Piwik\QuickForm2; use Zend_Db_Adapter_Exception; /** @@ -309,7 +309,7 @@ class FormDatabaseSetup_Rule_checkValidFilename extends HTML_QuickForm2_Rule { $prefix = $this->owner->getValue(); return empty($prefix) - || Common::isValidFilename($prefix); + || Filesystem::isValidFilename($prefix); } } diff --git a/plugins/Installation/ServerFilesGenerator.php b/plugins/Installation/ServerFilesGenerator.php new file mode 100644 index 0000000000..2fbf591a21 --- /dev/null +++ b/plugins/Installation/ServerFilesGenerator.php @@ -0,0 +1,134 @@ +<?php +/** + * Piwik - Open source web analytics + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + * @category Piwik_Plugins + * @package Installation + */ +namespace Piwik\Plugins\Installation; + +use Piwik\Filesystem; + +class ServerFilesGenerator +{ + + /** + * Generate Apache .htaccess files to restrict access + */ + public static function createHtAccessFiles() + { + // deny access to these folders + $directoriesToProtect = array( + '/config', + '/core', + '/lang', + '/tmp', + ); + foreach ($directoriesToProtect as $directoryToProtect) { + Filesystem::createHtAccess(PIWIK_INCLUDE_PATH . $directoryToProtect, $overwrite = true); + } + + // Allow/Deny lives in different modules depending on the Apache version + $allow = "<IfModule mod_access.c>\nAllow from all\n</IfModule>\n<IfModule !mod_access_compat>\n<IfModule mod_authz_host.c>\nAllow from all\n</IfModule>\n</IfModule>\n<IfModule mod_access_compat>\nAllow from all\n</IfModule>\n"; + $deny = "<IfModule mod_access.c>\nDeny from all\n</IfModule>\n<IfModule !mod_access_compat>\n<IfModule mod_authz_host.c>\nDeny from all\n</IfModule>\n</IfModule>\n<IfModule mod_access_compat>\nDeny from all\n</IfModule>\n"; + + // more selective allow/deny filters + $allowAny = "<Files \"*\">\n" . $allow . "Satisfy any\n</Files>\n"; + $allowStaticAssets = "<Files ~ \"\\.(test\.php|gif|ico|jpg|png|svg|js|css|swf)$\">\n" . $allow . "Satisfy any\n</Files>\n"; + $denyDirectPhp = "<Files ~ \"\\.(php|php4|php5|inc|tpl|in|twig)$\">\n" . $deny . "</Files>\n"; + + $directoriesToProtect = array( + '/js' => $allowAny, + '/libs' => $denyDirectPhp . $allowStaticAssets, + '/vendor' => $denyDirectPhp . $allowStaticAssets, + '/plugins' => $denyDirectPhp . $allowStaticAssets, + '/misc/user' => $denyDirectPhp . $allowStaticAssets, + ); + foreach ($directoriesToProtect as $directoryToProtect => $content) { + Filesystem::createHtAccess(PIWIK_INCLUDE_PATH . $directoryToProtect, $overwrite = true, $content); + } + } + + /** + * Generate IIS web.config files to restrict access + * + * Note: for IIS 7 and above + */ + public static function createWebConfigFiles() + { + @file_put_contents(PIWIK_INCLUDE_PATH . '/web.config', + '<?xml version="1.0" encoding="UTF-8"?> +<configuration> + <system.webServer> + <security> + <requestFiltering> + <hiddenSegments> + <add segment="config" /> + <add segment="core" /> + <add segment="lang" /> + <add segment="tmp" /> + </hiddenSegments> + <fileExtensions> + <add fileExtension=".tpl" allowed="false" /> + <add fileExtension=".twig" allowed="false" /> + <add fileExtension=".php4" allowed="false" /> + <add fileExtension=".php5" allowed="false" /> + <add fileExtension=".inc" allowed="false" /> + <add fileExtension=".in" allowed="false" /> + </fileExtensions> + </requestFiltering> + </security> + <directoryBrowse enabled="false" /> + <defaultDocument> + <files> + <remove value="index.php" /> + <add value="index.php" /> + </files> + </defaultDocument> + </system.webServer> +</configuration>'); + + // deny direct access to .php files + $directoriesToProtect = array( + '/libs', + '/vendor', + '/plugins', + ); + foreach ($directoriesToProtect as $directoryToProtect) { + @file_put_contents(PIWIK_INCLUDE_PATH . $directoryToProtect . '/web.config', + '<?xml version="1.0" encoding="UTF-8"?> +<configuration> + <system.webServer> + <security> + <requestFiltering> + <denyUrlSequences> + <add sequence=".php" /> + </denyUrlSequences> + </requestFiltering> + </security> + </system.webServer> +</configuration>'); + } + } + + /** + * Generate default robots.txt, favicon.ico, etc to suppress + * 404 (Not Found) errors in the web server logs, if Piwik + * is installed in the web root (or top level of subdomain). + * + * @see misc/crossdomain.xml + */ + public static function createWebRootFiles() + { + $filesToCreate = array( + '/robots.txt', + '/favicon.ico', + ); + foreach ($filesToCreate as $file) { + @file_put_contents(PIWIK_DOCUMENT_ROOT . $file, ''); + } + } +} \ No newline at end of file diff --git a/plugins/LanguagesManager/API.php b/plugins/LanguagesManager/API.php index eccb98050a..c7bee7603a 100644 --- a/plugins/LanguagesManager/API.php +++ b/plugins/LanguagesManager/API.php @@ -11,9 +11,10 @@ */ namespace Piwik\Plugins\LanguagesManager; -use Piwik\Piwik; use Piwik\Common; use Piwik\Db; +use Piwik\Filesystem; +use Piwik\Piwik; /** * The LanguagesManager API lets you access existing Piwik translations, and change Users languages preferences. @@ -54,7 +55,7 @@ class API public function isLanguageAvailable($languageCode) { return $languageCode !== false - && Common::isValidFilename($languageCode) + && Filesystem::isValidFilename($languageCode) && in_array($languageCode, $this->getAvailableLanguages()); } diff --git a/tests/PHPUnit/Core/CommonTest.php b/tests/PHPUnit/Core/CommonTest.php index b0f69a69c4..e666ad9b5d 100644 --- a/tests/PHPUnit/Core/CommonTest.php +++ b/tests/PHPUnit/Core/CommonTest.php @@ -1,5 +1,6 @@ <?php use Piwik\Common; +use Piwik\Filesystem; /** * Piwik - Open source web analytics @@ -383,7 +384,7 @@ class Core_CommonTest extends PHPUnit_Framework_TestCase "test", "test.txt", "test.......", "en-ZHsimplified", ); foreach ($valid as $toTest) { - $this->assertTrue(Common::isValidFilename($toTest), $toTest . " not valid!"); + $this->assertTrue(Filesystem::isValidFilename($toTest), $toTest . " not valid!"); } } @@ -398,7 +399,7 @@ class Core_CommonTest extends PHPUnit_Framework_TestCase "../test", "/etc/htpasswd", '$var', ';test', '[bizarre]', '', ".htaccess", "very long long eogaioge ageja geau ghaeihieg heiagie aiughaeui hfilename", ); foreach ($notvalid as $toTest) { - $this->assertFalse(Common::isValidFilename($toTest), $toTest . " valid but shouldn't!"); + $this->assertFalse(Filesystem::isValidFilename($toTest), $toTest . " valid but shouldn't!"); } } diff --git a/tests/PHPUnit/Core/PiwikTest.php b/tests/PHPUnit/Core/PiwikTest.php index d9ae053b25..0da18e4fbf 100644 --- a/tests/PHPUnit/Core/PiwikTest.php +++ b/tests/PHPUnit/Core/PiwikTest.php @@ -5,8 +5,9 @@ * @link http://piwik.org * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later */ -use Piwik\Piwik; use Piwik\Access; +use Piwik\Filesystem; +use Piwik\Piwik; use Piwik\Plugins\SitesManager\API; use Piwik\Translate; @@ -259,6 +260,6 @@ class PiwikTest extends DatabaseTestCase */ public function testCheckIfFileSystemIsNFSOnNonNFS() { - $this->assertFalse(Piwik::checkIfFileSystemIsNFS()); + $this->assertFalse(Filesystem::checkIfFileSystemIsNFS()); } } diff --git a/tests/PHPUnit/Core/ReleaseCheckListTest.php b/tests/PHPUnit/Core/ReleaseCheckListTest.php index a70cae6777..cdb12c3ca2 100644 --- a/tests/PHPUnit/Core/ReleaseCheckListTest.php +++ b/tests/PHPUnit/Core/ReleaseCheckListTest.php @@ -1,6 +1,6 @@ <?php -use Piwik\Piwik; use Piwik\Common; +use Piwik\Filesystem; use Piwik\Tracker\Db; /** @@ -57,7 +57,7 @@ class ReleaseCheckListTest extends PHPUnit_Framework_TestCase public function testTemplatesDontContainDebug() { $patternFailIfFound = 'dump('; - $files = Piwik::globr(PIWIK_INCLUDE_PATH . '/plugins', '*.twig'); + $files = Filesystem::globr(PIWIK_INCLUDE_PATH . '/plugins', '*.twig'); foreach ($files as $file) { $content = file_get_contents($file); $this->assertFalse(strpos($content, $patternFailIfFound), 'found in ' . $file); @@ -124,7 +124,7 @@ class ReleaseCheckListTest extends PHPUnit_Framework_TestCase // SVN native does not make this work on windows return; } - foreach (Piwik::globr(PIWIK_DOCUMENT_ROOT, '*') as $file) { + foreach (Filesystem::globr(PIWIK_DOCUMENT_ROOT, '*') as $file) { // skip files in these folders if (strpos($file, '/.git/') !== false || strpos($file, '/documentation/') !== false || -- GitLab