File Manager

Current Directory: /home/astoriaah/www/administrator/components/com_admintools/Model
Viewing File: /home/astoriaah/www/administrator/components/com_admintools/Model/ServerConfigMaker.php
<?php /** * @package admintools * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd * @license GNU General Public License version 3, or later */ namespace Akeeba\AdminTools\Admin\Model; // Protect from unauthorized access use Akeeba\AdminTools\Admin\Helper\ServerTechnology; use Akeeba\AdminTools\Admin\Helper\Storage; use FOF40\Container\Container; use FOF40\Model\Model; use Joomla\CMS\Filesystem\File; use Joomla\CMS\Uri\Uri; defined('_JEXEC') || die(); /** * Abstract class for .htaccess Maker and similar server configuration file makers */ abstract class ServerConfigMaker extends Model { /** * The default configuration of this feature. * * Note that you define an array. It becomes an object in the constructor. We have to do that since PHP doesn't * allow the intitialisation of anonymous objects (like e.g. Javascript) but lets us typecast an array to an object * – just not in the property declaration! * * @var object */ public $defaultConfig = []; /** * The current configuration of this feature * * @var object */ protected $config = null; /** * The Admin Tools configuration key under which we'll save $config as a JSON-encoded string * * @var string */ protected $configKey = ''; /** * The methods which are allowed to call the saveConfiguration method. Each line is in the format: * Full\Class\Name::methodName * * @var array */ protected $allowedCallersForSave = []; /** * The methods which are allowed to call the writeConfigFile method. Each line is in the format: * Full\Class\Name::methodName * * @var array */ protected $allowedCallersForWrite = []; /** * The methods which are allowed to call the makeConfigFile method. Each line is in the format: * Full\Class\Name::methodName * * @var array */ protected $allowedCallersForMake = []; /** * The base name of the configuration file being saved by this feature, e.g. ".htaccess". The file is always saved * in the site's root. Any old files under that name are renamed with a .admintools suffix. * * @var string */ protected $configFileName = ''; /** * Public class constructor * * It modified the default configuration with the domain name and path of the current site, as detected by Joomla!. * * @param Container $container The configuration variables to this model * @param array $config Configuration values for this model */ public function __construct(Container $container, array $config) { parent::__construct($container, $config); $myURI = Uri::getInstance(); $path = $myURI->getPath(); $path_parts = explode('/', $path); $path_parts = array_slice($path_parts, 0, count($path_parts) - 2); $path = implode('/', $path_parts); $myURI->setPath($path); // Unset any query parameters $myURI->setQuery(''); $host = $myURI->toString(); $host = substr($host, strpos($host, '://') + 3); $path = trim($path, '/'); $this->defaultConfig['rewritebase'] = '/'; $this->defaultConfig['httphost'] = $host; $this->defaultConfig['httpshost'] = $host; if (!empty($path)) { $this->defaultConfig['rewritebase'] = $path; } $this->defaultConfig = (object) $this->defaultConfig; } public function getConfigFileName(): string { return $this->configFileName; } /** * Loads the feature's configuration from the database * * @return object */ public function loadConfiguration() { if (is_null($this->config)) { $params = Storage::getInstance(); $savedConfig = $params->getValue($this->configKey, ''); if (!empty($savedConfig)) { if (function_exists('base64_encode') && function_exists('base64_encode')) { $savedConfig = base64_decode($savedConfig); } $savedConfig = json_decode($savedConfig, true); } else { $savedConfig = []; } $config = $this->defaultConfig; if (!empty($savedConfig)) { foreach ($savedConfig as $key => $value) { $config->$key = $value; } } $this->config = $config; } return $this->config; } /** * Save the configuration to the database * * @param object|array $data The data to save * @param bool $isConfigInput True = $data is object. False (default) = $data is an array. */ public function saveConfiguration($data, $isConfigInput = false) { // Make sure we are called by an expected caller ServerTechnology::checkCaller($this->allowedCallersForSave); if ($isConfigInput) { $config = $data; } else { $config = $this->defaultConfig; if (!empty($data)) { $ovars = get_object_vars($config); $okeys = array_keys($ovars); foreach ($data as $key => $value) { if (in_array($key, $okeys)) { // Clean up array types coming from textareas if (in_array($key, [ 'hoggeragents', 'bepexdirs', 'bepextypes', 'fepexdirs', 'fepextypes', 'exceptionfiles', 'exceptionfolders', 'exceptiondirs', 'fullaccessdirs', 'httpsurls', ]) ) { if (empty($value)) { $value = []; } else { $value = trim($value); $value = explode("\n", $value); if (!empty($value)) { $ret = []; foreach ($value as $v) { $vv = trim($v); if (!empty($vv)) { $ret[] = $vv; } } if (!empty($ret)) { $value = $ret; } else { $value = []; } } } } $config->$key = $value; } } } } // Make sure nobody tried to add the php extension to the list of allowed extension $disallowedExtensions = ['php', 'phP', 'pHp', 'pHP', 'Php', 'PhP', 'PHp', 'PHP']; foreach ($disallowedExtensions as $ext) { $pos = array_search($ext, $config->bepextypes); if ($pos !== false) { unset($config->bepextypes[$pos]); } $pos = array_search($ext, $config->fepextypes); if ($pos !== false) { unset($config->fepextypes[$pos]); } } $this->config = $config; $config = json_encode($config); // This keeps JRegistry from happily corrupting our data :@ if (function_exists('base64_encode') && function_exists('base64_encode')) { $config = base64_encode($config); } $params = Storage::getInstance(); $params->setValue($this->configKey, $config); $params->setValue('quickstart', 1); $params->save(); } /** * Create and return the configuration file's contents. This is the heart of these features. * * @return string */ abstract public function makeConfigFile(); /** * Make the configuration file and write it to the disk * * @return bool */ public function writeConfigFile(): bool { // Make sure we are called by an expected caller ServerTechnology::checkCaller($this->allowedCallersForWrite); $htaccessPath = JPATH_ROOT . '/' . $this->configFileName; $backupPath = JPATH_ROOT . '/' . $this->configFileName . '.admintools'; if (@file_exists($htaccessPath)) { if (!@copy($htaccessPath, $backupPath)) { File::copy(basename($htaccessPath), basename($backupPath), JPATH_ROOT); } } $configFileContents = $this->makeConfigFile(); /** * Convert CRLF to LF before saving the file. This would work around an issue with Windows browsers using CRLF * line endings in text areas which would then be transferred verbatim to the output file. Most servers don't * mind, but NginX will break hard when it sees the CR in the configuration file. */ $configFileContents = str_replace("\r\n", "\n", $configFileContents); if (!@file_put_contents($htaccessPath, $configFileContents)) { return File::write($htaccessPath, $configFileContents); } return true; } /** * Given a server configuration file, strips out header comments and create an hash of the remaining contents * * @param string $contents * * @return string */ public function getConfigHash(string $contents): string { // Get the lines of the configuration $lines = explode("\n", $contents); // Trim the lines and convert comments to empty lines $lines = array_map(function ($line) { $line = trim($line); if (substr($line, 0, 1) === '#') { $line = ''; } return $line; }, $lines); // Remove empty lines $lines = array_filter($lines, function ($line) { return !empty($line); }); // Get the MD5 of the normalised contents return md5(implode("\n", $lines)); } /** * Checks if current redirection rules do match the URL saved inside the live_site variable. For example: * - live_site: www.example.com - Redirect www to non-www WRONG! * - live_site: www.example.com - Redirect non-www to www CORRECT! * * @return bool Are the live_site variable and current redirection rules compatible? */ public function enableRedirects(): bool { $live_site = $this->container->platform->getConfig()->get('live_site', ''); // No value set (90% of cases), we're good to go if (!$live_site) { return true; } // The user put the protocol in the live site? That's an hard no if (stripos($live_site, 'http') !== false) { return false; } $config = $this->loadConfiguration(); // No redirection set? We're good to go if (!$config->wwwredir) { return true; } // Got www site and a redirect from www to non-www, that's wrong if ((stripos($live_site, 'www.') === 0) && ($config->wwwredir === 2)) { return false; } // Got non-www site and a redirect from non-www to www, that's wrong if ((stripos($live_site, 'www.') === false) && ($config->wwwredir === 1)) { return false; } // Otherwise we're good to go return true; } /** * Escapes a string so that it's a neutral string inside a regular expression. * * @param string $str The string to escape * * @return string The escaped string */ protected function escape_string_for_regex($str): string { //All regex special chars (according to arkani at iol dot pt below): // \ ^ . $ | ( ) [ ] // * + ? { } , - $patterns = [ '/\//', '/\^/', '/\./', '/\$/', '/\|/', '/\(/', '/\)/', '/\[/', '/\]/', '/\*/', '/\+/', '/\?/', '/\{/', '/\}/', '/\,/', '/\-/', ]; $replace = [ '\/', '\^', '\.', '\$', '\|', '\(', '\)', '\[', '\]', '\*', '\+', '\?', '\{', '\}', '\,', '\-', ]; return preg_replace($patterns, $replace, $str); } }