\n" . "\t" . $allow . "\n" . "\n"; $allowStaticAssets = "# Serve HTML files as text/html mime type - Note: requires mod_mime apache module!\n" . "\n" . " AddHandler text/html .html\n" . " AddHandler text/html .htm\n" . "\n\n" . "# Allow to serve static files which are safe\n" . "\n" . $allow . "\n" . "\n"; $noCachePreview = " # do not cache preview container files Header set Cache-Control \"Cache-Control: private, no-cache, no-store\" "; $allowManifestFile = "# Allow to serve manifest.json\n" . "\n" . $allow . "\n" . "\n"; $directoriesToProtect = array( '/js' => $allowAny . $noCachePreview, '/libs' => $denyAll . $allowStaticAssets, '/vendor' => $denyAll . $allowStaticAssets, '/plugins' => $denyAll . $allowStaticAssets . $allowManifestFile, '/misc/user' => $denyAll . $allowStaticAssets, '/node_modules' => $denyAll . $allowStaticAssets, ); foreach ($directoriesToProtect as $directoryToProtect => $content) { self::createHtAccess(PIWIK_INCLUDE_PATH . $directoryToProtect, $overwrite = true, $content); } // deny access to these folders $directoriesToProtect = array( PIWIK_USER_PATH . '/config' => $denyAll, PIWIK_INCLUDE_PATH. '/core' => $denyAll, PIWIK_INCLUDE_PATH . '/lang' => $denyAll, StaticContainer::get('path.tmp') => $denyAll, ); if (!empty($GLOBALS['CONFIG_INI_PATH_RESOLVER']) && is_callable($GLOBALS['CONFIG_INI_PATH_RESOLVER'])) { $file = call_user_func($GLOBALS['CONFIG_INI_PATH_RESOLVER']); $directoriesToProtect[dirname($file)] = $denyAll; } $gitDir = PIWIK_INCLUDE_PATH . '/.git'; if (is_dir($gitDir) && is_writable($gitDir)) { $directoriesToProtect[$gitDir] = $denyAll; } foreach ($directoriesToProtect as $directoryToProtect => $content) { self::createHtAccess($directoryToProtect, $overwrite = true, $content); } } /** * Create .htaccess file in specified directory * * Apache-specific; for IIS @see web.config * * .htaccess files are created on all webservers even Nginx, as sometimes Nginx knows how to handle .htaccess files * * @param string $path without trailing slash * @param bool $overwrite whether to overwrite an existing file or not * @param string $content */ protected static function createHtAccess($path, $overwrite, $content) { $file = $path . '/.htaccess'; $content = "# This file is auto generated by Matomo, do not edit directly\n# Please report any issue or improvement directly to the Matomo team.\n\n" . $content; if ($overwrite || !file_exists($file)) { @file_put_contents($file, $content, LOCK_EX); } } /** * Generate IIS web.config files to restrict access * * Note: for IIS 7 and above */ protected static function createWebConfigFiles() { if (!SettingsServer::isIIS()) { return; } @file_put_contents(PIWIK_INCLUDE_PATH . '/web.config', ' '); // deny direct access to .php files $directoriesToProtect = array( '/libs', '/vendor', '/plugins', '/node_modules', ); $additionForPlugins = ' '; foreach ($directoriesToProtect as $directoryToProtect) { @file_put_contents(PIWIK_INCLUDE_PATH . $directoryToProtect . '/web.config', ' ' . ($directoryToProtect === '/plugins' ? $additionForPlugins : '') . ' '); } } public static function deleteWebConfigFiles() { $path = PIWIK_INCLUDE_PATH; @unlink($path . '/web.config'); @unlink($path . '/libs/web.config'); @unlink($path . '/vendor/web.config'); @unlink($path . '/plugins/web.config'); @unlink($path . '/node_modules/web.config'); } /** * 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) { $path = PIWIK_DOCUMENT_ROOT . $file; if(!file_exists($path)) { @file_put_contents($path, ''); } } } /** * @return string */ protected static function getDenyAllHtaccessContent() { $deny = self::getDenyHtaccessContent(); $denyAll = "# First, deny access to all files in this directory\n" . "\n" . $deny . "\n" . "\n"; return $denyAll; } /** * @return string */ public static function getDenyHtaccessContent() { # Source: https://github.com/phpbb/phpbb/pull/2386/files#diff-f72a38c4bec79cc6ded3f8e435d6bd55L11 # With Apache 2.4 the "Order, Deny" syntax has been deprecated and moved from # module mod_authz_host to a new module called mod_access_compat (which may be # disabled) and a new "Require" syntax has been introduced to mod_authz_host. # We could just conditionally provide both versions, but unfortunately Apache # does not explicitly tell us its version if the module mod_version is not # available. In this case, we check for the availability of module # mod_authz_core (which should be on 2.4 or higher only) as a best guess. $deny = << Order Deny,Allow Deny from All = 2.4> Require all denied Order Deny,Allow Deny from All Require all denied HTACCESS_DENY; return $deny; } /** * @return string */ public static function getAllowHtaccessContent() { $allow = << Order Allow,Deny Allow from All = 2.4> Require all granted Order Allow,Deny Allow from All Require all granted HTACCESS_ALLOW; return $allow; } /** * Deletes all existing .htaccess files and web.config files that Matomo may have created, */ public static function deleteHtAccessFiles() { $files = Filesystem::globr(PIWIK_INCLUDE_PATH, ".htaccess"); // that match the list of directories we create htaccess files // (ie. not the root /.htaccess) $directoriesWithAutoHtaccess = array( '/js', '/libs', '/vendor', '/plugins', '/misc/user', '/node_modules', '/config', '/core', '/lang', '/tmp', ); foreach ($files as $file) { foreach ($directoriesWithAutoHtaccess as $dirToDelete) { // only delete the first .htaccess and not the ones in sub-directories $pathToDelete = $dirToDelete . '/.htaccess'; if (strpos($file, $pathToDelete) !== false) { @unlink($file); } } } } }